From 375c0be513db6f6f6cd2b58f0b01d0fbd3edadc8 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 16 Jun 2026 15:51:05 -0700 Subject: [PATCH] feat(galileo-adk): rebrand to SPLUNK_AO_* env vars and SplunkAO* classes (HYBIM-716) Core package (galileo-adk/src/galileo_adk/): - Rename GalileoADKCallback -> SplunkAOADKCallback - Rename GalileoADKObserver -> SplunkAOADKObserver - Rename GalileoADKPlugin -> SplunkAOADKPlugin - Rename GALILEO_* env vars -> SPLUNK_AO_* throughout callback.py, decorator.py, observer.py, plugin.py - Update __init__.py exports to SplunkAO* names - Update pyproject.toml: galileo dependency -> splunk-ao Core SDK (src/splunk_ao/config.py): - Add ("SPLUNK_AO_API_URL", "GALILEO_API_URL") to _bridge_env_vars() so SPLUNK_AO_API_URL is correctly passed to galileo-core for self-hosted deployments Documentation (galileo-adk/README.md): - Replace GalileoADK* class names with SplunkAOADK* in all code examples - Replace GALILEO_* env vars -> SPLUNK_AO_* in configuration tables - Update brand mentions (Galileo -> Splunk AO) Tests: - Update all class names and env var references in test_callback.py, test_observer.py, test_plugin.py, test_retriever.py Co-authored-by: Cursor --- galileo-adk/README.md | 56 ++++----- galileo-adk/pyproject.toml | 8 +- galileo-adk/src/galileo_adk/__init__.py | 12 +- galileo-adk/src/galileo_adk/callback.py | 30 ++--- galileo-adk/src/galileo_adk/decorator.py | 16 +-- galileo-adk/src/galileo_adk/observer.py | 8 +- galileo-adk/src/galileo_adk/plugin.py | 30 ++--- galileo-adk/tests/test_callback.py | 118 +++++++++--------- galileo-adk/tests/test_observer.py | 46 +++---- galileo-adk/tests/test_plugin.py | 150 +++++++++++------------ galileo-adk/tests/test_retriever.py | 68 +++++----- src/splunk_ao/config.py | 1 + 12 files changed, 272 insertions(+), 271 deletions(-) diff --git a/galileo-adk/README.md b/galileo-adk/README.md index f63aa897..191c5c57 100644 --- a/galileo-adk/README.md +++ b/galileo-adk/README.md @@ -4,7 +4,7 @@ [![Python versions](https://img.shields.io/pypi/pyversions/galileo-adk.svg)](https://pypi.org/project/galileo-adk/) [![License](https://img.shields.io/pypi/l/galileo-adk.svg)](https://github.com/rungalileo/galileo-python/blob/main/LICENSE) -Galileo observability for [Google ADK](https://github.com/google/adk-python) agents. Automatic tracing of agent runs, LLM calls, and tool executions. +Splunk AO observability for [Google ADK](https://github.com/google/adk-python) agents. Automatic tracing of agent runs, LLM calls, and tool executions. ## Installation @@ -12,19 +12,19 @@ Galileo observability for [Google ADK](https://github.com/google/adk-python) age pip install galileo-adk ``` -**Requirements:** Python 3.10+, a [Galileo API key](https://www.rungalileo.io/), and a [Google AI API key](https://aistudio.google.com/apikey) +**Requirements:** Python 3.10+, a [Splunk AO API key](https://www.splunk.com/), and a [Google AI API key](https://aistudio.google.com/apikey) ## Quick Start ```python import asyncio -from galileo_adk import GalileoADKPlugin +from galileo_adk import SplunkAOADKPlugin from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.genai import types async def main(): - plugin = GalileoADKPlugin(project="my-project", log_stream="production") + plugin = SplunkAOADKPlugin(project="my-project", log_stream="production") agent = LlmAgent(name="assistant", model="gemini-2.0-flash", instruction="You are helpful.") runner = Runner(agent=agent, plugins=[plugin]) @@ -34,7 +34,7 @@ async def main(): print(event.content.parts[0].text) if __name__ == "__main__": - # Set environment variables: GALILEO_API_KEY, GOOGLE_API_KEY + # Set environment variables: SPLUNK_AO_API_KEY, GOOGLE_API_KEY asyncio.run(main()) ``` @@ -42,25 +42,25 @@ if __name__ == "__main__": | Parameter | Environment Variable | Description | |-----------|---------------------|-------------| -| `project` | `GALILEO_PROJECT` | Project name (required unless `ingestion_hook` provided) | -| `log_stream` | `GALILEO_LOG_STREAM` | Log stream name (required unless `ingestion_hook` provided) | -| `ingestion_hook` | - | Custom callback for trace data (bypasses Galileo backend) | +| `project` | `SPLUNK_AO_PROJECT` | Project name (required unless `ingestion_hook` provided) | +| `log_stream` | `SPLUNK_AO_LOG_STREAM` | Log stream name (required unless `ingestion_hook` provided) | +| `ingestion_hook` | - | Custom callback for trace data (bypasses Splunk AO backend) | ## Features ### Session Tracking -All traces with the same `session_id` are automatically grouped into a Galileo session, enabling conversation-level tracking: +All traces with the same `session_id` are automatically grouped into a Splunk AO session, enabling conversation-level tracking: ```python import asyncio -from galileo_adk import GalileoADKPlugin +from galileo_adk import SplunkAOADKPlugin from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.genai import types async def main(): - plugin = GalileoADKPlugin(project="my-project", log_stream="production") + plugin = SplunkAOADKPlugin(project="my-project", log_stream="production") agent = LlmAgent(name="assistant", model="gemini-2.0-flash", instruction="You are helpful.") runner = Runner(agent=agent, plugins=[plugin]) @@ -80,7 +80,7 @@ async def main(): print(f"Response 2: {event.content.parts[0].text}") if __name__ == "__main__": - # Set environment variables: GALILEO_API_KEY, GOOGLE_API_KEY + # Set environment variables: SPLUNK_AO_API_KEY, GOOGLE_API_KEY asyncio.run(main()) ``` @@ -90,14 +90,14 @@ Attach custom metadata to traces using ADK's `RunConfig`. Metadata is propagated ```python import asyncio -from galileo_adk import GalileoADKPlugin +from galileo_adk import SplunkAOADKPlugin from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.adk.agents.run_config import RunConfig from google.genai import types async def main(): - plugin = GalileoADKPlugin(project="my-project", log_stream="production") + plugin = SplunkAOADKPlugin(project="my-project", log_stream="production") agent = LlmAgent(name="assistant", model="gemini-2.0-flash", instruction="You are helpful.") runner = Runner(agent=agent, plugins=[plugin]) @@ -121,7 +121,7 @@ async def main(): print(event.content.parts[0].text) if __name__ == "__main__": - # Set environment variables: GALILEO_API_KEY, GOOGLE_API_KEY + # Set environment variables: SPLUNK_AO_API_KEY, GOOGLE_API_KEY asyncio.run(main()) ``` @@ -131,13 +131,13 @@ For granular control over which callbacks to use, attach them directly to your a ```python import asyncio -from galileo_adk import GalileoADKCallback +from galileo_adk import SplunkAOADKCallback from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.genai import types async def main(): - callback = GalileoADKCallback(project="my-project", log_stream="production") + callback = SplunkAOADKCallback(project="my-project", log_stream="production") agent = LlmAgent( name="assistant", @@ -158,19 +158,19 @@ async def main(): print(event.content.parts[0].text) if __name__ == "__main__": - # Set environment variables: GALILEO_API_KEY, GOOGLE_API_KEY + # Set environment variables: SPLUNK_AO_API_KEY, GOOGLE_API_KEY asyncio.run(main()) ``` ### Retriever Spans -By default, all `FunctionTool` calls are logged as tool spans. To log a retriever function as a **retriever span** (enabling RAG quality metrics in Galileo), decorate it with `@galileo_retriever`: +By default, all `FunctionTool` calls are logged as tool spans. To log a retriever function as a **retriever span** (enabling RAG quality metrics in Splunk AO), decorate it with `@splunk_ao_retriever`: ```python -from galileo_adk import galileo_retriever +from galileo_adk import splunk_ao_retriever from google.adk.tools import FunctionTool -@galileo_retriever +@splunk_ao_retriever def search_docs(query: str) -> str: """Search the knowledge base.""" results = my_vector_db.search(query) @@ -181,20 +181,20 @@ tool = FunctionTool(search_docs) ### Ingestion Hook -Intercept traces for custom processing before forwarding to Galileo: +Intercept traces for custom processing before forwarding to Splunk AO: ```python import asyncio import os from splunk_ao import GalileoLogger -from galileo_adk import GalileoADKPlugin +from galileo_adk import SplunkAOADKPlugin from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.genai import types logger = GalileoLogger( - project=os.getenv("GALILEO_PROJECT", "my-project"), - log_stream=os.getenv("GALILEO_LOG_STREAM", "dev"), + project=os.getenv("SPLUNK_AO_PROJECT", "my-project"), + log_stream=os.getenv("SPLUNK_AO_LOG_STREAM", "dev"), ) def my_ingestion_hook(request): @@ -214,7 +214,7 @@ def my_ingestion_hook(request): logger.ingest_traces(request) async def main(): - plugin = GalileoADKPlugin(ingestion_hook=my_ingestion_hook) + plugin = SplunkAOADKPlugin(ingestion_hook=my_ingestion_hook) agent = LlmAgent(name="assistant", model="gemini-2.0-flash", instruction="You are helpful.") runner = Runner(agent=agent, plugins=[plugin]) @@ -224,13 +224,13 @@ async def main(): print(event.content.parts[0].text) if __name__ == "__main__": - # Set environment variables: GALILEO_API_KEY, GOOGLE_API_KEY + # Set environment variables: SPLUNK_AO_API_KEY, GOOGLE_API_KEY asyncio.run(main()) ``` ## Resources -- [Galileo Documentation](https://docs.rungalileo.io/) +- [Splunk AO Documentation](https://docs.splunk.com/) - [Google ADK Documentation](https://google.github.io/adk-docs/) ## License diff --git a/galileo-adk/pyproject.toml b/galileo-adk/pyproject.toml index 9c43cf5e..aeb15526 100644 --- a/galileo-adk/pyproject.toml +++ b/galileo-adk/pyproject.toml @@ -43,10 +43,10 @@ python_files = ["test_*.py"] python_classes = ["Test*"] python_functions = ["test_*"] env = [ - "GALILEO_CONSOLE_URL=http://fake.test:8088", - "GALILEO_API_KEY=api-1234567890", - "GALILEO_PROJECT=test-project", - "GALILEO_LOG_STREAM=test-log-stream", + "SPLUNK_AO_CONSOLE_URL=http://fake.test:8088", + "SPLUNK_AO_API_KEY=api-1234567890", + "SPLUNK_AO_PROJECT=test-project", + "SPLUNK_AO_LOG_STREAM=test-log-stream", ] addopts = [ "-v", diff --git a/galileo-adk/src/galileo_adk/__init__.py b/galileo-adk/src/galileo_adk/__init__.py index 8f709707..47011c8d 100644 --- a/galileo-adk/src/galileo_adk/__init__.py +++ b/galileo-adk/src/galileo_adk/__init__.py @@ -1,13 +1,13 @@ __version__ = "2.0.1" -from galileo_adk.callback import GalileoADKCallback -from galileo_adk.decorator import galileo_retriever +from galileo_adk.callback import SplunkAOADKCallback +from galileo_adk.decorator import splunk_ao_retriever from galileo_adk.observer import get_custom_metadata -from galileo_adk.plugin import GalileoADKPlugin +from galileo_adk.plugin import SplunkAOADKPlugin __all__ = [ - "GalileoADKPlugin", - "GalileoADKCallback", - "galileo_retriever", + "SplunkAOADKPlugin", + "SplunkAOADKCallback", + "splunk_ao_retriever", "get_custom_metadata", ] diff --git a/galileo-adk/src/galileo_adk/callback.py b/galileo-adk/src/galileo_adk/callback.py index 9768a72a..05ed65b6 100644 --- a/galileo-adk/src/galileo_adk/callback.py +++ b/galileo-adk/src/galileo_adk/callback.py @@ -1,4 +1,4 @@ -"""Galileo ADK Callback - Agent-level observability for Google ADK.""" +"""Splunk AO ADK Callback - Agent-level observability for Google ADK.""" from __future__ import annotations @@ -10,7 +10,7 @@ from splunk_ao.schema.trace import TracesIngestRequest from galileo_adk.observer import ( - GalileoObserver, + SplunkAOObserver, get_agent_name_from_tool_context, get_custom_metadata, get_invocation_id, @@ -35,15 +35,15 @@ _logger = logging.getLogger(__name__) -class GalileoADKCallback: - """Galileo observability callbacks for Google ADK agents. +class SplunkAOADKCallback: + """Splunk AO observability callbacks for Google ADK agents. Use this class when you need agent-level callbacks (passed directly to Agent constructors). For runner-level observability with full lifecycle tracking, - use GalileoADKPlugin instead. + use SplunkAOADKPlugin instead. - ADK session_id is automatically mapped to Galileo sessions for trace grouping. - All traces from the same ADK session will be grouped together in Galileo. + ADK session_id is automatically mapped to Splunk AO sessions for trace grouping. + All traces from the same ADK session will be grouped together in Splunk AO. Per-invocation metadata is passed via ADK's native RunConfig.custom_metadata: @@ -53,17 +53,17 @@ class GalileoADKCallback: Parameters ---------- project : str, optional - Galileo project name. Can also be set via GALILEO_PROJECT env var. + Splunk AO project name. Can also be set via SPLUNK_AO_PROJECT env var. Required unless `ingestion_hook` is provided. log_stream : str, optional - Log stream name within the project. Can also be set via GALILEO_LOG_STREAM env var. + Log stream name within the project. Can also be set via SPLUNK_AO_LOG_STREAM env var. Required unless `ingestion_hook` is provided. ingestion_hook : Callable[[TracesIngestRequest], None], optional - Custom callback to receive trace data instead of sending to Galileo. + Custom callback to receive trace data instead of sending to Splunk AO. Example ------- - >>> callback = GalileoADKCallback(project="my-project", log_stream="production") + >>> callback = SplunkAOADKCallback(project="my-project", log_stream="production") >>> agent = Agent( ... before_agent_callback=callback.before_agent_callback, ... after_agent_callback=callback.after_agent_callback, @@ -76,15 +76,15 @@ def __init__( log_stream: str | None = None, ingestion_hook: Callable[[TracesIngestRequest], None] | None = None, ) -> None: - effective_project = project or os.environ.get("GALILEO_PROJECT") - effective_log_stream = log_stream or os.environ.get("GALILEO_LOG_STREAM") + effective_project = project or os.environ.get("SPLUNK_AO_PROJECT") + effective_log_stream = log_stream or os.environ.get("SPLUNK_AO_LOG_STREAM") if not ingestion_hook and (not effective_project or not effective_log_stream): raise ValueError( "Both 'project' and 'log_stream' must be provided via parameters or " - "GALILEO_PROJECT/GALILEO_LOG_STREAM environment variables" + "SPLUNK_AO_PROJECT/SPLUNK_AO_LOG_STREAM environment variables" ) - self._observer = GalileoObserver( + self._observer = SplunkAOObserver( project=project, log_stream=log_stream, ingestion_hook=ingestion_hook, diff --git a/galileo-adk/src/galileo_adk/decorator.py b/galileo-adk/src/galileo_adk/decorator.py index 32a6c228..048dc743 100644 --- a/galileo-adk/src/galileo_adk/decorator.py +++ b/galileo-adk/src/galileo_adk/decorator.py @@ -1,4 +1,4 @@ -"""Decorators for marking ADK tools with Galileo observability metadata.""" +"""Decorators for marking ADK tools with Splunk AO observability metadata.""" from __future__ import annotations @@ -6,20 +6,20 @@ from typing import Any -def galileo_retriever(func: Callable[..., Any]) -> Callable[..., Any]: - """Mark a function as a retriever for Galileo observability. +def splunk_ao_retriever(func: Callable[..., Any]) -> Callable[..., Any]: + """Mark a function as a retriever for Splunk AO observability. - When a function decorated with @galileo_retriever is wrapped in a - FunctionTool, Galileo will log it as a retriever span (node_type="retriever") + When a function decorated with @splunk_ao_retriever is wrapped in a + FunctionTool, Splunk AO will log it as a retriever span (node_type="retriever") instead of a tool span, enabling RAG quality metrics. Example ------- - >>> from galileo_adk import galileo_retriever - >>> @galileo_retriever + >>> from galileo_adk import splunk_ao_retriever + >>> @splunk_ao_retriever ... def my_search(query: str) -> str: ... return search_docs(query) >>> tool = FunctionTool(my_search) """ - func._galileo_is_retriever = True # type: ignore[attr-defined] + func._splunk_ao_is_retriever = True # type: ignore[attr-defined] return func diff --git a/galileo-adk/src/galileo_adk/observer.py b/galileo-adk/src/galileo_adk/observer.py index 874cdc52..3c1076b1 100644 --- a/galileo-adk/src/galileo_adk/observer.py +++ b/galileo-adk/src/galileo_adk/observer.py @@ -1,4 +1,4 @@ -"""Shared observability logic for Galileo ADK integration.""" +"""Shared observability logic for Splunk AO ADK integration.""" from __future__ import annotations @@ -132,7 +132,7 @@ def get_custom_metadata(context: Any) -> dict[str, Any]: return {} -class GalileoObserver: +class SplunkAOObserver: """Shared observability logic for Plugin and Callback interfaces.""" _trace_builder: TraceBuilder | None @@ -393,12 +393,12 @@ def _is_retriever_tool(self, tool: Any) -> bool: Detection uses two strategies: 1. isinstance check against google.adk.tools.retrieval.BaseRetrievalTool - 2. Custom functions decorated with @galileo_retriever (sets _galileo_is_retriever on func) + 2. Custom functions decorated with @splunk_ao_retriever (sets _splunk_ao_is_retriever on func) """ if _BaseRetrievalTool is not None and isinstance(tool, _BaseRetrievalTool): return True func = getattr(tool, "func", None) - return bool(func is not None and getattr(func, "_galileo_is_retriever", False)) + return bool(func is not None and getattr(func, "_splunk_ao_is_retriever", False)) def on_tool_start( self, diff --git a/galileo-adk/src/galileo_adk/plugin.py b/galileo-adk/src/galileo_adk/plugin.py index cb35bea2..00dc0f0d 100644 --- a/galileo-adk/src/galileo_adk/plugin.py +++ b/galileo-adk/src/galileo_adk/plugin.py @@ -1,4 +1,4 @@ -"""Galileo ADK Plugin - Runner-level observability for Google ADK.""" +"""Splunk AO ADK Plugin - Runner-level observability for Google ADK.""" from __future__ import annotations @@ -12,7 +12,7 @@ from splunk_ao.schema.trace import TracesIngestRequest from galileo_adk.observer import ( - GalileoObserver, + SplunkAOObserver, get_agent_name_from_tool_context, get_custom_metadata, get_invocation_id, @@ -99,14 +99,14 @@ def _extract_status_code(error: Exception) -> int: return 500 -class GalileoADKPlugin(BasePlugin): - """Galileo observability plugin for Google ADK Runner. +class SplunkAOADKPlugin(BasePlugin): + """Splunk AO observability plugin for Google ADK Runner. Provides full lifecycle observability including invocation, agent, LLM, and tool spans. Pass to Runner's plugins list for automatic trace capture. - ADK session_id is automatically mapped to Galileo sessions for trace grouping. - All traces from the same ADK session will be grouped together in Galileo. + ADK session_id is automatically mapped to Splunk AO sessions for trace grouping. + All traces from the same ADK session will be grouped together in Splunk AO. Per-invocation metadata is passed via ADK's native RunConfig.custom_metadata: @@ -116,17 +116,17 @@ class GalileoADKPlugin(BasePlugin): Parameters ---------- project : str, optional - Galileo project name. Can also be set via GALILEO_PROJECT env var. + Splunk AO project name. Can also be set via SPLUNK_AO_PROJECT env var. Required unless `ingestion_hook` is provided. log_stream : str, optional - Log stream name within the project. Can also be set via GALILEO_LOG_STREAM env var. + Log stream name within the project. Can also be set via SPLUNK_AO_LOG_STREAM env var. Required unless `ingestion_hook` is provided. ingestion_hook : Callable[[TracesIngestRequest], None], optional - Custom callback to receive trace data instead of sending to Galileo. + Custom callback to receive trace data instead of sending to Splunk AO. Example ------- - >>> plugin = GalileoADKPlugin(project="my-project", log_stream="production") + >>> plugin = SplunkAOADKPlugin(project="my-project", log_stream="production") >>> runner = Runner(agent=agent, plugins=[plugin]) >>> run_config = RunConfig(custom_metadata={"turn": 1}) >>> await runner.run_async(..., run_config=run_config) @@ -138,16 +138,16 @@ def __init__( log_stream: str | None = None, ingestion_hook: Callable[[TracesIngestRequest], None] | None = None, ) -> None: - effective_project = project or os.environ.get("GALILEO_PROJECT") - effective_log_stream = log_stream or os.environ.get("GALILEO_LOG_STREAM") + effective_project = project or os.environ.get("SPLUNK_AO_PROJECT") + effective_log_stream = log_stream or os.environ.get("SPLUNK_AO_LOG_STREAM") if not ingestion_hook and (not effective_project or not effective_log_stream): raise ValueError( "Both 'project' and 'log_stream' must be provided via parameters or " - "GALILEO_PROJECT/GALILEO_LOG_STREAM environment variables" + "SPLUNK_AO_PROJECT/SPLUNK_AO_LOG_STREAM environment variables" ) - super().__init__(name="galileo") - self._observer = GalileoObserver( + super().__init__(name="splunk_ao") + self._observer = SplunkAOObserver( project=project, log_stream=log_stream, ingestion_hook=ingestion_hook, diff --git a/galileo-adk/tests/test_callback.py b/galileo-adk/tests/test_callback.py index f346a818..1458d7e7 100644 --- a/galileo-adk/tests/test_callback.py +++ b/galileo-adk/tests/test_callback.py @@ -3,7 +3,7 @@ import pytest -from galileo_adk import GalileoADKCallback +from galileo_adk import SplunkAOADKCallback from .mocks import ( MockCallbackContext, @@ -81,45 +81,45 @@ def adk_user_message() -> Generator[MagicMock, None, None]: return message -class TestGalileoADKCallbackInit: +class TestSplunkAOADKCallbackInit: """Tests for callback initialization validation.""" def test_init_requires_project_and_log_stream(self, monkeypatch: pytest.MonkeyPatch) -> None: """Callback raises error when neither project/log_stream nor hook provided.""" - # Given: GALILEO_PROJECT and GALILEO_LOG_STREAM env vars are not set - monkeypatch.delenv("GALILEO_PROJECT", raising=False) - monkeypatch.delenv("GALILEO_LOG_STREAM", raising=False) + # Given: SPLUNK_AO_PROJECT and SPLUNK_AO_LOG_STREAM env vars are not set + monkeypatch.delenv("SPLUNK_AO_PROJECT", raising=False) + monkeypatch.delenv("SPLUNK_AO_LOG_STREAM", raising=False) # When/Then: creating callback without project or log_stream raises an error with pytest.raises(ValueError, match="Both 'project' and 'log_stream' must be provided"): - GalileoADKCallback() + SplunkAOADKCallback() def test_init_requires_log_stream(self, monkeypatch: pytest.MonkeyPatch) -> None: """Callback raises error when project is provided but log_stream is not.""" - # Given: GALILEO_LOG_STREAM env var is not set - monkeypatch.delenv("GALILEO_LOG_STREAM", raising=False) + # Given: SPLUNK_AO_LOG_STREAM env var is not set + monkeypatch.delenv("SPLUNK_AO_LOG_STREAM", raising=False) # When/Then: creating callback with project but no log_stream raises an error with pytest.raises(ValueError, match="Both 'project' and 'log_stream' must be provided"): - GalileoADKCallback(project="my-project") + SplunkAOADKCallback(project="my-project") def test_init_requires_project(self, monkeypatch: pytest.MonkeyPatch) -> None: """Callback raises error when log_stream is provided but project is not.""" - # Given: GALILEO_PROJECT env var is not set - monkeypatch.delenv("GALILEO_PROJECT", raising=False) + # Given: SPLUNK_AO_PROJECT env var is not set + monkeypatch.delenv("SPLUNK_AO_PROJECT", raising=False) # When/Then: creating callback with log_stream but no project raises an error with pytest.raises(ValueError, match="Both 'project' and 'log_stream' must be provided"): - GalileoADKCallback(log_stream="my-stream") + SplunkAOADKCallback(log_stream="my-stream") -class TestGalileoADKCallback: +class TestSplunkAOADKCallback: @pytest.fixture - def callback(self) -> GalileoADKCallback: - return GalileoADKCallback(ingestion_hook=lambda r: None) + def callback(self) -> SplunkAOADKCallback: + return SplunkAOADKCallback(ingestion_hook=lambda r: None) def test_initialization_with_ingestion_hook(self) -> None: - callback = GalileoADKCallback(ingestion_hook=lambda r: None) + callback = SplunkAOADKCallback(ingestion_hook=lambda r: None) assert callback._handler is not None def test_initialization_with_project_and_log_stream(self) -> None: @@ -127,7 +127,7 @@ def test_initialization_with_project_and_log_stream(self) -> None: mock_logger = MagicMock() mock_context.get_logger_instance.return_value = mock_logger - callback = GalileoADKCallback(project="test-project", log_stream="test-stream") + callback = SplunkAOADKCallback(project="test-project", log_stream="test-stream") mock_context.get_logger_instance.assert_called_once_with( project="test-project", @@ -136,20 +136,20 @@ def test_initialization_with_project_and_log_stream(self) -> None: assert callback._handler._galileo_logger == mock_logger def test_initialization_defaults(self) -> None: - callback = GalileoADKCallback(ingestion_hook=lambda r: None) + callback = SplunkAOADKCallback(ingestion_hook=lambda r: None) assert callback._handler._start_new_trace is True assert callback._handler._flush_on_chain_end is True assert callback._handler._integration == "google_adk" - def test_before_agent_callback(self, callback: GalileoADKCallback, adk_callback_context: MagicMock) -> None: + def test_before_agent_callback(self, callback: SplunkAOADKCallback, adk_callback_context: MagicMock) -> None: result = callback.before_agent_callback(adk_callback_context) assert result is None assert callback._tracker.agent_count == 1 assert len(callback._handler._nodes) == 1 - def test_after_agent_callback(self, callback: GalileoADKCallback, adk_callback_context: MagicMock) -> None: + def test_after_agent_callback(self, callback: SplunkAOADKCallback, adk_callback_context: MagicMock) -> None: callback.before_agent_callback(adk_callback_context) result = callback.after_agent_callback(adk_callback_context) @@ -159,7 +159,7 @@ def test_after_agent_callback(self, callback: GalileoADKCallback, adk_callback_c def test_before_model_callback( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, adk_callback_context: MagicMock, adk_llm_request: MagicMock, ) -> None: @@ -173,7 +173,7 @@ def test_before_model_callback( def test_after_model_callback( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, adk_callback_context: MagicMock, adk_llm_request: MagicMock, adk_llm_response: MagicMock, @@ -188,7 +188,7 @@ def test_after_model_callback( def test_before_tool_callback( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, adk_callback_context: MagicMock, adk_tool: MagicMock, adk_tool_context: MagicMock, @@ -205,7 +205,7 @@ def test_before_tool_callback( def test_after_tool_callback( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, adk_callback_context: MagicMock, adk_tool: MagicMock, adk_tool_context: MagicMock, @@ -224,7 +224,7 @@ def test_after_tool_callback( def test_full_trace_lifecycle( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, adk_callback_context: MagicMock, adk_llm_request: MagicMock, adk_llm_response: MagicMock, @@ -252,19 +252,19 @@ def test_full_trace_lifecycle( callback.after_agent_callback(adk_callback_context) assert len(callback._handler._nodes) == 0 - def test_error_handling_before_agent(self, callback: GalileoADKCallback, adk_callback_context: MagicMock) -> None: + def test_error_handling_before_agent(self, callback: SplunkAOADKCallback, adk_callback_context: MagicMock) -> None: adk_callback_context.agent_name = property(lambda x: 1 / 0) # noqa: B017 result = callback.before_agent_callback(adk_callback_context) assert result is None def test_error_handling_after_agent_without_run_id( - self, callback: GalileoADKCallback, adk_callback_context: MagicMock + self, callback: SplunkAOADKCallback, adk_callback_context: MagicMock ) -> None: result = callback.after_agent_callback(adk_callback_context) assert result is None - def test_extract_agent_input(self, callback: GalileoADKCallback, adk_callback_context: MagicMock) -> None: + def test_extract_agent_input(self, callback: SplunkAOADKCallback, adk_callback_context: MagicMock) -> None: parent_context = MagicMock() parent_context.new_message = MagicMock() parent_context.new_message.parts = [MagicMock(text="Hello")] @@ -273,33 +273,33 @@ def test_extract_agent_input(self, callback: GalileoADKCallback, adk_callback_co result = callback._observer._extract_agent_input(adk_callback_context) assert result == "Hello" - def test_extract_agent_input_fallback(self, callback: GalileoADKCallback, adk_callback_context: MagicMock) -> None: + def test_extract_agent_input_fallback(self, callback: SplunkAOADKCallback, adk_callback_context: MagicMock) -> None: del adk_callback_context.parent_context result = callback._observer._extract_agent_input(adk_callback_context) assert result == "Agent invocation" - def test_extract_llm_input(self, callback: GalileoADKCallback, adk_llm_request: MagicMock) -> None: + def test_extract_llm_input(self, callback: SplunkAOADKCallback, adk_llm_request: MagicMock) -> None: result = callback._observer._extract_llm_input(adk_llm_request) assert isinstance(result, list) - def test_extract_model_name(self, callback: GalileoADKCallback, adk_llm_request: MagicMock) -> None: + def test_extract_model_name(self, callback: SplunkAOADKCallback, adk_llm_request: MagicMock) -> None: result = callback._observer._extract_model_name(adk_llm_request) assert result == "gemini-pro" - def test_extract_temperature(self, callback: GalileoADKCallback, adk_llm_request: MagicMock) -> None: + def test_extract_temperature(self, callback: SplunkAOADKCallback, adk_llm_request: MagicMock) -> None: adk_llm_request.config.temperature = 0.7 result = callback._observer._extract_temperature(adk_llm_request) assert result == 0.7 - def test_extract_temperature_none(self, callback: GalileoADKCallback, adk_llm_request: MagicMock) -> None: + def test_extract_temperature_none(self, callback: SplunkAOADKCallback, adk_llm_request: MagicMock) -> None: adk_llm_request.config.temperature = None result = callback._observer._extract_temperature(adk_llm_request) assert result is None - def test_extract_usage_metadata(self, callback: GalileoADKCallback, adk_llm_response: MagicMock) -> None: + def test_extract_usage_metadata(self, callback: SplunkAOADKCallback, adk_llm_response: MagicMock) -> None: adk_llm_response.usage_metadata.prompt_token_count = 10 adk_llm_response.usage_metadata.candidates_token_count = 20 adk_llm_response.usage_metadata.total_token_count = 30 @@ -312,15 +312,15 @@ def test_extract_usage_metadata(self, callback: GalileoADKCallback, adk_llm_resp class TestCallbackErrorHandling: - """Consolidated error handling tests for GalileoADKCallback.""" + """Consolidated error handling tests for SplunkAOADKCallback.""" @pytest.fixture - def callback(self) -> GalileoADKCallback: - return GalileoADKCallback(ingestion_hook=lambda r: None) + def callback(self) -> SplunkAOADKCallback: + return SplunkAOADKCallback(ingestion_hook=lambda r: None) def test_callback_handles_all_exception_types_gracefully( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, ) -> None: """Callbacks handle exceptions without propagating errors.""" # Given: a context that raises when accessing properties @@ -353,7 +353,7 @@ def test_callback_handles_all_exception_types_gracefully( def test_all_callbacks_return_none_consistently( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, ) -> None: """All callback methods return None.""" context = MockCallbackContext() @@ -383,12 +383,12 @@ class TestCallbackPluginCompatibility: """Tests for multi-plugin compatibility.""" @pytest.fixture - def callback(self) -> GalileoADKCallback: - return GalileoADKCallback(ingestion_hook=lambda r: None) + def callback(self) -> SplunkAOADKCallback: + return SplunkAOADKCallback(ingestion_hook=lambda r: None) def test_callback_handles_data_modification_gracefully( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, ) -> None: """Callback handles external data modification between before/after calls.""" context = MockCallbackContext() @@ -405,7 +405,7 @@ def test_callback_handles_data_modification_gracefully( assert result is None assert callback._tracker.llm_count == 0 - def test_namespaced_attributes(self, callback: GalileoADKCallback) -> None: + def test_namespaced_attributes(self, callback: SplunkAOADKCallback) -> None: """Callback tracks spans internally without polluting context.""" context = MockCallbackContext() request = MockLlmRequest() @@ -421,7 +421,7 @@ def test_namespaced_attributes(self, callback: GalileoADKCallback) -> None: context._other_plugin_data = "test" # type: ignore[attr-defined] assert context._other_plugin_data == "test" # type: ignore[attr-defined] - def test_read_only_operations(self, callback: GalileoADKCallback) -> None: + def test_read_only_operations(self, callback: SplunkAOADKCallback) -> None: """Callback doesn't modify the original request data.""" context = MockCallbackContext() original_text = "original message" @@ -434,7 +434,7 @@ def test_read_only_operations(self, callback: GalileoADKCallback) -> None: def test_compatibility_with_other_plugin_attributes( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, ) -> None: """Callback is compatible with other plugins setting attributes on context.""" context = MockCallbackContext() @@ -465,7 +465,7 @@ def test_compatibility_with_other_plugin_attributes( def test_complex_agent_interaction_sequence( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, ) -> None: """Multiple LLM and tool calls within a single agent lifecycle.""" context = MockCallbackContext() @@ -507,7 +507,7 @@ def test_complex_agent_interaction_sequence( def test_llm_correlation_without_request_id( self, - callback: GalileoADKCallback, + callback: SplunkAOADKCallback, ) -> None: """LLM spans correlate without request_id.""" context = MockCallbackContext() @@ -536,10 +536,10 @@ class TestAutomaticSessionMapping: """Tests for automatic ADK session_id → Galileo session mapping.""" @pytest.fixture - def callback(self) -> GalileoADKCallback: - return GalileoADKCallback(ingestion_hook=lambda r: None) + def callback(self) -> SplunkAOADKCallback: + return SplunkAOADKCallback(ingestion_hook=lambda r: None) - def test_session_mapped_on_before_agent(self, callback: GalileoADKCallback) -> None: + def test_session_mapped_on_before_agent(self, callback: SplunkAOADKCallback) -> None: """ADK session_id is mapped to Galileo session on before_agent_callback.""" # Given: a callback context with a session_id context = MockCallbackContext(session_id="adk-callback-session-123") @@ -550,7 +550,7 @@ def test_session_mapped_on_before_agent(self, callback: GalileoADKCallback) -> N # Then: the ADK session is tracked by the observer assert callback._observer._current_adk_session == "adk-callback-session-123" - def test_session_not_remapped_for_same_session_id(self, callback: GalileoADKCallback) -> None: + def test_session_not_remapped_for_same_session_id(self, callback: SplunkAOADKCallback) -> None: """Same session_id doesn't trigger repeated session mapping.""" # Given: a persistent session session_id = "persistent-callback-session" @@ -569,7 +569,7 @@ def test_session_not_remapped_for_same_session_id(self, callback: GalileoADKCall # Then: session remains the same (not remapped) assert first_mapped_session == second_mapped_session == session_id - def test_unknown_session_id_not_mapped(self, callback: GalileoADKCallback) -> None: + def test_unknown_session_id_not_mapped(self, callback: SplunkAOADKCallback) -> None: """Session_id of 'unknown' is not mapped to Galileo session.""" # Given: a callback context with unknown session_id context = MockCallbackContext(session_id="unknown") @@ -580,7 +580,7 @@ def test_unknown_session_id_not_mapped(self, callback: GalileoADKCallback) -> No # Then: session is not tracked (remains None) assert callback._observer._current_adk_session is None - def test_session_updated_when_different_session_id(self, callback: GalileoADKCallback) -> None: + def test_session_updated_when_different_session_id(self, callback: SplunkAOADKCallback) -> None: """Different session_id triggers session update.""" # Given: first agent with session-1 context_1 = MockCallbackContext(agent_name="agent_1", session_id="session-1") @@ -600,10 +600,10 @@ class TestRunConfigMetadata: """Tests for per-invocation metadata from RunConfig.custom_metadata.""" @pytest.fixture - def callback(self) -> GalileoADKCallback: - return GalileoADKCallback(ingestion_hook=lambda r: None) + def callback(self) -> SplunkAOADKCallback: + return SplunkAOADKCallback(ingestion_hook=lambda r: None) - def test_metadata_extracted_from_run_config(self, callback: GalileoADKCallback) -> None: + def test_metadata_extracted_from_run_config(self, callback: SplunkAOADKCallback) -> None: """Metadata is extracted from RunConfig.custom_metadata in before_agent_callback.""" # Given: a context with RunConfig containing custom_metadata run_config = MockRunConfig(custom_metadata={"turn": 1, "user_id": "test-user"}) @@ -615,7 +615,7 @@ def test_metadata_extracted_from_run_config(self, callback: GalileoADKCallback) # Then: metadata is stored for the invocation (in observer) assert len(callback._observer._invocation_metadata) > 0 - def test_missing_run_config_results_in_empty_metadata(self, callback: GalileoADKCallback) -> None: + def test_missing_run_config_results_in_empty_metadata(self, callback: SplunkAOADKCallback) -> None: """Missing RunConfig results in empty metadata (no error).""" # Given: a context without RunConfig context = MockCallbackContext(run_config=None) @@ -626,7 +626,7 @@ def test_missing_run_config_results_in_empty_metadata(self, callback: GalileoADK # Then: no crash and agent is tracked assert callback._tracker.agent_count == 1 - def test_metadata_cleaned_up_after_agent_ends(self, callback: GalileoADKCallback) -> None: + def test_metadata_cleaned_up_after_agent_ends(self, callback: SplunkAOADKCallback) -> None: """Per-invocation metadata is cleaned up when agent ends.""" # Given: a context with RunConfig containing custom_metadata run_config = MockRunConfig(custom_metadata={"turn": 1}) @@ -645,7 +645,7 @@ def test_metadata_cleaned_up_after_agent_ends(self, callback: GalileoADKCallback # Then: metadata is cleaned up assert invocation_id not in callback._observer._invocation_metadata - def test_concurrent_agents_have_isolated_metadata(self, callback: GalileoADKCallback) -> None: + def test_concurrent_agents_have_isolated_metadata(self, callback: SplunkAOADKCallback) -> None: """Concurrent agents with different RunConfig have isolated metadata.""" # Given: two contexts with different custom_metadata run_config_1 = MockRunConfig(custom_metadata={"turn": 1, "user": "alice"}) diff --git a/galileo-adk/tests/test_observer.py b/galileo-adk/tests/test_observer.py index b97f361a..1cd29ff6 100644 --- a/galileo-adk/tests/test_observer.py +++ b/galileo-adk/tests/test_observer.py @@ -1,24 +1,24 @@ -"""Unit tests for GalileoObserver extraction methods.""" +"""Unit tests for SplunkAOObserver extraction methods.""" from unittest.mock import MagicMock import pytest -from galileo_adk.observer import GalileoObserver +from galileo_adk.observer import SplunkAOObserver from .mocks import MockContent, MockEvent, MockPart @pytest.fixture -def observer() -> GalileoObserver: +def observer() -> SplunkAOObserver: """Create observer with ingestion hook for testing (no credentials needed).""" - return GalileoObserver(ingestion_hook=lambda r: None) + return SplunkAOObserver(ingestion_hook=lambda r: None) class TestExtractInvocationMetadata: """Tests for _extract_invocation_metadata method.""" - def test_extracts_invocation_id(self, observer: GalileoObserver) -> None: + def test_extracts_invocation_id(self, observer: SplunkAOObserver) -> None: # Given: an invocation context with an invocation_id context = MagicMock() context.invocation_id = "inv_123" @@ -30,7 +30,7 @@ def test_extracts_invocation_id(self, observer: GalileoObserver) -> None: # Then: invocation_id is included assert result["invocation_id"] == "inv_123" - def test_extracts_session_id_from_nested_session(self, observer: GalileoObserver) -> None: + def test_extracts_session_id_from_nested_session(self, observer: SplunkAOObserver) -> None: # Given: an invocation context with a nested session context = MagicMock() context.invocation_id = "inv_123" @@ -43,7 +43,7 @@ def test_extracts_session_id_from_nested_session(self, observer: GalileoObserver assert result["invocation_id"] == "inv_123" assert result["session_id"] == "sess_456" - def test_missing_attributes_returns_empty_metadata(self, observer: GalileoObserver) -> None: + def test_missing_attributes_returns_empty_metadata(self, observer: SplunkAOObserver) -> None: # Given: a context without invocation_id or session context = MagicMock(spec=[]) # Empty spec means no attributes @@ -58,7 +58,7 @@ def test_missing_attributes_returns_empty_metadata(self, observer: GalileoObserv class TestExtractAgentInput: """Tests for _extract_agent_input method.""" - def test_extracts_from_parent_context_new_message(self, observer: GalileoObserver) -> None: + def test_extracts_from_parent_context_new_message(self, observer: SplunkAOObserver) -> None: # Given: a callback context with parent_context.new_message context = MagicMock() context.parent_context.new_message.parts = [MockPart(text="Hello, agent!")] @@ -69,7 +69,7 @@ def test_extracts_from_parent_context_new_message(self, observer: GalileoObserve # Then: the text is extracted assert result == "Hello, agent!" - def test_fallback_when_no_parent_context(self, observer: GalileoObserver) -> None: + def test_fallback_when_no_parent_context(self, observer: SplunkAOObserver) -> None: # Given: a context without parent_context context = MagicMock(spec=[]) @@ -79,7 +79,7 @@ def test_fallback_when_no_parent_context(self, observer: GalileoObserver) -> Non # Then: fallback message is returned assert result == "Agent invocation" - def test_extracts_multiple_parts(self, observer: GalileoObserver) -> None: + def test_extracts_multiple_parts(self, observer: SplunkAOObserver) -> None: # Given: a callback context with multiple text parts context = MagicMock() context.parent_context.new_message.parts = [ @@ -98,7 +98,7 @@ def test_extracts_multiple_parts(self, observer: GalileoObserver) -> None: class TestExtractAgentOutput: """Tests for _extract_agent_output method.""" - def test_extracts_from_last_event(self, observer: GalileoObserver) -> None: + def test_extracts_from_last_event(self, observer: SplunkAOObserver) -> None: # Given: a context with parent_context.events containing output context = MagicMock() event1 = MockEvent(content=MockContent(parts=[MockPart(text="Processing...")])) @@ -111,7 +111,7 @@ def test_extracts_from_last_event(self, observer: GalileoObserver) -> None: # Then: the last event's content is extracted assert result == "Final answer" - def test_returns_empty_when_no_events(self, observer: GalileoObserver) -> None: + def test_returns_empty_when_no_events(self, observer: SplunkAOObserver) -> None: # Given: a context without events context = MagicMock() context.parent_context.events = [] @@ -126,7 +126,7 @@ def test_returns_empty_when_no_events(self, observer: GalileoObserver) -> None: class TestExtractTools: """Tests for _extract_tools method.""" - def test_converts_tools_when_present(self, observer: GalileoObserver) -> None: + def test_converts_tools_when_present(self, observer: SplunkAOObserver) -> None: # Given: an LLM request with tools in config request = MagicMock() tool = MagicMock() @@ -144,7 +144,7 @@ def test_converts_tools_when_present(self, observer: GalileoObserver) -> None: assert result[0]["type"] == "function" assert result[0]["function"]["name"] == "search" - def test_returns_none_when_no_tools(self, observer: GalileoObserver) -> None: + def test_returns_none_when_no_tools(self, observer: SplunkAOObserver) -> None: # Given: an LLM request without tools request = MagicMock() request.config.tools = None @@ -159,7 +159,7 @@ def test_returns_none_when_no_tools(self, observer: GalileoObserver) -> None: class TestExtractFinalOutput: """Tests for _extract_final_output method.""" - def test_extracts_from_session_events(self, observer: GalileoObserver) -> None: + def test_extracts_from_session_events(self, observer: SplunkAOObserver) -> None: # Given: an invocation context with session.events containing a final response context = MagicMock() event = MockEvent(content=MockContent(parts=[MockPart(text="The answer is 42")]), is_final=True) @@ -171,7 +171,7 @@ def test_extracts_from_session_events(self, observer: GalileoObserver) -> None: # Then: the final response content is extracted assert result == "The answer is 42" - def test_finds_final_response_not_at_end(self, observer: GalileoObserver) -> None: + def test_finds_final_response_not_at_end(self, observer: SplunkAOObserver) -> None: # Given: session.events where final response is not the last event context = MagicMock() tool_call = MockEvent(content=MockContent(parts=[MockPart(text="calling tool")]), is_final=False) @@ -185,7 +185,7 @@ def test_finds_final_response_not_at_end(self, observer: GalileoObserver) -> Non # Then: the final response content is extracted (not the last event) assert result == "The answer is 42" - def test_returns_empty_when_no_final_response(self, observer: GalileoObserver) -> None: + def test_returns_empty_when_no_final_response(self, observer: SplunkAOObserver) -> None: # Given: session.events with no final response context = MagicMock() tool_call = MockEvent(content=MockContent(parts=[MockPart(text="calling tool")]), is_final=False) @@ -198,7 +198,7 @@ def test_returns_empty_when_no_final_response(self, observer: GalileoObserver) - # Then: empty string is returned (no misleading data) assert result == "" - def test_handles_missing_session_gracefully(self, observer: GalileoObserver) -> None: + def test_handles_missing_session_gracefully(self, observer: SplunkAOObserver) -> None: # Given: an invocation context without session context = MagicMock(spec=[]) @@ -215,7 +215,7 @@ class TestUpdateSessionIfChanged: def test_hook_mode_sets_session_external_id_without_backend_call(self) -> None: # Given: an observer in hook mode captured_requests: list = [] - observer = GalileoObserver(ingestion_hook=lambda r: captured_requests.append(r)) + observer = SplunkAOObserver(ingestion_hook=lambda r: captured_requests.append(r)) logger = observer._handler._galileo_logger # When: updating session with an ADK session ID @@ -227,7 +227,7 @@ def test_hook_mode_sets_session_external_id_without_backend_call(self) -> None: def test_hook_mode_updates_external_id_on_session_change(self) -> None: # Given: an observer in hook mode with an existing session - observer = GalileoObserver(ingestion_hook=lambda r: None) + observer = SplunkAOObserver(ingestion_hook=lambda r: None) observer.update_session_if_changed("session-1") logger = observer._handler._galileo_logger @@ -240,7 +240,7 @@ def test_hook_mode_updates_external_id_on_session_change(self) -> None: def test_hook_mode_ignores_unknown_session_id(self) -> None: # Given: an observer in hook mode - observer = GalileoObserver(ingestion_hook=lambda r: None) + observer = SplunkAOObserver(ingestion_hook=lambda r: None) logger = observer._handler._galileo_logger # When: updating with "unknown" session ID @@ -252,7 +252,7 @@ def test_hook_mode_ignores_unknown_session_id(self) -> None: def test_hook_mode_ignores_duplicate_session_id(self) -> None: # Given: an observer in hook mode with an existing session - observer = GalileoObserver(ingestion_hook=lambda r: None) + observer = SplunkAOObserver(ingestion_hook=lambda r: None) observer.update_session_if_changed("session-1") logger = observer._handler._galileo_logger original_external_id = logger._session_external_id @@ -265,7 +265,7 @@ def test_hook_mode_ignores_duplicate_session_id(self) -> None: def test_hook_mode_preserves_parent_session_for_sub_invocations(self) -> None: # Given: an observer in hook mode with an existing parent session - observer = GalileoObserver(ingestion_hook=lambda r: None) + observer = SplunkAOObserver(ingestion_hook=lambda r: None) observer.update_session_if_changed("parent-session") logger = observer._handler._galileo_logger diff --git a/galileo-adk/tests/test_plugin.py b/galileo-adk/tests/test_plugin.py index 371ae5a5..a33272ca 100644 --- a/galileo-adk/tests/test_plugin.py +++ b/galileo-adk/tests/test_plugin.py @@ -1,11 +1,11 @@ -"""Integration tests for GalileoADKPlugin.""" +"""Integration tests for SplunkAOADKPlugin.""" from unittest.mock import MagicMock from uuid import uuid4 import pytest -from galileo_adk import GalileoADKPlugin +from galileo_adk import SplunkAOADKPlugin from .mocks import ( MockCallbackContext, @@ -19,26 +19,26 @@ ) -class TestGalileoADKPluginInit: +class TestSplunkAOADKPluginInit: """Tests for plugin initialization.""" def test_init_with_ingestion_hook_only(self) -> None: """Plugin initializes with only ingestion_hook.""" traces: list = [] - plugin = GalileoADKPlugin(ingestion_hook=lambda r: traces.extend(r.traces)) + plugin = SplunkAOADKPlugin(ingestion_hook=lambda r: traces.extend(r.traces)) assert plugin._observer is not None def test_init_with_ingestion_hook_without_env_vars(self, monkeypatch: pytest.MonkeyPatch) -> None: """Plugin with ingestion_hook works without any Galileo environment variables.""" # Given: no Galileo environment variables are set - monkeypatch.delenv("GALILEO_PROJECT", raising=False) - monkeypatch.delenv("GALILEO_LOG_STREAM", raising=False) - monkeypatch.delenv("GALILEO_API_KEY", raising=False) - monkeypatch.delenv("GALILEO_CONSOLE_URL", raising=False) + monkeypatch.delenv("SPLUNK_AO_PROJECT", raising=False) + monkeypatch.delenv("SPLUNK_AO_LOG_STREAM", raising=False) + monkeypatch.delenv("SPLUNK_AO_API_KEY", raising=False) + monkeypatch.delenv("SPLUNK_AO_CONSOLE_URL", raising=False) # When: creating plugin with only ingestion_hook traces: list = [] - plugin = GalileoADKPlugin(ingestion_hook=lambda r: traces.extend(r.traces)) + plugin = SplunkAOADKPlugin(ingestion_hook=lambda r: traces.extend(r.traces)) # Then: plugin initializes successfully with TraceBuilder (not SplunkAOLogger) assert plugin._observer is not None @@ -47,34 +47,34 @@ def test_init_with_ingestion_hook_without_env_vars(self, monkeypatch: pytest.Mon def test_init_requires_project_and_log_stream(self, monkeypatch: pytest.MonkeyPatch) -> None: """Plugin raises error when neither project/log_stream nor hook provided.""" - # Given: GALILEO_PROJECT and GALILEO_LOG_STREAM env vars are not set - monkeypatch.delenv("GALILEO_PROJECT", raising=False) - monkeypatch.delenv("GALILEO_LOG_STREAM", raising=False) + # Given: SPLUNK_AO_PROJECT and SPLUNK_AO_LOG_STREAM env vars are not set + monkeypatch.delenv("SPLUNK_AO_PROJECT", raising=False) + monkeypatch.delenv("SPLUNK_AO_LOG_STREAM", raising=False) # When/Then: creating plugin without project or log_stream raises an error with pytest.raises(ValueError, match="Both 'project' and 'log_stream' must be provided"): - GalileoADKPlugin() + SplunkAOADKPlugin() def test_init_requires_log_stream(self, monkeypatch: pytest.MonkeyPatch) -> None: """Plugin raises error when project is provided but log_stream is not.""" - # Given: GALILEO_LOG_STREAM env var is not set - monkeypatch.delenv("GALILEO_LOG_STREAM", raising=False) + # Given: SPLUNK_AO_LOG_STREAM env var is not set + monkeypatch.delenv("SPLUNK_AO_LOG_STREAM", raising=False) # When/Then: creating plugin with project but no log_stream raises an error with pytest.raises(ValueError, match="Both 'project' and 'log_stream' must be provided"): - GalileoADKPlugin(project="my-project") + SplunkAOADKPlugin(project="my-project") def test_init_requires_project(self, monkeypatch: pytest.MonkeyPatch) -> None: """Plugin raises error when log_stream is provided but project is not.""" - # Given: GALILEO_PROJECT env var is not set - monkeypatch.delenv("GALILEO_PROJECT", raising=False) + # Given: SPLUNK_AO_PROJECT env var is not set + monkeypatch.delenv("SPLUNK_AO_PROJECT", raising=False) # When/Then: creating plugin with log_stream but no project raises an error with pytest.raises(ValueError, match="Both 'project' and 'log_stream' must be provided"): - GalileoADKPlugin(log_stream="my-stream") + SplunkAOADKPlugin(log_stream="my-stream") -class TestGalileoADKPluginCallbacks: +class TestSplunkAOADKPluginCallbacks: """Integration tests for plugin callbacks.""" @pytest.fixture @@ -82,11 +82,11 @@ def captured_traces(self) -> list: return [] @pytest.fixture - def plugin(self, captured_traces: list) -> GalileoADKPlugin: - return GalileoADKPlugin(ingestion_hook=lambda r: captured_traces.extend(r.traces)) + def plugin(self, captured_traces: list) -> SplunkAOADKPlugin: + return SplunkAOADKPlugin(ingestion_hook=lambda r: captured_traces.extend(r.traces)) @pytest.mark.asyncio - async def test_on_user_message_creates_run_span(self, plugin: GalileoADKPlugin) -> None: + async def test_on_user_message_creates_run_span(self, plugin: SplunkAOADKPlugin) -> None: """on_user_message_callback creates a run span.""" context = MockInvocationContext() message = MockContent("Hello") @@ -100,7 +100,7 @@ async def test_on_user_message_creates_run_span(self, plugin: GalileoADKPlugin) assert plugin._tracker.get_run(context.invocation_id) is not None @pytest.mark.asyncio - async def test_before_agent_creates_agent_span(self, plugin: GalileoADKPlugin) -> None: + async def test_before_agent_creates_agent_span(self, plugin: SplunkAOADKPlugin) -> None: """before_agent_callback creates an agent span.""" invocation_id = str(uuid4()) context = MockCallbackContext(invocation_id=invocation_id) @@ -118,7 +118,7 @@ async def test_before_agent_creates_agent_span(self, plugin: GalileoADKPlugin) - assert plugin._tracker.agent_count == 1 @pytest.mark.asyncio - async def test_before_model_creates_llm_span(self, plugin: GalileoADKPlugin) -> None: + async def test_before_model_creates_llm_span(self, plugin: SplunkAOADKPlugin) -> None: """before_model_callback creates an LLM span.""" invocation_id = str(uuid4()) context = MockCallbackContext(invocation_id=invocation_id) @@ -141,7 +141,7 @@ async def test_before_model_creates_llm_span(self, plugin: GalileoADKPlugin) -> assert plugin._tracker.llm_count == 1 @pytest.mark.asyncio - async def test_full_lifecycle_with_trace_capture(self, plugin: GalileoADKPlugin, captured_traces: list) -> None: + async def test_full_lifecycle_with_trace_capture(self, plugin: SplunkAOADKPlugin, captured_traces: list) -> None: """Full agent lifecycle produces captured traces.""" invocation_id = str(uuid4()) callback_context = MockCallbackContext(invocation_id=invocation_id) @@ -177,7 +177,7 @@ async def test_full_lifecycle_with_trace_capture(self, plugin: GalileoADKPlugin, @pytest.mark.asyncio async def test_metadata_from_run_config_appears_on_spans( - self, plugin: GalileoADKPlugin, captured_traces: list + self, plugin: SplunkAOADKPlugin, captured_traces: list ) -> None: """Metadata from RunConfig.custom_metadata appears on captured spans.""" # Given: RunConfig with custom_metadata @@ -206,7 +206,7 @@ async def test_metadata_from_run_config_appears_on_spans( @pytest.mark.asyncio async def test_missing_run_config_results_in_empty_metadata( - self, plugin: GalileoADKPlugin, captured_traces: list + self, plugin: SplunkAOADKPlugin, captured_traces: list ) -> None: """Missing RunConfig results in empty metadata (no error).""" # Given: no RunConfig (run_config=None) @@ -231,11 +231,11 @@ class TestRunConfigMetadataIsolation: """Tests for per-invocation metadata isolation using RunConfig.custom_metadata.""" @pytest.fixture - def plugin(self) -> GalileoADKPlugin: - return GalileoADKPlugin(ingestion_hook=lambda r: None) + def plugin(self) -> SplunkAOADKPlugin: + return SplunkAOADKPlugin(ingestion_hook=lambda r: None) @pytest.mark.asyncio - async def test_concurrent_invocations_have_isolated_metadata(self, plugin: GalileoADKPlugin) -> None: + async def test_concurrent_invocations_have_isolated_metadata(self, plugin: SplunkAOADKPlugin) -> None: """Concurrent invocations with different RunConfig have isolated metadata.""" # Given: two invocations with different custom_metadata inv_id_1 = str(uuid4()) @@ -283,7 +283,7 @@ async def test_concurrent_invocations_have_isolated_metadata(self, plugin: Galil assert inv_id_2 not in plugin._observer._invocation_metadata @pytest.mark.asyncio - async def test_metadata_cleaned_up_after_run_ends(self, plugin: GalileoADKPlugin) -> None: + async def test_metadata_cleaned_up_after_run_ends(self, plugin: SplunkAOADKPlugin) -> None: """Per-invocation metadata is cleaned up when run ends.""" # Given: an invocation with custom_metadata invocation_id = str(uuid4()) @@ -310,7 +310,7 @@ async def test_metadata_cleaned_up_after_run_ends(self, plugin: GalileoADKPlugin assert invocation_id not in plugin._observer._invocation_metadata @pytest.mark.asyncio - async def test_sub_invocation_inherits_root_metadata(self, plugin: GalileoADKPlugin) -> None: + async def test_sub_invocation_inherits_root_metadata(self, plugin: SplunkAOADKPlugin) -> None: """Sub-invocations (e.g., AgentTool calls) inherit metadata from root invocation.""" # Given: root invocation with custom_metadata root_invocation_id = str(uuid4()) @@ -350,11 +350,11 @@ class TestBeforeRunCallback: """Tests for before_run_callback updating run span name and metadata.""" @pytest.fixture - def plugin(self) -> GalileoADKPlugin: - return GalileoADKPlugin(ingestion_hook=lambda r: None) + def plugin(self) -> SplunkAOADKPlugin: + return SplunkAOADKPlugin(ingestion_hook=lambda r: None) @pytest.mark.asyncio - async def test_before_run_updates_span_name_and_metadata(self, plugin: GalileoADKPlugin) -> None: + async def test_before_run_updates_span_name_and_metadata(self, plugin: SplunkAOADKPlugin) -> None: """before_run_callback updates run span name to reflect the routed agent.""" # Given: an invocation with a run span invocation_id = str(uuid4()) @@ -379,7 +379,7 @@ async def test_before_run_updates_span_name_and_metadata(self, plugin: GalileoAD assert node.span_params["metadata"]["adk_routed_agent"] == "sub_agent" @pytest.mark.asyncio - async def test_before_run_preserves_existing_metadata(self, plugin: GalileoADKPlugin) -> None: + async def test_before_run_preserves_existing_metadata(self, plugin: SplunkAOADKPlugin) -> None: """before_run_callback preserves any existing metadata on the run span.""" # Given: an invocation with run_config metadata invocation_id = str(uuid4()) @@ -403,7 +403,7 @@ async def test_before_run_preserves_existing_metadata(self, plugin: GalileoADKPl assert metadata["adk_routed_agent"] == "my_agent" @pytest.mark.asyncio - async def test_before_run_handles_missing_agent_gracefully(self, plugin: GalileoADKPlugin) -> None: + async def test_before_run_handles_missing_agent_gracefully(self, plugin: SplunkAOADKPlugin) -> None: """before_run_callback is a no-op when agent is not available.""" # Given: an invocation without an agent attribute invocation_id = str(uuid4()) @@ -421,15 +421,15 @@ async def test_before_run_handles_missing_agent_gracefully(self, plugin: Galileo assert plugin._tracker.get_run(invocation_id) is not None -class TestGalileoADKPluginErrorHandling: +class TestSplunkAOADKPluginErrorHandling: """Tests for error handling in callbacks.""" @pytest.fixture - def plugin(self) -> GalileoADKPlugin: - return GalileoADKPlugin(ingestion_hook=lambda r: None) + def plugin(self) -> SplunkAOADKPlugin: + return SplunkAOADKPlugin(ingestion_hook=lambda r: None) @pytest.mark.asyncio - async def test_callback_errors_dont_propagate(self, plugin: GalileoADKPlugin) -> None: + async def test_callback_errors_dont_propagate(self, plugin: SplunkAOADKPlugin) -> None: """Errors in callbacks don't propagate to caller.""" # Given: an invalid context (None) # When/Then: calling before_agent_callback should not raise @@ -437,7 +437,7 @@ async def test_callback_errors_dont_propagate(self, plugin: GalileoADKPlugin) -> assert result is None @pytest.mark.asyncio - async def test_model_error_callback_extracts_status_code(self, plugin: GalileoADKPlugin) -> None: + async def test_model_error_callback_extracts_status_code(self, plugin: SplunkAOADKPlugin) -> None: """on_model_error_callback handles errors gracefully.""" # Given: an invocation with an active LLM span invocation_id = str(uuid4()) @@ -474,11 +474,11 @@ class TestInvocationScopedToolTracking: """Tests for invocation-scoped tool tracking (ParallelAgent support).""" @pytest.fixture - def plugin(self) -> GalileoADKPlugin: - return GalileoADKPlugin(ingestion_hook=lambda r: None) + def plugin(self) -> SplunkAOADKPlugin: + return SplunkAOADKPlugin(ingestion_hook=lambda r: None) @pytest.mark.asyncio - async def test_tool_tracking_uses_current_adk_session(self, plugin: GalileoADKPlugin) -> None: + async def test_tool_tracking_uses_current_adk_session(self, plugin: SplunkAOADKPlugin) -> None: """Tools use _current_adk_session for consistent session tracking.""" inv_id = str(uuid4()) session_id = "main_session" @@ -519,7 +519,7 @@ async def test_tool_tracking_uses_current_adk_session(self, plugin: GalileoADKPl assert plugin._tracker.get_active_tool(session_id) is None @pytest.mark.asyncio - async def test_sub_invocation_parented_to_tool_via_session(self, plugin: GalileoADKPlugin) -> None: + async def test_sub_invocation_parented_to_tool_via_session(self, plugin: SplunkAOADKPlugin) -> None: """Sub-invocation from AgentTool uses parent's Galileo session despite different ADK session_id.""" parent_inv_id = str(uuid4()) child_inv_id = str(uuid4()) @@ -579,7 +579,7 @@ async def test_sub_invocation_parented_to_tool_via_session(self, plugin: Galileo assert plugin._tracker.get_active_tool(parent_session_id) is None @pytest.mark.asyncio - async def test_tool_parent_fallback_to_run_when_no_agent(self, plugin: GalileoADKPlugin) -> None: + async def test_tool_parent_fallback_to_run_when_no_agent(self, plugin: SplunkAOADKPlugin) -> None: """Tool falls back to invocation run when agent_name is not available.""" invocation_id = str(uuid4()) session_id = "test_session" @@ -604,7 +604,7 @@ async def test_tool_parent_fallback_to_run_when_no_agent(self, plugin: GalileoAD assert plugin._tracker.tool_count == 1 @pytest.mark.asyncio - async def test_tool_parent_fallback_to_active_tool(self, plugin: GalileoADKPlugin) -> None: + async def test_tool_parent_fallback_to_active_tool(self, plugin: SplunkAOADKPlugin) -> None: """Tool falls back to active tool when no agent or run is available.""" invocation_id = str(uuid4()) session_id = "test_session" @@ -628,11 +628,11 @@ class TestGetParentAgentRunId: """Tests for _get_parent_agent_run_id helper method.""" @pytest.fixture - def plugin(self) -> GalileoADKPlugin: - return GalileoADKPlugin(ingestion_hook=lambda r: None) + def plugin(self) -> SplunkAOADKPlugin: + return SplunkAOADKPlugin(ingestion_hook=lambda r: None) @pytest.mark.asyncio - async def test_finds_parent_agent_via_hierarchy(self, plugin: GalileoADKPlugin) -> None: + async def test_finds_parent_agent_via_hierarchy(self, plugin: SplunkAOADKPlugin) -> None: """Parent agent run_id found via ADK's parent_agent hierarchy.""" # Given: a parent agent and child agent setup invocation_id = str(uuid4()) @@ -663,7 +663,7 @@ async def test_finds_parent_agent_via_hierarchy(self, plugin: GalileoADKPlugin) assert result == parent_run_id @pytest.mark.asyncio - async def test_fallback_to_root_invocation(self, plugin: GalileoADKPlugin) -> None: + async def test_fallback_to_root_invocation(self, plugin: SplunkAOADKPlugin) -> None: """Falls back to root invocation run_id when no parent agent.""" # Given: a run span without any agents invocation_id = str(uuid4()) @@ -683,7 +683,7 @@ async def test_fallback_to_root_invocation(self, plugin: GalileoADKPlugin) -> No # Then: root run_id is returned as fallback assert result == run_id - def test_handles_exception_in_hierarchy_traversal(self, plugin: GalileoADKPlugin) -> None: + def test_handles_exception_in_hierarchy_traversal(self, plugin: SplunkAOADKPlugin) -> None: """Handles exceptions gracefully when traversing hierarchy.""" # Given: a context that raises when accessing hierarchy callback = MagicMock() @@ -702,11 +702,11 @@ class TestForceCommitPartialTrace: """Tests for _force_commit_partial_trace helper method.""" @pytest.fixture - def plugin(self) -> GalileoADKPlugin: - return GalileoADKPlugin(ingestion_hook=lambda r: None) + def plugin(self) -> SplunkAOADKPlugin: + return SplunkAOADKPlugin(ingestion_hook=lambda r: None) @pytest.mark.asyncio - async def test_closes_all_open_agent_spans(self, plugin: GalileoADKPlugin) -> None: + async def test_closes_all_open_agent_spans(self, plugin: SplunkAOADKPlugin) -> None: """All open agent spans are closed with error status.""" # Given: an invocation with an open agent span invocation_id = str(uuid4()) @@ -729,7 +729,7 @@ async def test_closes_all_open_agent_spans(self, plugin: GalileoADKPlugin) -> No assert plugin._tracker.get_agent(invocation_id, "test_agent") is None @pytest.mark.asyncio - async def test_closes_run_span_with_error_status(self, plugin: GalileoADKPlugin) -> None: + async def test_closes_run_span_with_error_status(self, plugin: SplunkAOADKPlugin) -> None: """Run span is closed with error output and status code.""" # Given: an invocation with a run span invocation_id = str(uuid4()) @@ -750,7 +750,7 @@ async def test_closes_run_span_with_error_status(self, plugin: GalileoADKPlugin) assert plugin._tracker.get_run(invocation_id) is None @pytest.mark.asyncio - async def test_status_code_extracted_from_error(self, plugin: GalileoADKPlugin) -> None: + async def test_status_code_extracted_from_error(self, plugin: SplunkAOADKPlugin) -> None: """Status code is extracted from error for span closure.""" # Given: an invocation with spans invocation_id = str(uuid4()) @@ -772,7 +772,7 @@ async def test_status_code_extracted_from_error(self, plugin: GalileoADKPlugin) assert plugin._tracker.get_agent(invocation_id, "test_agent") is None @pytest.mark.asyncio - async def test_closes_all_open_tool_spans(self, plugin: GalileoADKPlugin) -> None: + async def test_closes_all_open_tool_spans(self, plugin: SplunkAOADKPlugin) -> None: """All open tool spans are closed with error status.""" # Given: an invocation with an open tool span invocation_id = str(uuid4()) @@ -801,7 +801,7 @@ async def test_closes_all_open_tool_spans(self, plugin: GalileoADKPlugin) -> Non assert plugin._tracker.tool_count == 0 @pytest.mark.asyncio - async def test_closes_all_open_llm_spans(self, plugin: GalileoADKPlugin) -> None: + async def test_closes_all_open_llm_spans(self, plugin: SplunkAOADKPlugin) -> None: """All open LLM spans are closed with error status.""" # Given: an invocation with an open LLM span invocation_id = str(uuid4()) @@ -825,7 +825,7 @@ async def test_closes_all_open_llm_spans(self, plugin: GalileoADKPlugin) -> None assert plugin._tracker.llm_count == 0 @pytest.mark.asyncio - async def test_clears_active_tool_on_force_commit(self, plugin: GalileoADKPlugin) -> None: + async def test_clears_active_tool_on_force_commit(self, plugin: SplunkAOADKPlugin) -> None: """Active tool is cleared when force committing partial trace.""" # Given: an invocation with an active tool invocation_id = str(uuid4()) @@ -858,11 +858,11 @@ class TestAfterRunCallbackCleanup: """Tests for after_run_callback cleanup of orphaned spans.""" @pytest.fixture - def plugin(self) -> GalileoADKPlugin: - return GalileoADKPlugin(ingestion_hook=lambda r: None) + def plugin(self) -> SplunkAOADKPlugin: + return SplunkAOADKPlugin(ingestion_hook=lambda r: None) @pytest.mark.asyncio - async def test_cleans_up_orphaned_tools(self, plugin: GalileoADKPlugin) -> None: + async def test_cleans_up_orphaned_tools(self, plugin: SplunkAOADKPlugin) -> None: """Orphaned tool spans are closed on after_run_callback.""" # Given: an invocation with a tool that wasn't closed invocation_id = str(uuid4()) @@ -893,7 +893,7 @@ async def test_cleans_up_orphaned_tools(self, plugin: GalileoADKPlugin) -> None: assert plugin._tracker.tool_count == 0 @pytest.mark.asyncio - async def test_cleans_up_orphaned_llms(self, plugin: GalileoADKPlugin) -> None: + async def test_cleans_up_orphaned_llms(self, plugin: SplunkAOADKPlugin) -> None: """Orphaned LLM spans are closed on after_run_callback.""" # Given: an invocation with an LLM that wasn't closed invocation_id = str(uuid4()) @@ -922,7 +922,7 @@ async def test_cleans_up_orphaned_llms(self, plugin: GalileoADKPlugin) -> None: assert plugin._tracker.llm_count == 0 @pytest.mark.asyncio - async def test_cleans_up_orphaned_agents(self, plugin: GalileoADKPlugin) -> None: + async def test_cleans_up_orphaned_agents(self, plugin: SplunkAOADKPlugin) -> None: """Orphaned agent spans are closed on after_run_callback.""" # Given: an invocation with an agent that wasn't closed invocation_id = str(uuid4()) @@ -944,7 +944,7 @@ async def test_cleans_up_orphaned_agents(self, plugin: GalileoADKPlugin) -> None assert plugin._tracker.agent_count == 0 @pytest.mark.asyncio - async def test_tool_error_clears_active_tool(self, plugin: GalileoADKPlugin) -> None: + async def test_tool_error_clears_active_tool(self, plugin: SplunkAOADKPlugin) -> None: """Tool error properly clears the active tool for the session.""" inv_id = str(uuid4()) session_id = "test_session_error" @@ -987,11 +987,11 @@ class TestAutomaticSessionMapping: """Tests for automatic ADK session_id → Galileo session mapping.""" @pytest.fixture - def plugin(self) -> GalileoADKPlugin: - return GalileoADKPlugin(ingestion_hook=lambda r: None) + def plugin(self) -> SplunkAOADKPlugin: + return SplunkAOADKPlugin(ingestion_hook=lambda r: None) @pytest.mark.asyncio - async def test_session_mapped_on_first_user_message(self, plugin: GalileoADKPlugin) -> None: + async def test_session_mapped_on_first_user_message(self, plugin: SplunkAOADKPlugin) -> None: """ADK session_id is mapped to Galileo session on first user message.""" # Given: an invocation context with a session_id inv_context = MockInvocationContext() @@ -1007,7 +1007,7 @@ async def test_session_mapped_on_first_user_message(self, plugin: GalileoADKPlug assert plugin._observer._current_adk_session == "adk-session-123" @pytest.mark.asyncio - async def test_session_not_remapped_for_same_session_id(self, plugin: GalileoADKPlugin) -> None: + async def test_session_not_remapped_for_same_session_id(self, plugin: SplunkAOADKPlugin) -> None: """Same session_id doesn't trigger repeated session mapping.""" # Given: two invocations with the same session_id session_id = "persistent-session" @@ -1033,7 +1033,7 @@ async def test_session_not_remapped_for_same_session_id(self, plugin: GalileoADK assert first_mapped_session == second_mapped_session == session_id @pytest.mark.asyncio - async def test_unknown_session_id_not_mapped(self, plugin: GalileoADKPlugin) -> None: + async def test_unknown_session_id_not_mapped(self, plugin: SplunkAOADKPlugin) -> None: """Session_id of 'unknown' is not mapped to Galileo session.""" # Given: an invocation context with unknown session_id inv_context = MockInvocationContext() @@ -1049,7 +1049,7 @@ async def test_unknown_session_id_not_mapped(self, plugin: GalileoADKPlugin) -> assert plugin._observer._current_adk_session is None @pytest.mark.asyncio - async def test_session_mapping_with_sub_invocations(self, plugin: GalileoADKPlugin) -> None: + async def test_session_mapping_with_sub_invocations(self, plugin: SplunkAOADKPlugin) -> None: """Sub-invocations with same session_id don't trigger remapping.""" # Given: parent and child invocations sharing a session shared_session = "shared-session-xyz" @@ -1077,7 +1077,7 @@ async def test_session_mapping_with_sub_invocations(self, plugin: GalileoADKPlug assert plugin._observer._current_adk_session == shared_session @pytest.mark.asyncio - async def test_session_updated_when_different_session_id_top_level(self, plugin: GalileoADKPlugin) -> None: + async def test_session_updated_when_different_session_id_top_level(self, plugin: SplunkAOADKPlugin) -> None: """Different session_id from top-level call triggers session update.""" # Given: first invocation with session-1 (no active tools) inv_context_1 = MockInvocationContext(invocation_id="inv-1") diff --git a/galileo-adk/tests/test_retriever.py b/galileo-adk/tests/test_retriever.py index 9beddf92..1e3055f2 100644 --- a/galileo-adk/tests/test_retriever.py +++ b/galileo-adk/tests/test_retriever.py @@ -1,4 +1,4 @@ -"""Tests for retriever span detection in GalileoObserver.""" +"""Tests for retriever span detection in SplunkAOObserver.""" from __future__ import annotations @@ -7,8 +7,8 @@ import pytest -from galileo_adk.decorator import galileo_retriever -from galileo_adk.observer import GalileoObserver +from galileo_adk.decorator import splunk_ao_retriever +from galileo_adk.observer import SplunkAOObserver from .mocks import MockTool, MockToolContext @@ -36,32 +36,32 @@ def __init__(self, func: object, name: str = "function_tool") -> None: @pytest.fixture -def observer() -> GalileoObserver: +def observer() -> SplunkAOObserver: """Create observer with ingestion hook for testing (no credentials needed).""" - return GalileoObserver(ingestion_hook=lambda r: None) + return SplunkAOObserver(ingestion_hook=lambda r: None) class TestGalileoRetrieverDecorator: - """Tests for the @galileo_retriever decorator.""" + """Tests for the @splunk_ao_retriever decorator.""" def test_decorator_sets_attribute(self) -> None: # Given: a plain function def my_search(query: str) -> str: return "results" - # When: decorating with @galileo_retriever - decorated = galileo_retriever(my_search) + # When: decorating with @splunk_ao_retriever + decorated = splunk_ao_retriever(my_search) - # Then: the function has _galileo_is_retriever = True - assert getattr(decorated, "_galileo_is_retriever", False) is True + # Then: the function has _splunk_ao_is_retriever = True + assert getattr(decorated, "_splunk_ao_is_retriever", False) is True def test_decorator_preserves_function(self) -> None: # Given: a function with specific behavior def my_search(query: str) -> str: return f"results for {query}" - # When: decorating with @galileo_retriever - decorated = galileo_retriever(my_search) + # When: decorating with @splunk_ao_retriever + decorated = splunk_ao_retriever(my_search) # Then: the function still works as expected assert decorated("test") == "results for test" @@ -69,18 +69,18 @@ def my_search(query: str) -> str: def test_decorator_syntax(self) -> None: # Given/When: using decorator syntax - @galileo_retriever + @splunk_ao_retriever def my_search(query: str) -> str: return "results" # Then: the function is marked as a retriever - assert getattr(my_search, "_galileo_is_retriever", False) is True + assert getattr(my_search, "_splunk_ao_is_retriever", False) is True class TestIsRetrieverTool: """Tests for _is_retriever_tool detection method.""" - def test_regular_tool_is_not_retriever(self, observer: GalileoObserver) -> None: + def test_regular_tool_is_not_retriever(self, observer: SplunkAOObserver) -> None: # Given: a regular tool without retriever characteristics tool = MockTool(name="calculator") @@ -91,7 +91,7 @@ def test_regular_tool_is_not_retriever(self, observer: GalileoObserver) -> None: assert result is False @patch("galileo_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) - def test_base_retrieval_tool_instance_is_detected(self, observer: GalileoObserver) -> None: + def test_base_retrieval_tool_instance_is_detected(self, observer: SplunkAOObserver) -> None: # Given: a tool that is an instance of BaseRetrievalTool tool = MockBaseRetrievalTool(name="vertex_ai_rag") @@ -102,7 +102,7 @@ def test_base_retrieval_tool_instance_is_detected(self, observer: GalileoObserve assert result is True @patch("galileo_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) - def test_base_retrieval_tool_subclass_is_detected(self, observer: GalileoObserver) -> None: + def test_base_retrieval_tool_subclass_is_detected(self, observer: SplunkAOObserver) -> None: # Given: a tool that is a subclass of BaseRetrievalTool tool = MockRetrieverSubclass(name="custom_rag") @@ -112,9 +112,9 @@ def test_base_retrieval_tool_subclass_is_detected(self, observer: GalileoObserve # Then: it is detected as a retriever assert result is True - def test_decorated_function_tool_is_detected(self, observer: GalileoObserver) -> None: - # Given: a function decorated with @galileo_retriever wrapped in FunctionTool - @galileo_retriever + def test_decorated_function_tool_is_detected(self, observer: SplunkAOObserver) -> None: + # Given: a function decorated with @splunk_ao_retriever wrapped in FunctionTool + @splunk_ao_retriever def my_search(query: str) -> str: return "results" @@ -126,8 +126,8 @@ def my_search(query: str) -> str: # Then: it is detected as a retriever assert result is True - def test_undecorated_function_tool_is_not_retriever(self, observer: GalileoObserver) -> None: - # Given: a function NOT decorated with @galileo_retriever wrapped in FunctionTool + def test_undecorated_function_tool_is_not_retriever(self, observer: SplunkAOObserver) -> None: + # Given: a function NOT decorated with @splunk_ao_retriever wrapped in FunctionTool def my_calculator(expression: str) -> str: return "42" @@ -140,7 +140,7 @@ def my_calculator(expression: str) -> str: assert result is False @patch("galileo_adk.observer._BaseRetrievalTool", None) - def test_isinstance_skipped_when_base_class_unavailable(self, observer: GalileoObserver) -> None: + def test_isinstance_skipped_when_base_class_unavailable(self, observer: SplunkAOObserver) -> None: # Given: BaseRetrievalTool is not importable (None) and tool has no func attribute tool = MockBaseRetrievalTool(name="some_retriever") @@ -150,7 +150,7 @@ def test_isinstance_skipped_when_base_class_unavailable(self, observer: GalileoO # Then: it is NOT detected (isinstance check is skipped, no func attribute) assert result is False - def test_tool_without_func_attribute_is_not_retriever(self, observer: GalileoObserver) -> None: + def test_tool_without_func_attribute_is_not_retriever(self, observer: SplunkAOObserver) -> None: # Given: a tool with no func attribute and not a BaseRetrievalTool tool = MockTool(name="any_tool") @@ -164,9 +164,9 @@ def test_tool_without_func_attribute_is_not_retriever(self, observer: GalileoObs class TestOnToolStartRetriever: """Tests for on_tool_start retriever span creation.""" - def test_decorated_retriever_creates_retriever_span(self, observer: GalileoObserver) -> None: - # Given: a function decorated with @galileo_retriever wrapped in FunctionTool - @galileo_retriever + def test_decorated_retriever_creates_retriever_span(self, observer: SplunkAOObserver) -> None: + # Given: a function decorated with @splunk_ao_retriever wrapped in FunctionTool + @splunk_ao_retriever def my_search(query: str) -> str: return "results" @@ -188,9 +188,9 @@ def my_search(query: str) -> str: assert call_kwargs["is_retriever"] is True assert call_kwargs["name"] == "my_search" - def test_retriever_tool_extracts_query_from_tool_args(self, observer: GalileoObserver) -> None: + def test_retriever_tool_extracts_query_from_tool_args(self, observer: SplunkAOObserver) -> None: # Given: a retriever tool with a "query" key in tool_args - @galileo_retriever + @splunk_ao_retriever def my_search(query: str) -> str: return "results" @@ -210,9 +210,9 @@ def my_search(query: str) -> str: call_kwargs = mock_start.call_args.kwargs assert call_kwargs["input_data"] == "What is RAG?" - def test_retriever_tool_falls_back_when_no_query_key(self, observer: GalileoObserver) -> None: + def test_retriever_tool_falls_back_when_no_query_key(self, observer: SplunkAOObserver) -> None: # Given: a retriever tool without a "query" key in tool_args - @galileo_retriever + @splunk_ao_retriever def my_search(search_text: str) -> str: return "results" @@ -232,7 +232,7 @@ def my_search(search_text: str) -> str: call_kwargs = mock_start.call_args.kwargs assert "search_text" in call_kwargs["input_data"] - def test_regular_tool_creates_tool_span(self, observer: GalileoObserver) -> None: + def test_regular_tool_creates_tool_span(self, observer: SplunkAOObserver) -> None: # Given: a regular tool (not a retriever) tool = MockTool(name="calculator") tool_context = MockToolContext() @@ -250,7 +250,7 @@ def test_regular_tool_creates_tool_span(self, observer: GalileoObserver) -> None call_kwargs = mock_start.call_args.kwargs assert call_kwargs["is_retriever"] is False - def test_on_tool_start_returns_uuid(self, observer: GalileoObserver) -> None: + def test_on_tool_start_returns_uuid(self, observer: SplunkAOObserver) -> None: # Given: any tool tool = MockTool(name="test_tool") tool_context = MockToolContext() @@ -267,7 +267,7 @@ def test_on_tool_start_returns_uuid(self, observer: GalileoObserver) -> None: assert isinstance(run_id, UUID) @patch("galileo_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) - def test_base_retrieval_tool_creates_retriever_span(self, observer: GalileoObserver) -> None: + def test_base_retrieval_tool_creates_retriever_span(self, observer: SplunkAOObserver) -> None: # Given: a tool that is an instance of BaseRetrievalTool tool = MockBaseRetrievalTool(name="vertex_ai_rag") tool_context = MockToolContext() diff --git a/src/splunk_ao/config.py b/src/splunk_ao/config.py index 7e1fd778..c2814757 100644 --- a/src/splunk_ao/config.py +++ b/src/splunk_ao/config.py @@ -44,6 +44,7 @@ def _bridge_env_vars() -> None: """ _BRIDGE = [ ("SPLUNK_AO_API_KEY", "GALILEO_API_KEY"), + ("SPLUNK_AO_API_URL", "GALILEO_API_URL"), ("SPLUNK_AO_CONSOLE_URL", "GALILEO_CONSOLE_URL"), ("SPLUNK_AO_PROJECT", "GALILEO_PROJECT"), ("SPLUNK_AO_PROJECT_ID", "GALILEO_PROJECT_ID"),