From 375c0be513db6f6f6cd2b58f0b01d0fbd3edadc8 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 16 Jun 2026 15:51:05 -0700 Subject: [PATCH 01/22] 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"), From 788e932bb06f5825db0c240af7ca0703f70aae48 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 16 Jun 2026 16:24:07 -0700 Subject: [PATCH 02/22] fix(galileo-adk): rename package to splunk-ao-adk and reset to v0.1.0 - Rename PyPI package: galileo-adk -> splunk-ao-adk - Reset version to 0.1.0 (__init__.py + pyproject.toml) - Update description, authors (Splunk Inc.), repository URL - Replace galileo dependency with splunk-ao>=2.0.0,<3.0.0 - Update tool.uv.sources key: galileo -> splunk-ao - Update semantic_release tags: galileo-adk-v* -> splunk-ao-adk-v* - Replace galileo-core[testing] with splunk-ao[otel] in dev deps - Update ruff isort known-first-party to splunk_ao - Update README: badges and pip install -> splunk-ao-adk Co-authored-by: Cursor --- galileo-adk/README.md | 10 +++++----- galileo-adk/pyproject.toml | 24 +++++++++++------------- galileo-adk/src/galileo_adk/__init__.py | 2 +- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/galileo-adk/README.md b/galileo-adk/README.md index 191c5c57..11bc0b74 100644 --- a/galileo-adk/README.md +++ b/galileo-adk/README.md @@ -1,15 +1,15 @@ -# galileo-adk +# splunk-ao-adk -[![PyPI version](https://img.shields.io/pypi/v/galileo-adk.svg)](https://pypi.org/project/galileo-adk/) -[![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) +[![PyPI version](https://img.shields.io/pypi/v/splunk-ao-adk.svg)](https://pypi.org/project/splunk-ao-adk/) +[![Python versions](https://img.shields.io/pypi/pyversions/splunk-ao-adk.svg)](https://pypi.org/project/splunk-ao-adk/) +[![License](https://img.shields.io/pypi/l/splunk-ao-adk.svg)](https://github.com/splunk/splunk-ao-python/blob/main/LICENSE) Splunk AO observability for [Google ADK](https://github.com/google/adk-python) agents. Automatic tracing of agent runs, LLM calls, and tool executions. ## Installation ```bash -pip install galileo-adk +pip install splunk-ao-adk ``` **Requirements:** Python 3.10+, a [Splunk AO API key](https://www.splunk.com/), and a [Google AI API key](https://aistudio.google.com/apikey) diff --git a/galileo-adk/pyproject.toml b/galileo-adk/pyproject.toml index aeb15526..5f4c1f65 100644 --- a/galileo-adk/pyproject.toml +++ b/galileo-adk/pyproject.toml @@ -1,11 +1,11 @@ [project] -name = "galileo-adk" -version = "2.0.1" -description = "Galileo observability integration for Google ADK" +name = "splunk-ao-adk" +version = "0.1.0" +description = "Splunk AO observability integration for Google ADK" readme = "README.md" requires-python = ">=3.10,<3.15" license = { text = "Apache-2.0" } -authors = [{ name = "Galileo Technologies Inc.", email = "team@galileo.ai" }] +authors = [{ name = "Splunk Inc.", email = "team@splunk.com" }] classifiers = [ "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", @@ -15,18 +15,16 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "galileo>=2.0.0,<3.0.0", + "splunk-ao>=2.0.0,<3.0.0", "google-adk>=1.14.1,<2.0.0", ] [project.urls] -Repository = "https://github.com/rungalileo/galileo-python" +Repository = "https://github.com/splunk/splunk-ao-python" # UV-specific configuration (path for dev, ignored when installed from PyPI) - - [tool.uv] -sources = { galileo = { path = "../", editable = true } } +sources = { "splunk-ao" = { path = "../", editable = true } } [tool.hatch.build.targets.wheel] packages = ["src/galileo_adk"] @@ -81,7 +79,7 @@ ignore = [ ] [tool.ruff.lint.isort] -known-first-party = ["galileo_adk", "galileo"] +known-first-party = ["galileo_adk", "splunk_ao"] [tool.mypy] @@ -114,9 +112,9 @@ exclude_lines = [ [tool.semantic_release] version_variables = ["src/galileo_adk/__init__.py:__version__"] version_toml = ["pyproject.toml:project.version"] -tag_format = "galileo-adk-v{version}" +tag_format = "splunk-ao-adk-v{version}" version_source = "tag" -commit_message = "chore(release): galileo-adk v{version}\n\nAutomatically generated by python-semantic-release" +commit_message = "chore(release): splunk-ao-adk v{version}\n\nAutomatically generated by python-semantic-release" [tool.semantic_release.commit_parser_options] patch_tags = ["fix", "perf", "chore", "docs", "style", "refactor"] @@ -139,7 +137,7 @@ dev = [ "ruff>=0.12.3", "mypy>=1.16.0", "coverage>=7.9.2", - "galileo-core[testing]>=3.82.0", + "splunk-ao[otel]>=2.0.0,<3.0.0", ] [build-system] diff --git a/galileo-adk/src/galileo_adk/__init__.py b/galileo-adk/src/galileo_adk/__init__.py index 47011c8d..4a4788c2 100644 --- a/galileo-adk/src/galileo_adk/__init__.py +++ b/galileo-adk/src/galileo_adk/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.0.1" +__version__ = "0.1.0" from galileo_adk.callback import SplunkAOADKCallback from galileo_adk.decorator import splunk_ao_retriever From 67476e6a1d267eaa5ced118e9e1cecaaa5dc5be9 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Wed, 17 Jun 2026 12:02:55 -0700 Subject: [PATCH 03/22] refactor(galileo-adk): rename dir to splunk-ao-adk and module to splunk_ao_adk - Rename top-level directory: galileo-adk/ -> splunk-ao-adk/ - Rename source package: src/galileo_adk/ -> src/splunk_ao_adk/ - Update all internal imports: galileo_adk -> splunk_ao_adk in all .py files - Update pyproject.toml: packages, coverage.source, semantic_release.version_variables, ruff.isort.known-first-party to reference splunk_ao_adk - Update tests: all from/import/patch paths now use splunk_ao_adk Co-authored-by: Cursor --- galileo-adk/src/galileo_adk/__init__.py | 13 ------------- {galileo-adk => splunk-ao-adk}/.gitignore | 0 {galileo-adk => splunk-ao-adk}/CHANGELOG.md | 0 {galileo-adk => splunk-ao-adk}/CONTRIBUTING.md | 4 ++-- {galileo-adk => splunk-ao-adk}/README.md | 12 ++++++------ {galileo-adk => splunk-ao-adk}/pyproject.toml | 8 ++++---- splunk-ao-adk/src/splunk_ao_adk/__init__.py | 13 +++++++++++++ .../src/splunk_ao_adk}/callback.py | 4 ++-- .../src/splunk_ao_adk}/data_converters.py | 0 .../src/splunk_ao_adk}/decorator.py | 2 +- .../src/splunk_ao_adk}/observer.py | 6 +++--- .../src/splunk_ao_adk}/plugin.py | 4 ++-- .../src/splunk_ao_adk}/py.typed | 0 .../src/splunk_ao_adk}/span_manager.py | 2 +- .../src/splunk_ao_adk}/span_tracker.py | 0 .../src/splunk_ao_adk}/trace_builder.py | 0 .../src/splunk_ao_adk}/types.py | 0 {galileo-adk => splunk-ao-adk}/tests/__init__.py | 0 {galileo-adk => splunk-ao-adk}/tests/conftest.py | 0 {galileo-adk => splunk-ao-adk}/tests/mocks.py | 0 .../tests/test_callback.py | 4 ++-- .../tests/test_data_converters.py | 2 +- .../tests/test_observer.py | 2 +- {galileo-adk => splunk-ao-adk}/tests/test_plugin.py | 2 +- .../tests/test_retriever.py | 12 ++++++------ .../tests/test_span_manager.py | 2 +- .../tests/test_span_tracker.py | 2 +- .../tests/test_status_code.py | 4 ++-- .../tests/test_trace_builder.py | 2 +- 29 files changed, 50 insertions(+), 50 deletions(-) delete mode 100644 galileo-adk/src/galileo_adk/__init__.py rename {galileo-adk => splunk-ao-adk}/.gitignore (100%) rename {galileo-adk => splunk-ao-adk}/CHANGELOG.md (100%) rename {galileo-adk => splunk-ao-adk}/CONTRIBUTING.md (98%) rename {galileo-adk => splunk-ao-adk}/README.md (96%) rename {galileo-adk => splunk-ao-adk}/pyproject.toml (95%) create mode 100644 splunk-ao-adk/src/splunk_ao_adk/__init__.py rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/callback.py (98%) rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/data_converters.py (100%) rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/decorator.py (93%) rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/observer.py (99%) rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/plugin.py (99%) rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/py.typed (100%) rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/span_manager.py (99%) rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/span_tracker.py (100%) rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/trace_builder.py (100%) rename {galileo-adk/src/galileo_adk => splunk-ao-adk/src/splunk_ao_adk}/types.py (100%) rename {galileo-adk => splunk-ao-adk}/tests/__init__.py (100%) rename {galileo-adk => splunk-ao-adk}/tests/conftest.py (100%) rename {galileo-adk => splunk-ao-adk}/tests/mocks.py (100%) rename {galileo-adk => splunk-ao-adk}/tests/test_callback.py (99%) rename {galileo-adk => splunk-ao-adk}/tests/test_data_converters.py (99%) rename {galileo-adk => splunk-ao-adk}/tests/test_observer.py (99%) rename {galileo-adk => splunk-ao-adk}/tests/test_plugin.py (99%) rename {galileo-adk => splunk-ao-adk}/tests/test_retriever.py (96%) rename {galileo-adk => splunk-ao-adk}/tests/test_span_manager.py (99%) rename {galileo-adk => splunk-ao-adk}/tests/test_span_tracker.py (99%) rename {galileo-adk => splunk-ao-adk}/tests/test_status_code.py (98%) rename {galileo-adk => splunk-ao-adk}/tests/test_trace_builder.py (99%) diff --git a/galileo-adk/src/galileo_adk/__init__.py b/galileo-adk/src/galileo_adk/__init__.py deleted file mode 100644 index 4a4788c2..00000000 --- a/galileo-adk/src/galileo_adk/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -__version__ = "0.1.0" - -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 SplunkAOADKPlugin - -__all__ = [ - "SplunkAOADKPlugin", - "SplunkAOADKCallback", - "splunk_ao_retriever", - "get_custom_metadata", -] diff --git a/galileo-adk/.gitignore b/splunk-ao-adk/.gitignore similarity index 100% rename from galileo-adk/.gitignore rename to splunk-ao-adk/.gitignore diff --git a/galileo-adk/CHANGELOG.md b/splunk-ao-adk/CHANGELOG.md similarity index 100% rename from galileo-adk/CHANGELOG.md rename to splunk-ao-adk/CHANGELOG.md diff --git a/galileo-adk/CONTRIBUTING.md b/splunk-ao-adk/CONTRIBUTING.md similarity index 98% rename from galileo-adk/CONTRIBUTING.md rename to splunk-ao-adk/CONTRIBUTING.md index 12e74e23..cbfac454 100644 --- a/galileo-adk/CONTRIBUTING.md +++ b/splunk-ao-adk/CONTRIBUTING.md @@ -10,7 +10,7 @@ This package is part of the [galileo-python](https://github.com/rungalileo/galil galileo-python/ ├── src/splunk_ao/ ← Main Galileo SDK └── galileo-adk/ - ├── src/galileo_adk/ + ├── src/splunk_ao_adk/ ├── tests/ ├── pyproject.toml └── README.md @@ -124,7 +124,7 @@ source .venv/bin/activate pytest tests -v # Run with coverage -pytest tests --cov=galileo_adk --cov-report=term-missing +pytest tests --cov=splunk_ao_adk --cov-report=term-missing # Run linting ruff check src/ diff --git a/galileo-adk/README.md b/splunk-ao-adk/README.md similarity index 96% rename from galileo-adk/README.md rename to splunk-ao-adk/README.md index 11bc0b74..0c4e9e34 100644 --- a/galileo-adk/README.md +++ b/splunk-ao-adk/README.md @@ -18,7 +18,7 @@ pip install splunk-ao-adk ```python import asyncio -from galileo_adk import SplunkAOADKPlugin +from splunk_ao_adk import SplunkAOADKPlugin from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.genai import types @@ -54,7 +54,7 @@ All traces with the same `session_id` are automatically grouped into a Splunk AO ```python import asyncio -from galileo_adk import SplunkAOADKPlugin +from splunk_ao_adk import SplunkAOADKPlugin from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.genai import types @@ -90,7 +90,7 @@ Attach custom metadata to traces using ADK's `RunConfig`. Metadata is propagated ```python import asyncio -from galileo_adk import SplunkAOADKPlugin +from splunk_ao_adk import SplunkAOADKPlugin from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.adk.agents.run_config import RunConfig @@ -131,7 +131,7 @@ For granular control over which callbacks to use, attach them directly to your a ```python import asyncio -from galileo_adk import SplunkAOADKCallback +from splunk_ao_adk import SplunkAOADKCallback from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.genai import types @@ -167,7 +167,7 @@ if __name__ == "__main__": 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 splunk_ao_retriever +from splunk_ao_adk import splunk_ao_retriever from google.adk.tools import FunctionTool @splunk_ao_retriever @@ -187,7 +187,7 @@ Intercept traces for custom processing before forwarding to Splunk AO: import asyncio import os from splunk_ao import GalileoLogger -from galileo_adk import SplunkAOADKPlugin +from splunk_ao_adk import SplunkAOADKPlugin from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.genai import types diff --git a/galileo-adk/pyproject.toml b/splunk-ao-adk/pyproject.toml similarity index 95% rename from galileo-adk/pyproject.toml rename to splunk-ao-adk/pyproject.toml index 5f4c1f65..07fcc65a 100644 --- a/galileo-adk/pyproject.toml +++ b/splunk-ao-adk/pyproject.toml @@ -27,7 +27,7 @@ Repository = "https://github.com/splunk/splunk-ao-python" sources = { "splunk-ao" = { path = "../", editable = true } } [tool.hatch.build.targets.wheel] -packages = ["src/galileo_adk"] +packages = ["src/splunk_ao_adk"] [tool.pytest.ini_options] @@ -79,7 +79,7 @@ ignore = [ ] [tool.ruff.lint.isort] -known-first-party = ["galileo_adk", "splunk_ao"] +known-first-party = ["splunk_ao_adk", "splunk_ao"] [tool.mypy] @@ -91,7 +91,7 @@ ignore_missing_imports = true follow_imports = "skip" [tool.coverage.run] -source = ["src/galileo_adk"] +source = ["src/splunk_ao_adk"] omit = ["tests/*"] [tool.coverage.report] @@ -110,7 +110,7 @@ exclude_lines = [ [tool.semantic_release] -version_variables = ["src/galileo_adk/__init__.py:__version__"] +version_variables = ["src/splunk_ao_adk/__init__.py:__version__"] version_toml = ["pyproject.toml:project.version"] tag_format = "splunk-ao-adk-v{version}" version_source = "tag" diff --git a/splunk-ao-adk/src/splunk_ao_adk/__init__.py b/splunk-ao-adk/src/splunk_ao_adk/__init__.py new file mode 100644 index 00000000..0c0bbd87 --- /dev/null +++ b/splunk-ao-adk/src/splunk_ao_adk/__init__.py @@ -0,0 +1,13 @@ +__version__ = "0.1.0" + +from splunk_ao_adk.callback import SplunkAOADKCallback +from splunk_ao_adk.decorator import splunk_ao_retriever +from splunk_ao_adk.observer import get_custom_metadata +from splunk_ao_adk.plugin import SplunkAOADKPlugin + +__all__ = [ + "SplunkAOADKPlugin", + "SplunkAOADKCallback", + "splunk_ao_retriever", + "get_custom_metadata", +] diff --git a/galileo-adk/src/galileo_adk/callback.py b/splunk-ao-adk/src/splunk_ao_adk/callback.py similarity index 98% rename from galileo-adk/src/galileo_adk/callback.py rename to splunk-ao-adk/src/splunk_ao_adk/callback.py index 05ed65b6..d973b5f9 100644 --- a/galileo-adk/src/galileo_adk/callback.py +++ b/splunk-ao-adk/src/splunk_ao_adk/callback.py @@ -9,7 +9,7 @@ from splunk_ao.schema.trace import TracesIngestRequest -from galileo_adk.observer import ( +from splunk_ao_adk.observer import ( SplunkAOObserver, get_agent_name_from_tool_context, get_custom_metadata, @@ -18,7 +18,7 @@ get_tool_invocation_id, get_tool_session_id, ) -from galileo_adk.span_tracker import SpanTracker +from splunk_ao_adk.span_tracker import SpanTracker try: from google.adk.agents.callback_context import CallbackContext diff --git a/galileo-adk/src/galileo_adk/data_converters.py b/splunk-ao-adk/src/splunk_ao_adk/data_converters.py similarity index 100% rename from galileo-adk/src/galileo_adk/data_converters.py rename to splunk-ao-adk/src/splunk_ao_adk/data_converters.py diff --git a/galileo-adk/src/galileo_adk/decorator.py b/splunk-ao-adk/src/splunk_ao_adk/decorator.py similarity index 93% rename from galileo-adk/src/galileo_adk/decorator.py rename to splunk-ao-adk/src/splunk_ao_adk/decorator.py index 048dc743..6dc3b6c5 100644 --- a/galileo-adk/src/galileo_adk/decorator.py +++ b/splunk-ao-adk/src/splunk_ao_adk/decorator.py @@ -15,7 +15,7 @@ def splunk_ao_retriever(func: Callable[..., Any]) -> Callable[..., Any]: Example ------- - >>> from galileo_adk import splunk_ao_retriever + >>> from splunk_ao_adk import splunk_ao_retriever >>> @splunk_ao_retriever ... def my_search(query: str) -> str: ... return search_docs(query) diff --git a/galileo-adk/src/galileo_adk/observer.py b/splunk-ao-adk/src/splunk_ao_adk/observer.py similarity index 99% rename from galileo-adk/src/galileo_adk/observer.py rename to splunk-ao-adk/src/splunk_ao_adk/observer.py index 120cb005..d6684ce5 100644 --- a/galileo-adk/src/galileo_adk/observer.py +++ b/splunk-ao-adk/src/splunk_ao_adk/observer.py @@ -15,13 +15,13 @@ from splunk_ao.schema.trace import TracesIngestRequest from splunk_ao.utils.serialization import serialize_to_str -from galileo_adk.data_converters import ( +from splunk_ao_adk.data_converters import ( convert_adk_content_to_splunk_ao_messages, convert_adk_tools_to_splunk_ao_format, extract_text_from_adk_content, ) -from galileo_adk.span_manager import SpanManager -from galileo_adk.trace_builder import TraceBuilder +from splunk_ao_adk.span_manager import SpanManager +from splunk_ao_adk.trace_builder import TraceBuilder _logger = logging.getLogger(__name__) diff --git a/galileo-adk/src/galileo_adk/plugin.py b/splunk-ao-adk/src/splunk_ao_adk/plugin.py similarity index 99% rename from galileo-adk/src/galileo_adk/plugin.py rename to splunk-ao-adk/src/splunk_ao_adk/plugin.py index 00dc0f0d..e2fa27d9 100644 --- a/galileo-adk/src/galileo_adk/plugin.py +++ b/splunk-ao-adk/src/splunk_ao_adk/plugin.py @@ -11,7 +11,7 @@ from splunk_ao.schema.trace import TracesIngestRequest -from galileo_adk.observer import ( +from splunk_ao_adk.observer import ( SplunkAOObserver, get_agent_name_from_tool_context, get_custom_metadata, @@ -20,7 +20,7 @@ get_tool_invocation_id, get_tool_session_id, ) -from galileo_adk.span_tracker import SpanTracker +from splunk_ao_adk.span_tracker import SpanTracker try: from google.adk.agents.callback_context import CallbackContext diff --git a/galileo-adk/src/galileo_adk/py.typed b/splunk-ao-adk/src/splunk_ao_adk/py.typed similarity index 100% rename from galileo-adk/src/galileo_adk/py.typed rename to splunk-ao-adk/src/splunk_ao_adk/py.typed diff --git a/galileo-adk/src/galileo_adk/span_manager.py b/splunk-ao-adk/src/splunk_ao_adk/span_manager.py similarity index 99% rename from galileo-adk/src/galileo_adk/span_manager.py rename to splunk-ao-adk/src/splunk_ao_adk/span_manager.py index ea824e26..b8ed09c9 100644 --- a/galileo-adk/src/galileo_adk/span_manager.py +++ b/splunk-ao-adk/src/splunk_ao_adk/span_manager.py @@ -9,7 +9,7 @@ from splunk_ao.handlers.base_handler import SplunkAOBaseHandler -from galileo_adk.types import RunContext +from splunk_ao_adk.types import RunContext # Integration tag for all spans INTEGRATION_TAG = "google_adk" diff --git a/galileo-adk/src/galileo_adk/span_tracker.py b/splunk-ao-adk/src/splunk_ao_adk/span_tracker.py similarity index 100% rename from galileo-adk/src/galileo_adk/span_tracker.py rename to splunk-ao-adk/src/splunk_ao_adk/span_tracker.py diff --git a/galileo-adk/src/galileo_adk/trace_builder.py b/splunk-ao-adk/src/splunk_ao_adk/trace_builder.py similarity index 100% rename from galileo-adk/src/galileo_adk/trace_builder.py rename to splunk-ao-adk/src/splunk_ao_adk/trace_builder.py diff --git a/galileo-adk/src/galileo_adk/types.py b/splunk-ao-adk/src/splunk_ao_adk/types.py similarity index 100% rename from galileo-adk/src/galileo_adk/types.py rename to splunk-ao-adk/src/splunk_ao_adk/types.py diff --git a/galileo-adk/tests/__init__.py b/splunk-ao-adk/tests/__init__.py similarity index 100% rename from galileo-adk/tests/__init__.py rename to splunk-ao-adk/tests/__init__.py diff --git a/galileo-adk/tests/conftest.py b/splunk-ao-adk/tests/conftest.py similarity index 100% rename from galileo-adk/tests/conftest.py rename to splunk-ao-adk/tests/conftest.py diff --git a/galileo-adk/tests/mocks.py b/splunk-ao-adk/tests/mocks.py similarity index 100% rename from galileo-adk/tests/mocks.py rename to splunk-ao-adk/tests/mocks.py diff --git a/galileo-adk/tests/test_callback.py b/splunk-ao-adk/tests/test_callback.py similarity index 99% rename from galileo-adk/tests/test_callback.py rename to splunk-ao-adk/tests/test_callback.py index 9321160c..2021dd84 100644 --- a/galileo-adk/tests/test_callback.py +++ b/splunk-ao-adk/tests/test_callback.py @@ -3,7 +3,7 @@ import pytest -from galileo_adk import SplunkAOADKCallback +from splunk_ao_adk import SplunkAOADKCallback from .mocks import ( MockCallbackContext, @@ -123,7 +123,7 @@ def test_initialization_with_ingestion_hook(self) -> None: assert callback._handler is not None def test_initialization_with_project_and_log_stream(self) -> None: - with patch("galileo_adk.observer.splunk_ao_context") as mock_context: + with patch("splunk_ao_adk.observer.splunk_ao_context") as mock_context: mock_logger = MagicMock() mock_context.get_logger_instance.return_value = mock_logger diff --git a/galileo-adk/tests/test_data_converters.py b/splunk-ao-adk/tests/test_data_converters.py similarity index 99% rename from galileo-adk/tests/test_data_converters.py rename to splunk-ao-adk/tests/test_data_converters.py index a6c4c2dc..91d2849b 100644 --- a/galileo-adk/tests/test_data_converters.py +++ b/splunk-ao-adk/tests/test_data_converters.py @@ -4,7 +4,7 @@ from galileo_core.schemas.logging.llm import MessageRole -from galileo_adk.data_converters import ( +from splunk_ao_adk.data_converters import ( _extract_tool_info, _try_direct_attributes, _try_function_declarations, diff --git a/galileo-adk/tests/test_observer.py b/splunk-ao-adk/tests/test_observer.py similarity index 99% rename from galileo-adk/tests/test_observer.py rename to splunk-ao-adk/tests/test_observer.py index 1cd29ff6..25c63367 100644 --- a/galileo-adk/tests/test_observer.py +++ b/splunk-ao-adk/tests/test_observer.py @@ -4,7 +4,7 @@ import pytest -from galileo_adk.observer import SplunkAOObserver +from splunk_ao_adk.observer import SplunkAOObserver from .mocks import MockContent, MockEvent, MockPart diff --git a/galileo-adk/tests/test_plugin.py b/splunk-ao-adk/tests/test_plugin.py similarity index 99% rename from galileo-adk/tests/test_plugin.py rename to splunk-ao-adk/tests/test_plugin.py index a33272ca..ab7a2e45 100644 --- a/galileo-adk/tests/test_plugin.py +++ b/splunk-ao-adk/tests/test_plugin.py @@ -5,7 +5,7 @@ import pytest -from galileo_adk import SplunkAOADKPlugin +from splunk_ao_adk import SplunkAOADKPlugin from .mocks import ( MockCallbackContext, diff --git a/galileo-adk/tests/test_retriever.py b/splunk-ao-adk/tests/test_retriever.py similarity index 96% rename from galileo-adk/tests/test_retriever.py rename to splunk-ao-adk/tests/test_retriever.py index 8cdf72e9..9b915030 100644 --- a/galileo-adk/tests/test_retriever.py +++ b/splunk-ao-adk/tests/test_retriever.py @@ -7,8 +7,8 @@ import pytest -from galileo_adk.decorator import splunk_ao_retriever -from galileo_adk.observer import SplunkAOObserver +from splunk_ao_adk.decorator import splunk_ao_retriever +from splunk_ao_adk.observer import SplunkAOObserver from .mocks import MockTool, MockToolContext @@ -89,7 +89,7 @@ def test_regular_tool_is_not_retriever(self, observer: SplunkAOObserver) -> None # Then: it is not detected as a retriever assert result is False - @patch("galileo_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) + @patch("splunk_ao_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) 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") @@ -100,7 +100,7 @@ def test_base_retrieval_tool_instance_is_detected(self, observer: SplunkAOObserv # Then: it is detected as a retriever assert result is True - @patch("galileo_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) + @patch("splunk_ao_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) 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") @@ -138,7 +138,7 @@ def my_calculator(expression: str) -> str: # Then: it is not detected as a retriever assert result is False - @patch("galileo_adk.observer._BaseRetrievalTool", None) + @patch("splunk_ao_adk.observer._BaseRetrievalTool", 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") @@ -265,7 +265,7 @@ def test_on_tool_start_returns_uuid(self, observer: SplunkAOObserver) -> None: # Then: a valid UUID is returned assert isinstance(run_id, UUID) - @patch("galileo_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) + @patch("splunk_ao_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) 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") diff --git a/galileo-adk/tests/test_span_manager.py b/splunk-ao-adk/tests/test_span_manager.py similarity index 99% rename from galileo-adk/tests/test_span_manager.py rename to splunk-ao-adk/tests/test_span_manager.py index 26367881..27eda6c2 100644 --- a/galileo-adk/tests/test_span_manager.py +++ b/splunk-ao-adk/tests/test_span_manager.py @@ -5,7 +5,7 @@ import pytest -from galileo_adk.span_manager import INTEGRATION_TAG, SpanManager +from splunk_ao_adk.span_manager import INTEGRATION_TAG, SpanManager class TestSpanManagerRunSpans: diff --git a/galileo-adk/tests/test_span_tracker.py b/splunk-ao-adk/tests/test_span_tracker.py similarity index 99% rename from galileo-adk/tests/test_span_tracker.py rename to splunk-ao-adk/tests/test_span_tracker.py index 07bb052d..064355ed 100644 --- a/galileo-adk/tests/test_span_tracker.py +++ b/splunk-ao-adk/tests/test_span_tracker.py @@ -4,7 +4,7 @@ from unittest.mock import MagicMock from uuid import uuid4 -from galileo_adk.span_tracker import SpanTracker +from splunk_ao_adk.span_tracker import SpanTracker class TestSpanTrackerRuns: diff --git a/galileo-adk/tests/test_status_code.py b/splunk-ao-adk/tests/test_status_code.py similarity index 98% rename from galileo-adk/tests/test_status_code.py rename to splunk-ao-adk/tests/test_status_code.py index 176af877..f119b245 100644 --- a/galileo-adk/tests/test_status_code.py +++ b/splunk-ao-adk/tests/test_status_code.py @@ -2,8 +2,8 @@ import pytest -from galileo_adk import GalileoADKPlugin -from galileo_adk.plugin import _extract_status_code, _is_http_status_code +from splunk_ao_adk import GalileoADKPlugin +from splunk_ao_adk.plugin import _extract_status_code, _is_http_status_code class TestIsHttpStatusCode: diff --git a/galileo-adk/tests/test_trace_builder.py b/splunk-ao-adk/tests/test_trace_builder.py similarity index 99% rename from galileo-adk/tests/test_trace_builder.py rename to splunk-ao-adk/tests/test_trace_builder.py index 1beeac7f..c2d62299 100644 --- a/galileo-adk/tests/test_trace_builder.py +++ b/splunk-ao-adk/tests/test_trace_builder.py @@ -5,7 +5,7 @@ import pytest from splunk_ao.schema.trace import TracesIngestRequest -from galileo_adk.trace_builder import TraceBuilder +from splunk_ao_adk.trace_builder import TraceBuilder class TestTraceBuilderInit: From bcdc1a5657b47e5ed80525b48b86744997ded1f8 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Thu, 18 Jun 2026 09:58:16 -0700 Subject: [PATCH 04/22] fix(splunk-ao-adk): correct version ranges and restore galileo-core[testing] (PR #32 review) - Lower splunk-ao runtime dep floor: >=2.0.0 -> >=0.1.0,<1.0.0 to match actual package version (was unsatisfiable from PyPI) - Lower splunk-ao[otel] dev dep floor: >=2.0.0 -> >=0.1.0,<1.0.0 - Re-add galileo-core[testing]>=3.82.0 dev dep: provides the mock_request fixture (autouse in conftest.py and mocks.py) that the test suite requires; splunk-ao[otel] alone does not include the testing extras Co-authored-by: Cursor --- splunk-ao-adk/pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/splunk-ao-adk/pyproject.toml b/splunk-ao-adk/pyproject.toml index 07fcc65a..33a05cca 100644 --- a/splunk-ao-adk/pyproject.toml +++ b/splunk-ao-adk/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "splunk-ao>=2.0.0,<3.0.0", + "splunk-ao>=0.1.0,<1.0.0", "google-adk>=1.14.1,<2.0.0", ] @@ -137,7 +137,8 @@ dev = [ "ruff>=0.12.3", "mypy>=1.16.0", "coverage>=7.9.2", - "splunk-ao[otel]>=2.0.0,<3.0.0", + "splunk-ao[otel]>=0.1.0,<1.0.0", + "galileo-core[testing]>=3.82.0", ] [build-system] From 5c7b5ff288a2c3ddfbf540638ad26644ed7c6b60 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 11:37:52 -0700 Subject: [PATCH 05/22] ci: add GitHub Actions workflow to test splunk-ao-adk Adds test-splunk-ao-adk.yaml to run unit tests on pull requests and pushes to main that touch the splunk-ao-adk package. Key differences from the galileo-python reference workflow: - Removes galileo-version min/latest matrix; splunk-ao is always installed as a local editable from the monorepo root via [tool.uv] sources in pyproject.toml - Python matrix: 3.10, 3.12, 3.13, 3.14 (drops 3.11, adds 3.14) - pytest pythonpath ".." (repo root) makes test_support importable without extra steps - Coverage upload pinned to Python 3.13 (replaces 3.11 + latest axis) Co-authored-by: Cursor --- .github/workflows/test-splunk-ao-adk.yaml | 67 +++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/test-splunk-ao-adk.yaml diff --git a/.github/workflows/test-splunk-ao-adk.yaml b/.github/workflows/test-splunk-ao-adk.yaml new file mode 100644 index 00000000..29ee3996 --- /dev/null +++ b/.github/workflows/test-splunk-ao-adk.yaml @@ -0,0 +1,67 @@ +name: Test splunk-ao-adk + +on: + push: + branches: [main] + paths: + - 'splunk-ao-adk/**' + - '.github/workflows/test-splunk-ao-adk.yaml' + pull_request: + paths: + - 'splunk-ao-adk/**' + - '.github/workflows/test-splunk-ao-adk.yaml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + test: + name: Python ${{ matrix.python-version }} + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.12", "3.13", "3.14"] + + runs-on: ubuntu-latest + # Hard cap per matrix job to bail out fast on real hangs. + timeout-minutes: 30 + + defaults: + run: + working-directory: splunk-ao-adk + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + # uv sources in pyproject.toml pins splunk-ao to the local monorepo root + # as an editable install, so this always tests against the latest local code. + # pytest pythonpath includes ".." (the repo root) so test_support is importable. + - name: Install dependencies + run: uv sync --dev + + - name: Show installed versions + run: uv pip list | grep -E "^(splunk-ao|google-adk)" + + - name: Run type check + run: uv run mypy src/ + + - name: Run tests + run: uv run pytest --cov=splunk_ao_adk --cov-report=xml + + - name: Upload coverage + if: matrix.python-version == '3.13' + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: splunk-ao-adk + files: splunk-ao-adk/coverage.xml From 143c00ba7402e03e65fd162aaa93c5bd2d37e46e Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 11:51:25 -0700 Subject: [PATCH 06/22] fix(ci): use uv run pip list to avoid VIRTUAL_ENV override in show-versions step Co-authored-by: Cursor --- .github/workflows/test-splunk-ao-adk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-splunk-ao-adk.yaml b/.github/workflows/test-splunk-ao-adk.yaml index 29ee3996..c6976e14 100644 --- a/.github/workflows/test-splunk-ao-adk.yaml +++ b/.github/workflows/test-splunk-ao-adk.yaml @@ -50,7 +50,7 @@ jobs: run: uv sync --dev - name: Show installed versions - run: uv pip list | grep -E "^(splunk-ao|google-adk)" + run: uv run pip list | grep -E "^(splunk-ao|google-adk)" - name: Run type check run: uv run mypy src/ From b8c1baf790bd53ada2487d4e3fef855f43517cce Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 11:54:29 -0700 Subject: [PATCH 07/22] fix(ci): use pip show instead of pip list grep for installed versions step Co-authored-by: Cursor --- .github/workflows/test-splunk-ao-adk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-splunk-ao-adk.yaml b/.github/workflows/test-splunk-ao-adk.yaml index c6976e14..f304dd86 100644 --- a/.github/workflows/test-splunk-ao-adk.yaml +++ b/.github/workflows/test-splunk-ao-adk.yaml @@ -50,7 +50,7 @@ jobs: run: uv sync --dev - name: Show installed versions - run: uv run pip list | grep -E "^(splunk-ao|google-adk)" + run: uv run pip show splunk-ao google-adk - name: Run type check run: uv run mypy src/ From 3f6e4d90103e48817e458acc20e2a37f268bb678 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 12:06:54 -0700 Subject: [PATCH 08/22] fix(ci): use uv tree --depth 1 for show-versions step to avoid VIRTUAL_ENV pip routing Co-authored-by: Cursor --- .github/workflows/test-splunk-ao-adk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-splunk-ao-adk.yaml b/.github/workflows/test-splunk-ao-adk.yaml index f304dd86..6ab961c0 100644 --- a/.github/workflows/test-splunk-ao-adk.yaml +++ b/.github/workflows/test-splunk-ao-adk.yaml @@ -50,7 +50,7 @@ jobs: run: uv sync --dev - name: Show installed versions - run: uv run pip show splunk-ao google-adk + run: uv tree --depth 1 - name: Run type check run: uv run mypy src/ From ca6ce24dead885b15e4a645a9a56d23a0184d506 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 12:10:10 -0700 Subject: [PATCH 09/22] fix(dev): pin PyPI as default uv index to bypass Splunk Artifactory in global uv config Co-authored-by: Cursor --- splunk-ao-adk/pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/splunk-ao-adk/pyproject.toml b/splunk-ao-adk/pyproject.toml index 33a05cca..06241119 100644 --- a/splunk-ao-adk/pyproject.toml +++ b/splunk-ao-adk/pyproject.toml @@ -26,6 +26,12 @@ Repository = "https://github.com/splunk/splunk-ao-python" [tool.uv] sources = { "splunk-ao" = { path = "../", editable = true } } +# Ensure all packages resolve from PyPI, overriding any corporate index in the +# global uv config that only mirrors Splunk-internal packages. +[[tool.uv.index]] +url = "https://pypi.org/simple" +default = true + [tool.hatch.build.targets.wheel] packages = ["src/splunk_ao_adk"] From e36989577d367e5ceb8c19bbf40a56e08569aff6 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 14:13:58 -0700 Subject: [PATCH 10/22] fix(dev): add uv.toml to splunk-ao-adk to replace global Artifactory index Project-level uv.toml with [[index]] replaces (not merges with) the list in ~/.config/uv/uv.toml, so the Splunk Artifactory mirror that does not carry all public PyPI packages is bypassed for this subpackage. Also fix the ci-tests workflow path trigger to reference the new filename, and revert the ineffective [[tool.uv.index]] approach from pyproject.toml. Co-authored-by: Cursor --- ...plunk-ao-adk.yaml => ci-tests-splunk-ao-adk.yaml} | 12 ++---------- splunk-ao-adk/pyproject.toml | 6 ------ splunk-ao-adk/uv.toml | 6 ++++++ 3 files changed, 8 insertions(+), 16 deletions(-) rename .github/workflows/{test-splunk-ao-adk.yaml => ci-tests-splunk-ao-adk.yaml} (81%) create mode 100644 splunk-ao-adk/uv.toml diff --git a/.github/workflows/test-splunk-ao-adk.yaml b/.github/workflows/ci-tests-splunk-ao-adk.yaml similarity index 81% rename from .github/workflows/test-splunk-ao-adk.yaml rename to .github/workflows/ci-tests-splunk-ao-adk.yaml index 6ab961c0..b39034e0 100644 --- a/.github/workflows/test-splunk-ao-adk.yaml +++ b/.github/workflows/ci-tests-splunk-ao-adk.yaml @@ -5,11 +5,11 @@ on: branches: [main] paths: - 'splunk-ao-adk/**' - - '.github/workflows/test-splunk-ao-adk.yaml' + - '.github/workflows/ci-tests-splunk-ao-adk.yaml' pull_request: paths: - 'splunk-ao-adk/**' - - '.github/workflows/test-splunk-ao-adk.yaml' + - '.github/workflows/ci-tests-splunk-ao-adk.yaml' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -57,11 +57,3 @@ jobs: - name: Run tests run: uv run pytest --cov=splunk_ao_adk --cov-report=xml - - - name: Upload coverage - if: matrix.python-version == '3.13' - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - flags: splunk-ao-adk - files: splunk-ao-adk/coverage.xml diff --git a/splunk-ao-adk/pyproject.toml b/splunk-ao-adk/pyproject.toml index 06241119..33a05cca 100644 --- a/splunk-ao-adk/pyproject.toml +++ b/splunk-ao-adk/pyproject.toml @@ -26,12 +26,6 @@ Repository = "https://github.com/splunk/splunk-ao-python" [tool.uv] sources = { "splunk-ao" = { path = "../", editable = true } } -# Ensure all packages resolve from PyPI, overriding any corporate index in the -# global uv config that only mirrors Splunk-internal packages. -[[tool.uv.index]] -url = "https://pypi.org/simple" -default = true - [tool.hatch.build.targets.wheel] packages = ["src/splunk_ao_adk"] diff --git a/splunk-ao-adk/uv.toml b/splunk-ao-adk/uv.toml new file mode 100644 index 00000000..2f93c2fe --- /dev/null +++ b/splunk-ao-adk/uv.toml @@ -0,0 +1,6 @@ +# Project-level uv config. List settings here REPLACE (not extend) the +# global ~/.config/uv/uv.toml, so [[index]] below overrides any corporate +# Artifactory mirror configured globally that does not mirror all PyPI packages. +[[index]] +url = "https://pypi.org/simple" +default = true From c9eda2298fb2e56ee9cd52c3c25af4ea109d47b6 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 14:21:58 -0700 Subject: [PATCH 11/22] fix(ci): update python matrix --- .github/workflows/ci-tests-splunk-ao-adk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests-splunk-ao-adk.yaml b/.github/workflows/ci-tests-splunk-ao-adk.yaml index b39034e0..60fb81c6 100644 --- a/.github/workflows/ci-tests-splunk-ao-adk.yaml +++ b/.github/workflows/ci-tests-splunk-ao-adk.yaml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.12", "3.13", "3.14"] + python-version: ["3.11", "3.12", "3.13", "3.14"] runs-on: ubuntu-latest # Hard cap per matrix job to bail out fast on real hangs. From 0adcca2609c1327243693236fef718ab543316e4 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 15:34:26 -0700 Subject: [PATCH 12/22] fix(dev): rename missed galileo logger --- splunk-ao-adk/src/splunk_ao_adk/observer.py | 8 ++++---- splunk-ao-adk/tests/test_callback.py | 2 +- splunk-ao-adk/tests/test_observer.py | 10 +++++----- splunk-ao-adk/tests/test_status_code.py | 4 ++-- splunk-ao-adk/uv.toml | 6 ------ 5 files changed, 12 insertions(+), 18 deletions(-) delete mode 100644 splunk-ao-adk/uv.toml diff --git a/splunk-ao-adk/src/splunk_ao_adk/observer.py b/splunk-ao-adk/src/splunk_ao_adk/observer.py index d6684ce5..b21bd43f 100644 --- a/splunk-ao-adk/src/splunk_ao_adk/observer.py +++ b/splunk-ao-adk/src/splunk_ao_adk/observer.py @@ -149,16 +149,16 @@ def __init__( trace_builder = TraceBuilder(ingestion_hook=ingestion_hook) self._trace_builder = trace_builder self._handler = SplunkAOBaseHandler( - galileo_logger=trace_builder, # type: ignore[arg-type] + splunk_ao_logger=trace_builder, # type: ignore[arg-type] start_new_trace=True, flush_on_chain_end=True, integration="google_adk", ) else: self._trace_builder = None - galileo_logger = splunk_ao_context.get_logger_instance(project=project, log_stream=log_stream) + splunk_ao_logger = splunk_ao_context.get_logger_instance(project=project, log_stream=log_stream) self._handler = SplunkAOBaseHandler( - galileo_logger=galileo_logger, + splunk_ao_logger=splunk_ao_logger, start_new_trace=True, flush_on_chain_end=True, integration="google_adk", @@ -291,7 +291,7 @@ def update_session_if_changed(self, adk_session_id: str, is_sub_invocation: bool return # Normal mode: create/find session on backend - logger = self._handler._galileo_logger + logger = self._handler._splunk_ao_logger previous_session_id = logger.session_id logger.start_session( name=adk_session_id, diff --git a/splunk-ao-adk/tests/test_callback.py b/splunk-ao-adk/tests/test_callback.py index 2021dd84..a68c3682 100644 --- a/splunk-ao-adk/tests/test_callback.py +++ b/splunk-ao-adk/tests/test_callback.py @@ -133,7 +133,7 @@ def test_initialization_with_project_and_log_stream(self) -> None: project="test-project", log_stream="test-stream", ) - assert callback._handler._galileo_logger == mock_logger + assert callback._handler._splunk_ao_logger == mock_logger def test_initialization_defaults(self) -> None: callback = SplunkAOADKCallback(ingestion_hook=lambda r: None) diff --git a/splunk-ao-adk/tests/test_observer.py b/splunk-ao-adk/tests/test_observer.py index 25c63367..2caee853 100644 --- a/splunk-ao-adk/tests/test_observer.py +++ b/splunk-ao-adk/tests/test_observer.py @@ -216,7 +216,7 @@ def test_hook_mode_sets_session_external_id_without_backend_call(self) -> None: # Given: an observer in hook mode captured_requests: list = [] observer = SplunkAOObserver(ingestion_hook=lambda r: captured_requests.append(r)) - logger = observer._handler._galileo_logger + logger = observer._handler._splunk_ao_logger # When: updating session with an ADK session ID observer.update_session_if_changed("adk-session-123") @@ -229,7 +229,7 @@ def test_hook_mode_updates_external_id_on_session_change(self) -> None: # Given: an observer in hook mode with an existing session observer = SplunkAOObserver(ingestion_hook=lambda r: None) observer.update_session_if_changed("session-1") - logger = observer._handler._galileo_logger + logger = observer._handler._splunk_ao_logger # When: the ADK session changes observer.update_session_if_changed("session-2") @@ -241,7 +241,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 = SplunkAOObserver(ingestion_hook=lambda r: None) - logger = observer._handler._galileo_logger + logger = observer._handler._splunk_ao_logger # When: updating with "unknown" session ID observer.update_session_if_changed("unknown") @@ -254,7 +254,7 @@ def test_hook_mode_ignores_duplicate_session_id(self) -> None: # Given: an observer in hook mode with an existing session observer = SplunkAOObserver(ingestion_hook=lambda r: None) observer.update_session_if_changed("session-1") - logger = observer._handler._galileo_logger + logger = observer._handler._splunk_ao_logger original_external_id = logger._session_external_id # When: updating with the same session ID @@ -267,7 +267,7 @@ def test_hook_mode_preserves_parent_session_for_sub_invocations(self) -> None: # Given: an observer in hook mode with an existing parent session observer = SplunkAOObserver(ingestion_hook=lambda r: None) observer.update_session_if_changed("parent-session") - logger = observer._handler._galileo_logger + logger = observer._handler._splunk_ao_logger # When: a sub-invocation tries to change the session observer.update_session_if_changed("sub-invocation-session", is_sub_invocation=True) diff --git a/splunk-ao-adk/tests/test_status_code.py b/splunk-ao-adk/tests/test_status_code.py index f119b245..13874859 100644 --- a/splunk-ao-adk/tests/test_status_code.py +++ b/splunk-ao-adk/tests/test_status_code.py @@ -2,7 +2,7 @@ import pytest -from splunk_ao_adk import GalileoADKPlugin +from splunk_ao_adk import SplunkAOADKPlugin from splunk_ao_adk.plugin import _extract_status_code, _is_http_status_code @@ -139,7 +139,7 @@ class TestFatalErrorClassification: @pytest.fixture def plugin(self): - return GalileoADKPlugin(ingestion_hook=lambda r: None) + return SplunkAOADKPlugin(ingestion_hook=lambda r: None) def test_401_is_fatal(self, plugin) -> None: """401 Unauthorized is a fatal error.""" diff --git a/splunk-ao-adk/uv.toml b/splunk-ao-adk/uv.toml deleted file mode 100644 index 2f93c2fe..00000000 --- a/splunk-ao-adk/uv.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Project-level uv config. List settings here REPLACE (not extend) the -# global ~/.config/uv/uv.toml, so [[index]] below overrides any corporate -# Artifactory mirror configured globally that does not mirror all PyPI packages. -[[index]] -url = "https://pypi.org/simple" -default = true From 653bc4da8b29de5fc162e1184fe837467959669d Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 19:29:13 -0700 Subject: [PATCH 13/22] fix(ci): Add cache list dependencies --- .github/workflows/ci-tests-splunk-ao-adk.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests-splunk-ao-adk.yaml b/.github/workflows/ci-tests-splunk-ao-adk.yaml index 60fb81c6..b64c889a 100644 --- a/.github/workflows/ci-tests-splunk-ao-adk.yaml +++ b/.github/workflows/ci-tests-splunk-ao-adk.yaml @@ -36,9 +36,12 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Python ${{ matrix.python-version }} + id: setup-python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: + cache: "uv" python-version: ${{ matrix.python-version }} + cache-dependency-path: "uv.lock" - name: Install uv uses: astral-sh/setup-uv@v5 @@ -50,7 +53,7 @@ jobs: run: uv sync --dev - name: Show installed versions - run: uv tree --depth 1 + run: uv pip list | grep -E "^(galileo|google-adk|splunk-ao)" - name: Run type check run: uv run mypy src/ From abf7d4e556329dc72bd093fefcfce0402a44eb8e Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 19:32:51 -0700 Subject: [PATCH 14/22] fix(ci): cache for uv not supported --- .github/workflows/ci-tests-splunk-ao-adk.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci-tests-splunk-ao-adk.yaml b/.github/workflows/ci-tests-splunk-ao-adk.yaml index b64c889a..9cde7d26 100644 --- a/.github/workflows/ci-tests-splunk-ao-adk.yaml +++ b/.github/workflows/ci-tests-splunk-ao-adk.yaml @@ -36,12 +36,9 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Python ${{ matrix.python-version }} - id: setup-python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: - cache: "uv" python-version: ${{ matrix.python-version }} - cache-dependency-path: "uv.lock" - name: Install uv uses: astral-sh/setup-uv@v5 From 18c0829fb45464279cac022c607157c895748191 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 19:50:00 -0700 Subject: [PATCH 15/22] fix(ci): point uv cache-dependency-glob to action's uv.lock for proper cache invalidation --- .github/workflows/ci-tests-splunk-ao-adk.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests-splunk-ao-adk.yaml b/.github/workflows/ci-tests-splunk-ao-adk.yaml index 9cde7d26..a87e4b17 100644 --- a/.github/workflows/ci-tests-splunk-ao-adk.yaml +++ b/.github/workflows/ci-tests-splunk-ao-adk.yaml @@ -41,7 +41,9 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + with: + cache-dependency-glob: ${{ github.action_path }}/uv.lock # uv sources in pyproject.toml pins splunk-ao to the local monorepo root # as an editable install, so this always tests against the latest local code. From bd8ea35dbd4b084f63cde4aa2afb1b5ca24a1776 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 20:11:20 -0700 Subject: [PATCH 16/22] fix(ci): point uv cache-dependency-glob to action's pyproject.toml --- .github/workflows/ci-tests-splunk-ao-adk.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests-splunk-ao-adk.yaml b/.github/workflows/ci-tests-splunk-ao-adk.yaml index a87e4b17..c60d924f 100644 --- a/.github/workflows/ci-tests-splunk-ao-adk.yaml +++ b/.github/workflows/ci-tests-splunk-ao-adk.yaml @@ -43,7 +43,8 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 with: - cache-dependency-glob: ${{ github.action_path }}/uv.lock + enable-cache: true + cache-dependency-glob: "**/pyproject.toml" # uv sources in pyproject.toml pins splunk-ao to the local monorepo root # as an editable install, so this always tests against the latest local code. From 7d50a76d266a0b0d708cfaa4c085f8ee5156fcdb Mon Sep 17 00:00:00 2001 From: adityamehra Date: Tue, 23 Jun 2026 20:14:16 -0700 Subject: [PATCH 17/22] fix(ci): point uv cache-dependency-glob to action's pyproject.toml --- .github/workflows/ci-tests-splunk-ao-adk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests-splunk-ao-adk.yaml b/.github/workflows/ci-tests-splunk-ao-adk.yaml index c60d924f..43985065 100644 --- a/.github/workflows/ci-tests-splunk-ao-adk.yaml +++ b/.github/workflows/ci-tests-splunk-ao-adk.yaml @@ -44,7 +44,7 @@ jobs: uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 with: enable-cache: true - cache-dependency-glob: "**/pyproject.toml" + cache-dependency-glob: "**/splunk-ao-adk/pyproject.toml" # uv sources in pyproject.toml pins splunk-ao to the local monorepo root # as an editable install, so this always tests against the latest local code. From 15dc3cf4818f02e65c3e277ef146a074331e4246 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Thu, 25 Jun 2026 11:45:45 -0700 Subject: [PATCH 18/22] docs(splunk-ao-adk): fix GalileoLogger import and rebrand CONTRIBUTING.md (PR #32 review) README.md: - Fix broken import: GalileoLogger -> SplunkAOLogger (splunk_ao no longer exports GalileoLogger; it exports SplunkAOLogger) CONTRIBUTING.md: - Heading and intro: galileo-adk -> splunk-ao-adk - Monorepo name/link: galileo-python/rungalileo -> splunk-ao-python/splunk - Directory tree: galileo-python/ -> splunk-ao-python/, galileo-adk/ -> splunk-ao-adk/ - Architecture diagram: GalileoADKPlugin -> SplunkAOADKPlugin, GalileoADKCallback -> SplunkAOADKCallback, GalileoObserver -> SplunkAOObserver, GalileoLogger -> SplunkAOLogger, GalileoBaseHandler -> SplunkAOBaseHandler, Galileo Backend -> Splunk AO Backend - TraceBuilder section: GalileoLogger -> SplunkAOLogger throughout - Setup: cd galileo-adk -> cd splunk-ao-adk, galileo editable -> splunk-ao - Release sequence: galileo/galileo-adk -> splunk-ao/splunk-ao-adk, dep example updated - Workflow name: Release galileo-adk -> Release splunk-ao-adk - Links: rungalileo/galileo-python -> splunk/splunk-ao-python, docs.rungalileo.io -> docs.splunk.com/observability Co-authored-by: Cursor --- splunk-ao-adk/CONTRIBUTING.md | 56 +++++++++++++++++------------------ splunk-ao-adk/README.md | 4 +-- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/splunk-ao-adk/CONTRIBUTING.md b/splunk-ao-adk/CONTRIBUTING.md index cbfac454..d4464e02 100644 --- a/splunk-ao-adk/CONTRIBUTING.md +++ b/splunk-ao-adk/CONTRIBUTING.md @@ -1,15 +1,15 @@ -# Contributing to galileo-adk +# Contributing to splunk-ao-adk -Thank you for your interest in contributing to galileo-adk! +Thank you for your interest in contributing to splunk-ao-adk! ## Project Structure -This package is part of the [galileo-python](https://github.com/rungalileo/galileo-python) monorepo: +This package is part of the [splunk-ao-python](https://github.com/splunk/splunk-ao-python) monorepo: ``` -galileo-python/ -├── src/splunk_ao/ ← Main Galileo SDK -└── galileo-adk/ +splunk-ao-python/ +├── src/splunk_ao/ ← Main Splunk AO SDK +└── splunk-ao-adk/ ├── src/splunk_ao_adk/ ├── tests/ ├── pyproject.toml @@ -23,7 +23,7 @@ galileo-python/ │ Google ADK │ │ │ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ -│ │ GalileoADKPlugin │ │ GalileoADKCallback │ │ +│ │ SplunkAOADKPlugin │ │ SplunkAOADKCallback │ │ │ │ (Runner plugins) │ │ (Agent callbacks) │ │ │ │ │ │ │ │ │ │ on_user_msg │ │ before/after_agent │ │ @@ -40,7 +40,7 @@ galileo-python/ │ └─────────┬──────────┘ │ │ ▼ │ │ ┌────────────────────┐ │ -│ │ GalileoObserver │ ← Shared logic + metadata │ +│ │ SplunkAOObserver │ ← Shared logic + metadata │ │ └─────────┬──────────┘ │ │ ▼ │ │ ┌────────────────────┐ │ @@ -48,43 +48,43 @@ galileo-python/ │ └─────────┬──────────┘ │ │ ▼ │ │ ┌────────────────────┐ │ -│ │ GalileoBaseHandler │ ← From galileo lib │ +│ │ SplunkAOBaseHandler│ ← From splunk-ao lib │ │ └─────────┬──────────┘ │ └────────────────────────────┼─────────────────────────────────────────────┘ ▼ ┌────────────────────────┐ - │ Galileo Backend │ + │ Splunk AO Backend │ │ (or ingestion_hook) │ └────────────────────────┘ ``` -### TraceBuilder vs GalileoLogger +### TraceBuilder vs SplunkAOLogger The plugin supports two modes of operation: | Mode | Logger | When to Use | |------|--------|-------------| -| **Normal** | `GalileoLogger` | Traces sent directly to Galileo backend | +| **Normal** | `SplunkAOLogger` | Traces sent directly to Splunk AO backend | | **Hook** | `TraceBuilder` | Traces passed to custom `ingestion_hook` callback | -**TraceBuilder** is a lightweight alternative to `GalileoLogger` that: +**TraceBuilder** is a lightweight alternative to `SplunkAOLogger` that: - Implements the same trace-building interface (spans, traces, metadata) -- Requires no Galileo credentials or backend connectivity +- Requires no Splunk AO credentials or backend connectivity - Passes completed traces to the `ingestion_hook` consumer - Delegates session management to the hook consumer via `session_external_id` ``` Normal mode: Hook mode: ┌─────────────────┐ ┌─────────────────┐ -│ GalileoObserver │ │ GalileoObserver │ +│ SplunkAOObserver│ │ SplunkAOObserver│ └────────┬────────┘ └────────┬────────┘ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ -│ GalileoLogger │ │ TraceBuilder │ +│ SplunkAOLogger │ │ TraceBuilder │ └────────┬────────┘ └────────┬────────┘ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ -│ Galileo Backend │ │ ingestion_hook │ +│ Splunk AO Backend│ │ ingestion_hook │ └─────────────────┘ └─────────────────┘ ``` @@ -108,11 +108,11 @@ invocation [agent_name] ← Run span (workflow wrapper) ### Setup ```bash -cd galileo-adk +cd splunk-ao-adk uv sync --dev ``` -This installs `galileo` in **editable mode** from `../src/splunk_ao/`. Changes to either package are immediately available without reinstalling. +This installs `splunk-ao` in **editable mode** from `../src/splunk_ao/`. Changes to either package are immediately available without reinstalling. ### Running Tests @@ -152,23 +152,23 @@ The `[tool.uv].sources` configuration is **dev-only** and ignored when building ### Release Sequence -When both `galileo` and `galileo-adk` have changes: +When both `splunk-ao` and `splunk-ao-adk` have changes: -1. Merge changes to `galileo` (main SDK) -2. Wait for `galileo` to publish to PyPI -3. Update `galileo-adk/pyproject.toml` dependency constraint if needed: +1. Merge changes to `splunk-ao` (main SDK) +2. Wait for `splunk-ao` to publish to PyPI +3. Update `splunk-ao-adk/pyproject.toml` dependency constraint if needed: ```toml dependencies = [ - "galileo>=1.45.0,<2.0.0", # bump minimum version + "splunk-ao>=0.1.0,<1.0.0", # bump minimum version ] ``` -4. Merge and publish `galileo-adk` +4. Merge and publish `splunk-ao-adk` ### Triggering a Release Releases are triggered via GitHub Actions: -1. Go to **Actions** > **Release galileo-adk** +1. Go to **Actions** > **Release splunk-ao-adk** 2. Click **Run workflow** 3. Either leave version empty for automatic semantic versioning, or specify a version (e.g., `1.0.0`, `1.1.0-beta.1`) 4. Click **Run workflow** @@ -193,5 +193,5 @@ docs(adk): update configuration examples ## Questions? -- Open an issue on [GitHub](https://github.com/rungalileo/galileo-python/issues) -- Check the [Galileo Documentation](https://docs.rungalileo.io/) +- Open an issue on [GitHub](https://github.com/splunk/splunk-ao-python/issues) +- Check the [Splunk Observability Documentation](https://docs.splunk.com/observability/) diff --git a/splunk-ao-adk/README.md b/splunk-ao-adk/README.md index 0c4e9e34..41f85cb6 100644 --- a/splunk-ao-adk/README.md +++ b/splunk-ao-adk/README.md @@ -186,13 +186,13 @@ Intercept traces for custom processing before forwarding to Splunk AO: ```python import asyncio import os -from splunk_ao import GalileoLogger +from splunk_ao import SplunkAOLogger from splunk_ao_adk import SplunkAOADKPlugin from google.adk.runners import Runner from google.adk.agents import LlmAgent from google.genai import types -logger = GalileoLogger( +logger = SplunkAOLogger( project=os.getenv("SPLUNK_AO_PROJECT", "my-project"), log_stream=os.getenv("SPLUNK_AO_LOG_STREAM", "dev"), ) From 58ac9c4d08bdfbef3c33da5dab04d9cc17b1cf0b Mon Sep 17 00:00:00 2001 From: adityamehra Date: Thu, 25 Jun 2026 12:25:25 -0700 Subject: [PATCH 19/22] fix(docs): Use galileo docs --- splunk-ao-adk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splunk-ao-adk/README.md b/splunk-ao-adk/README.md index 41f85cb6..5a13f2ad 100644 --- a/splunk-ao-adk/README.md +++ b/splunk-ao-adk/README.md @@ -230,7 +230,7 @@ if __name__ == "__main__": ## Resources -- [Splunk AO Documentation](https://docs.splunk.com/) +- [Splunk AO Documentation](https://docs.rungalileo.io/) - [Google ADK Documentation](https://google.github.io/adk-docs/) ## License From 28f65b262fe441c20d87dabf63f22dc498943575 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Thu, 25 Jun 2026 12:36:07 -0700 Subject: [PATCH 20/22] test(config): add unit tests for _bridge_env_vars() including SPLUNK_AO_API_URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers the three key behaviours of SplunkAOConfig._bridge_env_vars(): 1. Propagation — every SPLUNK_AO_* key is copied to its GALILEO_* counterpart when that counterpart is absent (parametrized over all 13 bridge pairs). 2. Non-overwrite — an explicit GALILEO_* value already present in the environment is never overwritten by the bridge (parametrized over all pairs). 3. Absence — when a SPLUNK_AO_* source key is absent, no spurious GALILEO_* entry is introduced. 4. Regression — dedicated test for the SPLUNK_AO_API_URL -> GALILEO_API_URL entry added as a fix for self-hosted deployments. Co-authored-by: Cursor --- tests/test_config.py | 82 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/test_config.py b/tests/test_config.py index 242fe7a9..d3682439 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,3 +1,4 @@ +import os from unittest.mock import MagicMock, patch import pytest @@ -22,6 +23,87 @@ def _clear_auth_env(monkeypatch) -> None: monkeypatch.delenv(var, raising=False) +# --------------------------------------------------------------------------- +# _bridge_env_vars tests +# --------------------------------------------------------------------------- + +# Every (SPLUNK_AO_*, GALILEO_*) pair defined in _bridge_env_vars. +_ALL_BRIDGE_PAIRS = [ + ("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"), + ("SPLUNK_AO_LOG_STREAM", "GALILEO_LOG_STREAM"), + ("SPLUNK_AO_LOG_STREAM_ID", "GALILEO_LOG_STREAM_ID"), + ("SPLUNK_AO_JWT_TOKEN", "GALILEO_JWT_TOKEN"), + ("SPLUNK_AO_SSO_ID_TOKEN", "GALILEO_SSO_ID_TOKEN"), + ("SPLUNK_AO_SSO_PROVIDER", "GALILEO_SSO_PROVIDER"), + ("SPLUNK_AO_USERNAME", "GALILEO_USERNAME"), + ("SPLUNK_AO_PASSWORD", "GALILEO_PASSWORD"), + ("SPLUNK_AO_MODE", "GALILEO_MODE"), +] + + +@pytest.mark.parametrize("splunk_key,galileo_key", _ALL_BRIDGE_PAIRS) +def test_bridge_env_vars_propagates_splunk_ao_to_galileo(monkeypatch, splunk_key, galileo_key) -> None: + """Each SPLUNK_AO_* value is copied to its GALILEO_* counterpart when the + GALILEO_* key is absent from the environment.""" + value = f"test-value-for-{splunk_key}" + monkeypatch.setenv(splunk_key, value) + monkeypatch.delenv(galileo_key, raising=False) + + SplunkAOConfig._bridge_env_vars() + + assert os.environ.get(galileo_key) == value, ( + f"Expected {galileo_key}={value!r} after bridging {splunk_key}" + ) + + +@pytest.mark.parametrize("splunk_key,galileo_key", _ALL_BRIDGE_PAIRS) +def test_bridge_env_vars_does_not_overwrite_existing_galileo_value(monkeypatch, splunk_key, galileo_key) -> None: + """An explicit GALILEO_* value already in the environment must win over any + SPLUNK_AO_* value — the bridge must not overwrite it.""" + existing = "pre-existing-galileo-value" + monkeypatch.setenv(galileo_key, existing) + monkeypatch.setenv(splunk_key, "should-not-overwrite") + + SplunkAOConfig._bridge_env_vars() + + assert os.environ.get(galileo_key) == existing, ( + f"Expected {galileo_key} to retain its original value; bridge must not overwrite it" + ) + + +def test_bridge_env_vars_skips_absent_splunk_ao_keys(monkeypatch) -> None: + """When a SPLUNK_AO_* key is absent, the corresponding GALILEO_* key must + not be set (no spurious entries introduced by the bridge).""" + for splunk_key, galileo_key in _ALL_BRIDGE_PAIRS: + monkeypatch.delenv(splunk_key, raising=False) + monkeypatch.delenv(galileo_key, raising=False) + + SplunkAOConfig._bridge_env_vars() + + for _, galileo_key in _ALL_BRIDGE_PAIRS: + assert galileo_key not in os.environ, ( + f"{galileo_key} must not be set when its SPLUNK_AO_* source is absent" + ) + + +def test_bridge_env_vars_api_url_specifically(monkeypatch) -> None: + """Explicit regression test for the SPLUNK_AO_API_URL -> GALILEO_API_URL + bridge entry, which was added as a fix for self-hosted deployments.""" + monkeypatch.setenv("SPLUNK_AO_API_URL", "https://my-splunk-ao-host.example.com/api") + monkeypatch.delenv("GALILEO_API_URL", raising=False) + + SplunkAOConfig._bridge_env_vars() + + assert os.environ.get("GALILEO_API_URL") == "https://my-splunk-ao-host.example.com/api" + + +# --------------------------------------------------------------------------- + + @patch("galileo_core.schemas.base_config.GalileoConfig.set_validated_api_client", new=lambda x: x) @patch("galileo_core.schemas.base_config.GalileoConfig.get_jwt_token") def test_default_console_url(mock_get_jwt_token) -> None: From 84e59ada37d0ebe2e6dc6d9b710b57efe6da45b8 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Thu, 25 Jun 2026 13:01:19 -0700 Subject: [PATCH 21/22] fix(config): remove SPLUNK_AO_API_URL bridge entry and fix test isolation SPLUNK_AO_API_URL -> GALILEO_API_URL bridge removed: - api_url is an internal galileo-core field derived automatically from console_url (replaces "console" with "api" in the hostname); there is no user-facing GALILEO_API_URL env var in the upstream rungalileo/galileo-python SDK and it is not in our own _CONFIGURATION_KEYS - Adding SPLUNK_AO_API_URL to the bridge was a mistake introduced during rebranding; self-hosted users should set SPLUNK_AO_CONSOLE_URL which is correctly bridged and from which api_url is derived Test isolation fix in test_bridge_env_vars_*: - Replace monkeypatch with patch.dict(os.environ) so that GALILEO_* keys written directly to os.environ by _bridge_env_vars() (bypassing monkeypatch tracking) are fully restored on block exit, preventing URL-validation errors from leaking into subsequent tests - Remove SPLUNK_AO_API_URL from _ALL_BRIDGE_PAIRS parametrization - Remove test_bridge_env_vars_api_url_specifically - Use valid URL strings for URL-type keys (CONSOLE_URL) to avoid Pydantic validation errors if a value does leak - Introduce _val() helper for per-key test values Co-authored-by: Cursor --- src/splunk_ao/config.py | 1 - tests/test_config.py | 87 +++++++++++++++++++++-------------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/splunk_ao/config.py b/src/splunk_ao/config.py index c2814757..7e1fd778 100644 --- a/src/splunk_ao/config.py +++ b/src/splunk_ao/config.py @@ -44,7 +44,6 @@ 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"), diff --git a/tests/test_config.py b/tests/test_config.py index d3682439..104a5a11 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -28,9 +28,11 @@ def _clear_auth_env(monkeypatch) -> None: # --------------------------------------------------------------------------- # Every (SPLUNK_AO_*, GALILEO_*) pair defined in _bridge_env_vars. +# Note: SPLUNK_AO_API_URL is intentionally excluded — api_url is an internal +# field derived automatically from console_url in galileo-core; there is no +# user-facing GALILEO_API_URL env var in the upstream SDK. _ALL_BRIDGE_PAIRS = [ ("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"), @@ -44,61 +46,60 @@ def _clear_auth_env(monkeypatch) -> None: ("SPLUNK_AO_MODE", "GALILEO_MODE"), ] +# Safe test values per key — URL keys must be valid URLs to avoid leaking +# an invalid GALILEO_* URL into the shared os.environ and breaking other tests. +_TEST_VALUE: dict[str, str] = { + "SPLUNK_AO_CONSOLE_URL": "https://splunk-ao-test.example.com", + "GALILEO_CONSOLE_URL": "https://galileo-test.example.com", +} + + +def _val(key: str) -> str: + return _TEST_VALUE.get(key, f"test-{key.lower().replace('_', '-')}") + @pytest.mark.parametrize("splunk_key,galileo_key", _ALL_BRIDGE_PAIRS) -def test_bridge_env_vars_propagates_splunk_ao_to_galileo(monkeypatch, splunk_key, galileo_key) -> None: +def test_bridge_env_vars_propagates_splunk_ao_to_galileo(splunk_key, galileo_key) -> None: """Each SPLUNK_AO_* value is copied to its GALILEO_* counterpart when the - GALILEO_* key is absent from the environment.""" - value = f"test-value-for-{splunk_key}" - monkeypatch.setenv(splunk_key, value) - monkeypatch.delenv(galileo_key, raising=False) - - SplunkAOConfig._bridge_env_vars() + GALILEO_* key is absent from the environment. - assert os.environ.get(galileo_key) == value, ( - f"Expected {galileo_key}={value!r} after bridging {splunk_key}" - ) + Uses patch.dict so that any GALILEO_* key written directly to os.environ by + _bridge_env_vars() (which bypasses monkeypatch tracking) is automatically + cleaned up on block exit, preventing leakage into subsequent tests. + """ + value = _val(splunk_key) + with patch.dict(os.environ, {splunk_key: value}, clear=False): + os.environ.pop(galileo_key, None) + SplunkAOConfig._bridge_env_vars() + assert os.environ.get(galileo_key) == value, ( + f"Expected {galileo_key}={value!r} after bridging {splunk_key}" + ) @pytest.mark.parametrize("splunk_key,galileo_key", _ALL_BRIDGE_PAIRS) -def test_bridge_env_vars_does_not_overwrite_existing_galileo_value(monkeypatch, splunk_key, galileo_key) -> None: +def test_bridge_env_vars_does_not_overwrite_existing_galileo_value(splunk_key, galileo_key) -> None: """An explicit GALILEO_* value already in the environment must win over any SPLUNK_AO_* value — the bridge must not overwrite it.""" - existing = "pre-existing-galileo-value" - monkeypatch.setenv(galileo_key, existing) - monkeypatch.setenv(splunk_key, "should-not-overwrite") - - SplunkAOConfig._bridge_env_vars() - - assert os.environ.get(galileo_key) == existing, ( - f"Expected {galileo_key} to retain its original value; bridge must not overwrite it" - ) + existing = _val(galileo_key) + with patch.dict(os.environ, {galileo_key: existing, splunk_key: "should-not-overwrite"}, clear=False): + SplunkAOConfig._bridge_env_vars() + assert os.environ.get(galileo_key) == existing, ( + f"Expected {galileo_key} to retain its original value; bridge must not overwrite it" + ) -def test_bridge_env_vars_skips_absent_splunk_ao_keys(monkeypatch) -> None: +def test_bridge_env_vars_skips_absent_splunk_ao_keys() -> None: """When a SPLUNK_AO_* key is absent, the corresponding GALILEO_* key must not be set (no spurious entries introduced by the bridge).""" - for splunk_key, galileo_key in _ALL_BRIDGE_PAIRS: - monkeypatch.delenv(splunk_key, raising=False) - monkeypatch.delenv(galileo_key, raising=False) - - SplunkAOConfig._bridge_env_vars() - - for _, galileo_key in _ALL_BRIDGE_PAIRS: - assert galileo_key not in os.environ, ( - f"{galileo_key} must not be set when its SPLUNK_AO_* source is absent" - ) - - -def test_bridge_env_vars_api_url_specifically(monkeypatch) -> None: - """Explicit regression test for the SPLUNK_AO_API_URL -> GALILEO_API_URL - bridge entry, which was added as a fix for self-hosted deployments.""" - monkeypatch.setenv("SPLUNK_AO_API_URL", "https://my-splunk-ao-host.example.com/api") - monkeypatch.delenv("GALILEO_API_URL", raising=False) - - SplunkAOConfig._bridge_env_vars() - - assert os.environ.get("GALILEO_API_URL") == "https://my-splunk-ao-host.example.com/api" + all_bridge_keys = {k for pair in _ALL_BRIDGE_PAIRS for k in pair} + # Build an env that has no bridge-related keys at all. + clean_env = {k: v for k, v in os.environ.items() if k not in all_bridge_keys} + with patch.dict(os.environ, clean_env, clear=True): + SplunkAOConfig._bridge_env_vars() + for _, galileo_key in _ALL_BRIDGE_PAIRS: + assert galileo_key not in os.environ, ( + f"{galileo_key} must not be set when its SPLUNK_AO_* source is absent" + ) # --------------------------------------------------------------------------- From 6841cd0c0a4935b58b1b02056651d1e6b5a28e74 Mon Sep 17 00:00:00 2001 From: adityamehra Date: Thu, 25 Jun 2026 13:14:08 -0700 Subject: [PATCH 22/22] fix(docs): Update to python 3.11+ --- splunk-ao-adk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splunk-ao-adk/README.md b/splunk-ao-adk/README.md index 5a13f2ad..452b9430 100644 --- a/splunk-ao-adk/README.md +++ b/splunk-ao-adk/README.md @@ -12,7 +12,7 @@ Splunk AO observability for [Google ADK](https://github.com/google/adk-python) a pip install splunk-ao-adk ``` -**Requirements:** Python 3.10+, a [Splunk AO API key](https://www.splunk.com/), and a [Google AI API key](https://aistudio.google.com/apikey) +**Requirements:** Python 3.11+, a [Splunk AO API key](https://www.splunk.com/), and a [Google AI API key](https://aistudio.google.com/apikey) ## Quick Start