Skip to content
Merged
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
7 changes: 7 additions & 0 deletions src/agents/tracing/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ def create_span(
"""
self._refresh_disabled_flag()
tracing_api_key: str | None = None
trace_metadata: dict[str, Any] | None = None
if self._disabled or disabled:
logger.debug(f"Tracing is disabled. Not creating span {span_data}")
return NoOpSpan(span_data)
Expand All @@ -331,6 +332,8 @@ def create_span(
parent_id = current_span.span_id if current_span else None
trace_id = current_trace.trace_id
tracing_api_key = current_trace.tracing_api_key
# Trace is an interface; custom implementations may omit metadata.
trace_metadata = getattr(current_trace, "metadata", None)

elif isinstance(parent, Trace):
if isinstance(parent, NoOpTrace):
Expand All @@ -339,13 +342,16 @@ def create_span(
trace_id = parent.trace_id
parent_id = None
tracing_api_key = parent.tracing_api_key
# Trace is an interface; custom implementations may omit metadata.
trace_metadata = getattr(parent, "metadata", None)
elif isinstance(parent, Span):
if isinstance(parent, NoOpSpan):
logger.debug(f"Parent {parent} is no-op, returning NoOpSpan")
return NoOpSpan(span_data)
parent_id = parent.span_id
trace_id = parent.trace_id
tracing_api_key = parent.tracing_api_key
trace_metadata = parent.trace_metadata

logger.debug(f"Creating span {span_data} with id {span_id}")

Expand All @@ -356,6 +362,7 @@ def create_span(
processor=self._multi_processor,
span_data=span_data,
tracing_api_key=tracing_api_key,
trace_metadata=trace_metadata,
)

def shutdown(self) -> None:
Expand Down
12 changes: 12 additions & 0 deletions src/agents/tracing/spans.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ def tracing_api_key(self) -> str | None:
"""The API key to use when exporting this span."""
pass

@property
def trace_metadata(self) -> dict[str, Any] | None:
"""Trace-level metadata inherited by this span, if available."""
return None


class NoOpSpan(Span[TSpanData]):
"""A no-op implementation of Span that doesn't record any data.
Expand Down Expand Up @@ -266,6 +271,7 @@ class SpanImpl(Span[TSpanData]):
"_processor",
"_span_data",
"_tracing_api_key",
"_trace_metadata",
)

def __init__(
Expand All @@ -276,6 +282,7 @@ def __init__(
processor: TracingProcessor,
span_data: TSpanData,
tracing_api_key: str | None,
trace_metadata: dict[str, Any] | None = None,
):
self._trace_id = trace_id
self._span_id = span_id or util.gen_span_id()
Expand All @@ -287,6 +294,7 @@ def __init__(
self._prev_span_token: contextvars.Token[Span[TSpanData] | None] | None = None
self._span_data = span_data
self._tracing_api_key = tracing_api_key
self._trace_metadata = trace_metadata

@property
def trace_id(self) -> str:
Expand Down Expand Up @@ -356,6 +364,10 @@ def ended_at(self) -> str | None:
def tracing_api_key(self) -> str | None:
return self._tracing_api_key

@property
def trace_metadata(self) -> dict[str, Any] | None:
return self._trace_metadata

def export(self) -> dict[str, Any] | None:
return {
"object": "trace.span",
Expand Down
60 changes: 60 additions & 0 deletions tests/test_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
from agents.tracing import (
Span,
Trace,
TracingProcessor,
agent_span,
custom_span,
function_span,
generation_span,
handoff_span,
set_trace_processors,
trace,
)
from agents.tracing.spans import SpanError
Expand Down Expand Up @@ -410,6 +412,64 @@ def test_trace_and_spans_use_tracing_config_key():
assert span.tracing_api_key == "tracing-key"


def test_trace_metadata_propagates_to_spans():
metadata = {"source": "run"}
with trace(workflow_name="test", metadata=metadata) as current_trace:
with custom_span(name="direct_child", parent=current_trace) as direct_child:
assert direct_child.trace_metadata == metadata
with custom_span(name="parent") as parent:
assert parent.trace_metadata == metadata
with custom_span(name="child", parent=parent) as child:
assert child.trace_metadata == metadata


def test_processor_can_lookup_trace_metadata_by_span_trace_id():
class MetadataPropagatingProcessor(TracingProcessor):
def __init__(self) -> None:
self.trace_metadata_by_id: dict[str, dict[str, Any]] = {}
self.looked_up_metadata: dict[str, Any] | None = None
self.span_trace_metadata: dict[str, Any] | None = None

def on_trace_start(self, trace: Trace) -> None:
trace_metadata = getattr(trace, "metadata", None)
if trace_metadata:
self.trace_metadata_by_id[trace.trace_id] = dict(trace_metadata)

def on_trace_end(self, trace: Trace) -> None:
return None

def on_span_start(self, span: Span[Any]) -> None:
return None

def on_span_end(self, span: Span[Any]) -> None:
if span.span_data.type != "agent":
return
self.looked_up_metadata = self.trace_metadata_by_id.get(span.trace_id)
self.span_trace_metadata = span.trace_metadata

def shutdown(self) -> None:
return None

def force_flush(self) -> None:
return None

metadata = {
"user_id": "u_123",
"chat_type": "support",
}
processor = MetadataPropagatingProcessor()
set_trace_processors([processor])
try:
with trace(workflow_name="workflow", metadata=metadata):
with agent_span(name="agent"):
pass
finally:
set_trace_processors([SPAN_PROCESSOR_TESTING])

assert processor.looked_up_metadata == metadata
assert processor.span_trace_metadata == metadata


def test_trace_to_json_only_includes_tracing_api_key_when_requested():
with trace(workflow_name="test", tracing={"api_key": "secret-key"}) as tr:
default_json = tr.to_json()
Expand Down