Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 28 additions & 28 deletions galileo-adk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@
[![Python versions](https://img.shields.io/pypi/pyversions/galileo-adk.svg)](https://pypi.org/project/galileo-adk/)
[![License](https://img.shields.io/pypi/l/galileo-adk.svg)](https://github.com/rungalileo/galileo-python/blob/main/LICENSE)

Galileo observability for [Google ADK](https://github.com/google/adk-python) agents. Automatic tracing of agent runs, LLM calls, and tool executions.
Splunk AO observability for [Google ADK](https://github.com/google/adk-python) agents. Automatic tracing of agent runs, LLM calls, and tool executions.

## Installation

```bash
pip install galileo-adk
```

**Requirements:** Python 3.10+, a [Galileo API key](https://www.rungalileo.io/), and a [Google AI API key](https://aistudio.google.com/apikey)
**Requirements:** Python 3.10+, a [Splunk AO API key](https://www.splunk.com/), and a [Google AI API key](https://aistudio.google.com/apikey)

## Quick Start

```python
import asyncio
from galileo_adk import GalileoADKPlugin
from galileo_adk import SplunkAOADKPlugin
from google.adk.runners import Runner
from google.adk.agents import LlmAgent
from google.genai import types

async def main():
plugin = GalileoADKPlugin(project="my-project", log_stream="production")
plugin = SplunkAOADKPlugin(project="my-project", log_stream="production")
agent = LlmAgent(name="assistant", model="gemini-2.0-flash", instruction="You are helpful.")
runner = Runner(agent=agent, plugins=[plugin])

Expand All @@ -34,33 +34,33 @@ 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())
```

## Configuration

| Parameter | Environment Variable | Description |
|-----------|---------------------|-------------|
| `project` | `GALILEO_PROJECT` | Project name (required unless `ingestion_hook` provided) |
| `log_stream` | `GALILEO_LOG_STREAM` | Log stream name (required unless `ingestion_hook` provided) |
| `ingestion_hook` | - | Custom callback for trace data (bypasses Galileo backend) |
| `project` | `SPLUNK_AO_PROJECT` | Project name (required unless `ingestion_hook` provided) |
| `log_stream` | `SPLUNK_AO_LOG_STREAM` | Log stream name (required unless `ingestion_hook` provided) |
| `ingestion_hook` | - | Custom callback for trace data (bypasses Splunk AO backend) |

## Features

### Session Tracking

All traces with the same `session_id` are automatically grouped into a Galileo session, enabling conversation-level tracking:
All traces with the same `session_id` are automatically grouped into a Splunk AO session, enabling conversation-level tracking:

```python
import asyncio
from galileo_adk import GalileoADKPlugin
from galileo_adk import SplunkAOADKPlugin
from google.adk.runners import Runner
from google.adk.agents import LlmAgent
from google.genai import types

async def main():
plugin = GalileoADKPlugin(project="my-project", log_stream="production")
plugin = SplunkAOADKPlugin(project="my-project", log_stream="production")
agent = LlmAgent(name="assistant", model="gemini-2.0-flash", instruction="You are helpful.")
runner = Runner(agent=agent, plugins=[plugin])

Expand All @@ -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())
```

Expand All @@ -90,14 +90,14 @@ Attach custom metadata to traces using ADK's `RunConfig`. Metadata is propagated

```python
import asyncio
from galileo_adk import GalileoADKPlugin
from galileo_adk import SplunkAOADKPlugin
from google.adk.runners import Runner
from google.adk.agents import LlmAgent
from google.adk.agents.run_config import RunConfig
from google.genai import types

async def main():
plugin = GalileoADKPlugin(project="my-project", log_stream="production")
plugin = SplunkAOADKPlugin(project="my-project", log_stream="production")
agent = LlmAgent(name="assistant", model="gemini-2.0-flash", instruction="You are helpful.")
runner = Runner(agent=agent, plugins=[plugin])

Expand All @@ -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())
```

Expand All @@ -131,13 +131,13 @@ For granular control over which callbacks to use, attach them directly to your a

```python
import asyncio
from galileo_adk import GalileoADKCallback
from galileo_adk import SplunkAOADKCallback
from google.adk.runners import Runner
from google.adk.agents import LlmAgent
from google.genai import types

async def main():
callback = GalileoADKCallback(project="my-project", log_stream="production")
callback = SplunkAOADKCallback(project="my-project", log_stream="production")

agent = LlmAgent(
name="assistant",
Expand All @@ -158,19 +158,19 @@ async def main():
print(event.content.parts[0].text)

if __name__ == "__main__":
# Set environment variables: GALILEO_API_KEY, GOOGLE_API_KEY
# Set environment variables: SPLUNK_AO_API_KEY, GOOGLE_API_KEY
asyncio.run(main())
```

### Retriever Spans

By default, all `FunctionTool` calls are logged as tool spans. To log a retriever function as a **retriever span** (enabling RAG quality metrics in Galileo), decorate it with `@galileo_retriever`:
By default, all `FunctionTool` calls are logged as tool spans. To log a retriever function as a **retriever span** (enabling RAG quality metrics in Splunk AO), decorate it with `@splunk_ao_retriever`:

```python
from galileo_adk import galileo_retriever
from galileo_adk import splunk_ao_retriever
from google.adk.tools import FunctionTool

@galileo_retriever
@splunk_ao_retriever
def search_docs(query: str) -> str:
"""Search the knowledge base."""
results = my_vector_db.search(query)
Expand All @@ -181,20 +181,20 @@ tool = FunctionTool(search_docs)

### Ingestion Hook

Intercept traces for custom processing before forwarding to Galileo:
Intercept traces for custom processing before forwarding to Splunk AO:

```python
import asyncio
import os
from splunk_ao import GalileoLogger
from galileo_adk import GalileoADKPlugin
from galileo_adk import SplunkAOADKPlugin
from google.adk.runners import Runner
from google.adk.agents import LlmAgent
from google.genai import types

logger = GalileoLogger(
project=os.getenv("GALILEO_PROJECT", "my-project"),
log_stream=os.getenv("GALILEO_LOG_STREAM", "dev"),
project=os.getenv("SPLUNK_AO_PROJECT", "my-project"),
log_stream=os.getenv("SPLUNK_AO_LOG_STREAM", "dev"),
)

def my_ingestion_hook(request):
Expand All @@ -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])

Expand All @@ -224,13 +224,13 @@ async def main():
print(event.content.parts[0].text)

if __name__ == "__main__":
# Set environment variables: GALILEO_API_KEY, GOOGLE_API_KEY
# Set environment variables: SPLUNK_AO_API_KEY, GOOGLE_API_KEY
asyncio.run(main())
```

## Resources

- [Galileo Documentation](https://docs.rungalileo.io/)
- [Splunk AO Documentation](https://docs.splunk.com/)
- [Google ADK Documentation](https://google.github.io/adk-docs/)

## License
Expand Down
8 changes: 4 additions & 4 deletions galileo-adk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
env = [
"GALILEO_CONSOLE_URL=http://fake.test:8088",
"GALILEO_API_KEY=api-1234567890",
"GALILEO_PROJECT=test-project",
"GALILEO_LOG_STREAM=test-log-stream",
"SPLUNK_AO_CONSOLE_URL=http://fake.test:8088",
"SPLUNK_AO_API_KEY=api-1234567890",
"SPLUNK_AO_PROJECT=test-project",
"SPLUNK_AO_LOG_STREAM=test-log-stream",
]
addopts = [
"-v",
Expand Down
12 changes: 6 additions & 6 deletions galileo-adk/src/galileo_adk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
__version__ = "2.0.1"

from galileo_adk.callback import GalileoADKCallback
from galileo_adk.decorator import galileo_retriever
from galileo_adk.callback import SplunkAOADKCallback

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update directory to from galileo-adk to splunk-ao-adk

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — PyPI package renamed to splunk-ao-adk in pyproject.toml. The Python import module name (galileo_adk) is kept as-is for now to avoid breaking downstream consumers; a follow-up rename of the source directory (galileo_adk/splunk_ao_adk/) can be tracked separately if desired.

from galileo_adk.decorator import splunk_ao_retriever
from galileo_adk.observer import get_custom_metadata
from galileo_adk.plugin import GalileoADKPlugin
from galileo_adk.plugin import SplunkAOADKPlugin

__all__ = [
"GalileoADKPlugin",
"GalileoADKCallback",
"galileo_retriever",
"SplunkAOADKPlugin",
"SplunkAOADKCallback",
"splunk_ao_retriever",
"get_custom_metadata",
]
30 changes: 15 additions & 15 deletions galileo-adk/src/galileo_adk/callback.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -10,7 +10,7 @@
from splunk_ao.schema.trace import TracesIngestRequest

from galileo_adk.observer import (
GalileoObserver,
SplunkAOObserver,
get_agent_name_from_tool_context,
get_custom_metadata,
get_invocation_id,
Expand All @@ -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:

Expand All @@ -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,
Expand All @@ -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,
Expand Down
16 changes: 8 additions & 8 deletions galileo-adk/src/galileo_adk/decorator.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
"""Decorators for marking ADK tools with Galileo observability metadata."""
"""Decorators for marking ADK tools with Splunk AO observability metadata."""

from __future__ import annotations

from collections.abc import Callable
from typing import Any


def galileo_retriever(func: Callable[..., Any]) -> Callable[..., Any]:
"""Mark a function as a retriever for Galileo observability.
def splunk_ao_retriever(func: Callable[..., Any]) -> Callable[..., Any]:
"""Mark a function as a retriever for Splunk AO observability.

When a function decorated with @galileo_retriever is wrapped in a
FunctionTool, Galileo will log it as a retriever span (node_type="retriever")
When a function decorated with @splunk_ao_retriever is wrapped in a
FunctionTool, Splunk AO will log it as a retriever span (node_type="retriever")
instead of a tool span, enabling RAG quality metrics.

Example
-------
>>> from galileo_adk import galileo_retriever
>>> @galileo_retriever
>>> from galileo_adk import splunk_ao_retriever
>>> @splunk_ao_retriever
... def my_search(query: str) -> str:
... return search_docs(query)
>>> tool = FunctionTool(my_search)
"""
func._galileo_is_retriever = True # type: ignore[attr-defined]
func._splunk_ao_is_retriever = True # type: ignore[attr-defined]
return func
8 changes: 4 additions & 4 deletions galileo-adk/src/galileo_adk/observer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Shared observability logic for Galileo ADK integration."""
"""Shared observability logic for Splunk AO ADK integration."""

from __future__ import annotations

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -393,12 +393,12 @@ def _is_retriever_tool(self, tool: Any) -> bool:

Detection uses two strategies:
1. isinstance check against google.adk.tools.retrieval.BaseRetrievalTool
2. Custom functions decorated with @galileo_retriever (sets _galileo_is_retriever on func)
2. Custom functions decorated with @splunk_ao_retriever (sets _splunk_ao_is_retriever on func)
"""
if _BaseRetrievalTool is not None and isinstance(tool, _BaseRetrievalTool):
return True
func = getattr(tool, "func", None)
return bool(func is not None and getattr(func, "_galileo_is_retriever", False))
return bool(func is not None and getattr(func, "_splunk_ao_is_retriever", False))

def on_tool_start(
self,
Expand Down
Loading
Loading