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
4 changes: 2 additions & 2 deletions examples/agent/google-adk/my_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

# Create tracer provider and register Splunk AO span processor
tracer_provider = trace_sdk.TracerProvider()
galileo_span_processor = otel.SplunkAOSpanProcessor()
tracer_provider.add_span_processor(galileo_span_processor)
splunk_ao_span_processor = otel.SplunkAOSpanProcessor()
tracer_provider.add_span_processor(splunk_ao_span_processor)

# Instrument Google ADK with OpenInference (this captures inputs/outputs)
GoogleADKInstrumentor().instrument(tracer_provider=tracer_provider)
Expand Down
4 changes: 2 additions & 2 deletions examples/agent/google-adk/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
google-adk
google-adk>=1.28.1
openinference-instrumentation-google-adk
python-dotenv
splunk-ao
splunk-ao
4 changes: 2 additions & 2 deletions examples/agent/langgraph-open-telemetry/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
client = openai.OpenAI(api_key=openai_api_key)
print("✓ OpenAI client configured")

galileo_span_processor = otel.SplunkAOSpanProcessor()
splunk_ao_span_processor = otel.SplunkAOSpanProcessor()


resource = Resource.create(
Expand All @@ -40,7 +40,7 @@


# Add a span processor that sends traces to Splunk AO
otel.add_splunk_ao_span_processor(tracer_provider, galileo_span_processor)
otel.add_splunk_ao_span_processor(tracer_provider, splunk_ao_span_processor)

# OPTIONAL: Console output disabled to reduce noise in Splunk AO
# Uncomment the next 3 lines if you want local console debugging:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import chromadb.utils.embedding_functions as ef
import openai
from fastapi import FastAPI
from splunk_ao.otel import start_galileo_span
from splunk_ao.otel import start_splunk_ao_span
from splunk_ao_core.schemas.logging.span import RetrieverSpan
from splunk_ao_core.schemas.shared.document import Document
from langgraph.graph import END, START, StateGraph
Expand Down Expand Up @@ -110,7 +110,7 @@ def retrieve_documents(state: GraphState) -> GraphState:
query = state["question"]
retriever_span = RetrieverSpan(name="chromadb_search", input=query)

with start_galileo_span(retriever_span) as span:
with start_splunk_ao_span(retriever_span) as span:
try:
collection = chroma.get_collection(CHROMA_COLLECTION, embedding_function=embedding_fn)
results = collection.query(query_texts=[query], n_results=3)
Expand Down
2 changes: 1 addition & 1 deletion splunk-ao-adk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ classifiers = [
]
dependencies = [
"splunk-ao>=0.1.0,<1.0.0",
"google-adk>=1.14.1,<2.0.0",
"google-adk>=1.28.1,<2.0.0",
]

[project.urls]
Expand Down
4 changes: 2 additions & 2 deletions src/splunk_ao/handlers/openai_agents/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from agents.tracing import ResponseSpanData, get_current_span, get_trace_provider

from galileo_core.schemas.logging.span import LlmMetrics, LlmSpan
from galileo_core.schemas.logging.span import Span as GalileoSpan
from galileo_core.schemas.logging.span import Span as SplunkAOSpan
from splunk_ao import SplunkAOLogger, splunk_ao_context
from splunk_ao.schema.handlers import Node
from splunk_ao.utils import _get_timestamp
Expand Down Expand Up @@ -492,7 +492,7 @@ def _extract_tool_output(self, item_dict: dict[str, Any], item_type: str) -> str
return serialize_to_str(item_dict.get("output") or item_dict.get("results"))

@staticmethod
def add_splunk_ao_custom_span(span: GalileoSpan) -> Span[SplunkAOCustomSpan]:
def add_splunk_ao_custom_span(span: SplunkAOSpan) -> Span[SplunkAOCustomSpan]:
"""Add a Galileo custom span to the trace."""
trace_provider = get_trace_provider()
current_span = get_current_span()
Expand Down
58 changes: 29 additions & 29 deletions src/splunk_ao/otel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from requests import Session

from galileo_core.schemas.logging.span import RetrieverSpan, ToolSpan, WorkflowSpan
from galileo_core.schemas.logging.span import Span as GalileoSpan
from galileo_core.schemas.logging.span import Span as SplunkAOSpan
from splunk_ao.config import SplunkAOConfig
from splunk_ao.decorator import (
_dataset_input_context,
Expand Down Expand Up @@ -318,32 +318,32 @@ def add_splunk_ao_span_processor(tracer_provider: TracerProvider, processor: Spl
_TRACE_PROVIDER_CONTEXT_VAR.set(tracer_provider)


def _set_retriever_span_attributes(span: trace.Span, galileo_span: RetrieverSpan) -> None:
def _set_retriever_span_attributes(span: trace.Span, splunk_ao_span: RetrieverSpan) -> None:
span.set_attribute("db.operation", "search")
span.set_attribute("gen_ai.input.messages", json.dumps([{"role": "user", "content": galileo_span.input}]))
span.set_attribute("gen_ai.input.messages", json.dumps([{"role": "user", "content": splunk_ao_span.input}]))
span.set_attribute(
"gen_ai.output.messages",
json.dumps(
[
{
"role": "assistant",
"content": {"documents": document_adapter.dump_python(galileo_span.output, mode="json")},
"content": {"documents": document_adapter.dump_python(splunk_ao_span.output, mode="json")},
}
]
),
)


def _set_tool_span_attributes(span: trace.Span, galileo_span: ToolSpan) -> None:
def _set_tool_span_attributes(span: trace.Span, splunk_ao_span: ToolSpan) -> None:
span.set_attribute("gen_ai.operation.name", "execute_tool")
span.set_attribute("gen_ai.tool.name", galileo_span.name)
span.set_attribute("gen_ai.tool.call.arguments", galileo_span.input)
span.set_attribute("gen_ai.input.messages", json.dumps([{"role": "tool", "content": galileo_span.input}]))
if galileo_span.output is not None:
span.set_attribute("gen_ai.tool.call.result", galileo_span.output)
span.set_attribute("gen_ai.output.messages", json.dumps([{"role": "tool", "content": galileo_span.output}]))
if galileo_span.tool_call_id is not None:
span.set_attribute("gen_ai.tool.call.id", galileo_span.tool_call_id)
span.set_attribute("gen_ai.tool.name", splunk_ao_span.name)
span.set_attribute("gen_ai.tool.call.arguments", splunk_ao_span.input)
span.set_attribute("gen_ai.input.messages", json.dumps([{"role": "tool", "content": splunk_ao_span.input}]))
if splunk_ao_span.output is not None:
span.set_attribute("gen_ai.tool.call.result", splunk_ao_span.output)
span.set_attribute("gen_ai.output.messages", json.dumps([{"role": "tool", "content": splunk_ao_span.output}]))
if splunk_ao_span.tool_call_id is not None:
span.set_attribute("gen_ai.tool.call.id", splunk_ao_span.tool_call_id)


def _apply_dataset_attributes(
Expand All @@ -358,26 +358,26 @@ def _apply_dataset_attributes(
span.set_attribute("splunk_ao.dataset.metadata", json.dumps(dataset_metadata))


def _set_workflow_span_attributes(span: trace.Span, galileo_span: WorkflowSpan) -> None:
def _set_workflow_span_attributes(span: trace.Span, splunk_ao_span: WorkflowSpan) -> None:
"""Set OpenTelemetry attributes for WorkflowSpan."""
# Handle input - Union[str, Sequence[Message]]
if isinstance(galileo_span.input, str):
input_messages = [{"role": "user", "content": galileo_span.input}]
if isinstance(splunk_ao_span.input, str):
input_messages = [{"role": "user", "content": splunk_ao_span.input}]
else:
# Sequence[Message] - serialize each message
input_messages = []
for msg in list(galileo_span.input):
for msg in list(splunk_ao_span.input):
if hasattr(msg, "model_dump"):
input_messages.append(msg.model_dump(exclude_none=True))
else:
input_messages.append(msg)
span.set_attribute("gen_ai.input.messages", json.dumps(input_messages))

# Handle output - Union[str, Message, Sequence[Document], None]
if galileo_span.output is None:
if splunk_ao_span.output is None:
return

output_value = galileo_span.output
output_value = splunk_ao_span.output
# Type annotation to handle flexible content types (string or dict)
# Content can be: str (simple output), dict (documents), or dict (Message model_dump)
output_messages: list[dict[str, Any]] = []
Expand All @@ -401,22 +401,22 @@ def _set_workflow_span_attributes(span: trace.Span, galileo_span: WorkflowSpan)


@contextmanager
def start_splunk_ao_span(galileo_span: GalileoSpan) -> Generator[trace.Span, Any, None]:
def start_splunk_ao_span(splunk_ao_span: SplunkAOSpan) -> Generator[trace.Span, Any, None]:
tracer_provider = _TRACE_PROVIDER_CONTEXT_VAR.get()
if tracer_provider is None:
tracer_provider = trace.get_tracer_provider()
_TRACE_PROVIDER_CONTEXT_VAR.set(cast(TracerProvider, tracer_provider))
tracer = tracer_provider.get_tracer("galileo-tracer")
with tracer.start_as_current_span(galileo_span.name) as span:
with tracer.start_as_current_span(splunk_ao_span.name) as span:
yield span
span.set_attribute("gen_ai.system", "galileo-otel")
span.set_attribute("gen_ai.system", "splunk-ao-otel")
# Set dataset attributes for ground truth/reference output support
_apply_dataset_attributes(
span, galileo_span.dataset_input, galileo_span.dataset_output, galileo_span.dataset_metadata
span, splunk_ao_span.dataset_input, splunk_ao_span.dataset_output, splunk_ao_span.dataset_metadata
)
if isinstance(galileo_span, RetrieverSpan):
_set_retriever_span_attributes(span, galileo_span)
elif isinstance(galileo_span, ToolSpan):
_set_tool_span_attributes(span, galileo_span)
elif isinstance(galileo_span, WorkflowSpan):
_set_workflow_span_attributes(span, galileo_span)
if isinstance(splunk_ao_span, RetrieverSpan):
_set_retriever_span_attributes(span, splunk_ao_span)
elif isinstance(splunk_ao_span, ToolSpan):
_set_tool_span_attributes(span, splunk_ao_span)
elif isinstance(splunk_ao_span, WorkflowSpan):
_set_workflow_span_attributes(span, splunk_ao_span)
4 changes: 2 additions & 2 deletions src/splunk_ao/utils/openai_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
)
from agents.tracing import ResponseSpanData

from galileo_core.schemas.logging.span import Span as GalileoSpan
from galileo_core.schemas.logging.span import Span as SplunkAOSpan
from splunk_ao.schema.handlers import SPAN_TYPE
from splunk_ao.utils.serialization import serialize_to_str

_logger = logging.getLogger(__name__)


class SplunkAOCustomSpan(CustomSpanData):
def __init__(self, span: GalileoSpan, data: dict[str, Any]):
def __init__(self, span: SplunkAOSpan, data: dict[str, Any]):
self.span = span
super().__init__(span.name, data)

Expand Down
4 changes: 2 additions & 2 deletions tests/test_openai_agents_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,8 @@ def test_span_data_types(self, span_data: Any, expected_type: str) -> None:

def test_galileo_custom_span(self) -> None:
"""Test mapping SplunkAOCustomSpan."""
galileo_span = WorkflowSpan(name="Test", input="input", output="output", status_code=200)
assert _map_span_type(SplunkAOCustomSpan(galileo_span, {})) == "splunk_ao_custom"
splunk_ao_span = WorkflowSpan(name="Test", input="input", output="output", status_code=200)
assert _map_span_type(SplunkAOCustomSpan(splunk_ao_span, {})) == "splunk_ao_custom"

@pytest.mark.parametrize(
"type_attr,expected_type", [("function", "tool"), ("generation", "llm"), ("agent", "workflow")]
Expand Down
8 changes: 4 additions & 4 deletions tests/test_otel.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ class TestOTelUnavailable:
"""Test behavior when OpenTelemetry is not available."""

@patch("splunk_ao.otel.OTEL_AVAILABLE", False)
def test_galileo_span_processor_raises_import_error_when_otel_unavailable(self):
def test_splunk_ao_span_processor_raises_import_error_when_otel_unavailable(self):
"""Test that SplunkAOSpanProcessor raises ImportError when OpenTelemetry is not available."""
with pytest.raises(ImportError, match=re.escape(INSTALL_ERR_MSG)):
SplunkAOSpanProcessor(project="test")
Expand Down Expand Up @@ -569,7 +569,7 @@ def test_tool_span_with_output_no_tool_call_id(self):
assert mock_otel_span.set_attribute.call_count == 6


class TestStartGalileoSpan:
class TestStartSplunkAOSpan:
"""Test suite for start_splunk_ao_span context manager."""

@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -604,7 +604,7 @@ def test_start_splunk_ao_span_dispatches_tool_span(self):

# Then: the span has gen_ai.system set and tool-specific attributes
calls = {args[0]: args[1] for args, _ in mock_otel_span.set_attribute.call_args_list}
assert calls["gen_ai.system"] == "galileo-otel"
assert calls["gen_ai.system"] == "splunk-ao-otel"
assert calls["gen_ai.operation.name"] == "execute_tool"
assert calls["gen_ai.tool.name"] == "my-tool"
assert calls["gen_ai.tool.call.arguments"] == "tool input data"
Expand Down Expand Up @@ -632,7 +632,7 @@ def test_start_splunk_ao_span_tool_span_with_none_output(self):

# Then: gen_ai.system, operation name, tool name, tool arguments, and input are set (no output or tool_call_id)
calls = {args[0]: args[1] for args, _ in mock_otel_span.set_attribute.call_args_list}
assert calls["gen_ai.system"] == "galileo-otel"
assert calls["gen_ai.system"] == "splunk-ao-otel"
assert calls["gen_ai.operation.name"] == "execute_tool"
assert calls["gen_ai.tool.name"] == "minimal-tool"
assert calls["gen_ai.tool.call.arguments"] == "just input"
Expand Down
Loading