diff --git a/.github/workflows/ci-tests-splunk-ao-adk.yaml b/.github/workflows/ci-tests-splunk-ao-adk.yaml new file mode 100644 index 00000000..43985065 --- /dev/null +++ b/.github/workflows/ci-tests-splunk-ao-adk.yaml @@ -0,0 +1,62 @@ +name: Test splunk-ao-adk + +on: + push: + branches: [main] + paths: + - 'splunk-ao-adk/**' + - '.github/workflows/ci-tests-splunk-ao-adk.yaml' + pull_request: + paths: + - 'splunk-ao-adk/**' + - '.github/workflows/ci-tests-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.11", "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@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + with: + enable-cache: true + 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. + # 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 "^(galileo|google-adk|splunk-ao)" + + - name: Run type check + run: uv run mypy src/ + + - name: Run tests + run: uv run pytest --cov=splunk_ao_adk --cov-report=xml diff --git a/galileo-adk/src/galileo_adk/__init__.py b/galileo-adk/src/galileo_adk/__init__.py deleted file mode 100644 index 209c3e6d..00000000 --- a/galileo-adk/src/galileo_adk/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -__version__ = "2.0.1" - -from galileo_adk.callback import GalileoADKCallback -from galileo_adk.decorator import splunk_ao_retriever -from galileo_adk.observer import get_custom_metadata -from galileo_adk.plugin import GalileoADKPlugin - -__all__ = [ - "GalileoADKPlugin", - "GalileoADKCallback", - "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 79% rename from galileo-adk/CONTRIBUTING.md rename to splunk-ao-adk/CONTRIBUTING.md index 12e74e23..d4464e02 100644 --- a/galileo-adk/CONTRIBUTING.md +++ b/splunk-ao-adk/CONTRIBUTING.md @@ -1,16 +1,16 @@ -# 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/ - ├── src/galileo_adk/ +splunk-ao-python/ +├── src/splunk_ao/ ← Main Splunk AO SDK +└── splunk-ao-adk/ + ├── src/splunk_ao_adk/ ├── tests/ ├── pyproject.toml └── README.md @@ -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 @@ -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/ @@ -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/galileo-adk/README.md b/splunk-ao-adk/README.md similarity index 72% rename from galileo-adk/README.md rename to splunk-ao-adk/README.md index c360c9e9..452b9430 100644 --- a/galileo-adk/README.md +++ b/splunk-ao-adk/README.md @@ -1,30 +1,30 @@ -# 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) -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 ```bash -pip install galileo-adk +pip install splunk-ao-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.11+, 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 splunk_ao_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 splunk_ao_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 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 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 splunk_ao_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,16 +158,16 @@ 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 `@splunk_ao_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 splunk_ao_retriever +from splunk_ao_adk import splunk_ao_retriever from google.adk.tools import FunctionTool @splunk_ao_retriever @@ -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 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( - project=os.getenv("GALILEO_PROJECT", "my-project"), - log_stream=os.getenv("GALILEO_LOG_STREAM", "dev"), +logger = SplunkAOLogger( + 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.rungalileo.io/) - [Google ADK Documentation](https://google.github.io/adk-docs/) ## License diff --git a/galileo-adk/pyproject.toml b/splunk-ao-adk/pyproject.toml similarity index 77% rename from galileo-adk/pyproject.toml rename to splunk-ao-adk/pyproject.toml index 851c82b4..96e46476 100644 --- a/galileo-adk/pyproject.toml +++ b/splunk-ao-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.11,<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", @@ -14,21 +14,19 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "galileo>=2.0.0,<3.0.0", + "splunk-ao>=0.1.0,<1.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"] +packages = ["src/splunk_ao_adk"] [tool.pytest.ini_options] @@ -42,10 +40,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", @@ -80,7 +78,7 @@ ignore = [ ] [tool.ruff.lint.isort] -known-first-party = ["galileo_adk", "galileo"] +known-first-party = ["splunk_ao_adk", "splunk_ao"] [tool.mypy] @@ -92,7 +90,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] @@ -111,11 +109,11 @@ 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 = "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"] @@ -138,6 +136,7 @@ dev = [ "ruff>=0.12.3", "mypy>=1.16.0", "coverage>=7.9.2", + "splunk-ao[otel]>=0.1.0,<1.0.0", "galileo-core[testing]>=3.82.0", ] 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 89% rename from galileo-adk/src/galileo_adk/callback.py rename to splunk-ao-adk/src/splunk_ao_adk/callback.py index 9768a72a..d973b5f9 100644 --- a/galileo-adk/src/galileo_adk/callback.py +++ b/splunk-ao-adk/src/splunk_ao_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 @@ -9,8 +9,8 @@ from splunk_ao.schema.trace import TracesIngestRequest -from galileo_adk.observer import ( - GalileoObserver, +from splunk_ao_adk.observer import ( + SplunkAOObserver, get_agent_name_from_tool_context, get_custom_metadata, get_invocation_id, @@ -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 @@ -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/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 66% rename from galileo-adk/src/galileo_adk/decorator.py rename to splunk-ao-adk/src/splunk_ao_adk/decorator.py index bcc0d206..6dc3b6c5 100644 --- a/galileo-adk/src/galileo_adk/decorator.py +++ b/splunk-ao-adk/src/splunk_ao_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 @@ -7,15 +7,15 @@ def splunk_ao_retriever(func: Callable[..., Any]) -> Callable[..., Any]: - """Mark a function as a retriever for Galileo observability. + """Mark a function as a retriever for Splunk AO observability. When a function decorated with @splunk_ao_retriever is wrapped in a - FunctionTool, Galileo will log it as a retriever span (node_type="retriever") + 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 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 97% rename from galileo-adk/src/galileo_adk/observer.py rename to splunk-ao-adk/src/splunk_ao_adk/observer.py index d83d1b93..b21bd43f 100644 --- a/galileo-adk/src/galileo_adk/observer.py +++ b/splunk-ao-adk/src/splunk_ao_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 @@ -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__) @@ -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 @@ -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/galileo-adk/src/galileo_adk/plugin.py b/splunk-ao-adk/src/splunk_ao_adk/plugin.py similarity index 95% rename from galileo-adk/src/galileo_adk/plugin.py rename to splunk-ao-adk/src/splunk_ao_adk/plugin.py index cb35bea2..e2fa27d9 100644 --- a/galileo-adk/src/galileo_adk/plugin.py +++ b/splunk-ao-adk/src/splunk_ao_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 @@ -11,8 +11,8 @@ from splunk_ao.schema.trace import TracesIngestRequest -from galileo_adk.observer import ( - GalileoObserver, +from splunk_ao_adk.observer import ( + SplunkAOObserver, get_agent_name_from_tool_context, get_custom_metadata, get_invocation_id, @@ -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 @@ -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/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 86% rename from galileo-adk/tests/test_callback.py rename to splunk-ao-adk/tests/test_callback.py index 49775a4e..a68c3682 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 GalileoADKCallback +from splunk_ao_adk import SplunkAOADKCallback from .mocks import ( MockCallbackContext, @@ -81,75 +81,75 @@ 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: - 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 - 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", 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 = 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_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 84% rename from galileo-adk/tests/test_observer.py rename to splunk-ao-adk/tests/test_observer.py index b97f361a..2caee853 100644 --- a/galileo-adk/tests/test_observer.py +++ b/splunk-ao-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 splunk_ao_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,8 +215,8 @@ 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)) - logger = observer._handler._galileo_logger + observer = SplunkAOObserver(ingestion_hook=lambda r: captured_requests.append(r)) + logger = observer._handler._splunk_ao_logger # When: updating session with an ADK session ID observer.update_session_if_changed("adk-session-123") @@ -227,9 +227,9 @@ 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 + logger = observer._handler._splunk_ao_logger # When: the ADK session changes observer.update_session_if_changed("session-2") @@ -240,8 +240,8 @@ 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) - logger = observer._handler._galileo_logger + observer = SplunkAOObserver(ingestion_hook=lambda r: None) + logger = observer._handler._splunk_ao_logger # When: updating with "unknown" session ID observer.update_session_if_changed("unknown") @@ -252,9 +252,9 @@ 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 + logger = observer._handler._splunk_ao_logger original_external_id = logger._session_external_id # When: updating with the same session ID @@ -265,9 +265,9 @@ 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 + 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/galileo-adk/tests/test_plugin.py b/splunk-ao-adk/tests/test_plugin.py similarity index 91% rename from galileo-adk/tests/test_plugin.py rename to splunk-ao-adk/tests/test_plugin.py index 371ae5a5..ab7a2e45 100644 --- a/galileo-adk/tests/test_plugin.py +++ b/splunk-ao-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 splunk_ao_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/splunk-ao-adk/tests/test_retriever.py similarity index 89% rename from galileo-adk/tests/test_retriever.py rename to splunk-ao-adk/tests/test_retriever.py index d428ffa3..9b915030 100644 --- a/galileo-adk/tests/test_retriever.py +++ b/splunk-ao-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,9 +7,8 @@ import pytest -from galileo_adk.decorator import splunk_ao_retriever -from galileo_adk.observer import GalileoObserver - +from splunk_ao_adk.decorator import splunk_ao_retriever +from splunk_ao_adk.observer import SplunkAOObserver from .mocks import MockTool, MockToolContext @@ -36,9 +35,9 @@ 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: @@ -80,7 +79,7 @@ def my_search(query: str) -> str: 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") @@ -90,8 +89,8 @@ def test_regular_tool_is_not_retriever(self, observer: GalileoObserver) -> None: # Then: it is not detected as a retriever assert result is False - @patch("galileo_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) - def test_base_retrieval_tool_instance_is_detected(self, observer: GalileoObserver) -> None: + @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") @@ -101,8 +100,8 @@ def test_base_retrieval_tool_instance_is_detected(self, observer: GalileoObserve # Then: it is detected as a retriever assert result is True - @patch("galileo_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) - def test_base_retrieval_tool_subclass_is_detected(self, observer: GalileoObserver) -> None: + @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") @@ -112,7 +111,7 @@ 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: + 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: @@ -126,7 +125,7 @@ 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: + 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" @@ -139,8 +138,8 @@ def my_calculator(expression: str) -> str: # Then: it is not detected as a retriever assert result is False - @patch("galileo_adk.observer._BaseRetrievalTool", None) - def test_isinstance_skipped_when_base_class_unavailable(self, observer: GalileoObserver) -> 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") @@ -150,7 +149,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,7 +163,7 @@ 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: + 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: @@ -188,7 +187,7 @@ 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 @splunk_ao_retriever def my_search(query: str) -> str: @@ -210,7 +209,7 @@ 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 @splunk_ao_retriever def my_search(search_text: str) -> str: @@ -232,7 +231,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 +249,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() @@ -266,8 +265,8 @@ def test_on_tool_start_returns_uuid(self, observer: GalileoObserver) -> None: # Then: a valid UUID is returned assert isinstance(run_id, UUID) - @patch("galileo_adk.observer._BaseRetrievalTool", MockBaseRetrievalTool) - def test_base_retrieval_tool_creates_retriever_span(self, observer: GalileoObserver) -> None: + @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") tool_context = MockToolContext() 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 97% rename from galileo-adk/tests/test_status_code.py rename to splunk-ao-adk/tests/test_status_code.py index 176af877..13874859 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 SplunkAOADKPlugin +from splunk_ao_adk.plugin import _extract_status_code, _is_http_status_code class TestIsHttpStatusCode: @@ -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/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: diff --git a/tests/test_config.py b/tests/test_config.py index 242fe7a9..104a5a11 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,88 @@ 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. +# 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_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"), +] + +# 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(splunk_key, galileo_key) -> None: + """Each SPLUNK_AO_* value is copied to its GALILEO_* counterpart when the + GALILEO_* key is absent from the environment. + + 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(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 = _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() -> None: + """When a SPLUNK_AO_* key is absent, the corresponding GALILEO_* key must + not be set (no spurious entries introduced by the bridge).""" + 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" + ) + + +# --------------------------------------------------------------------------- + + @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: