diff --git a/src/agents/tracing/processor_interface.py b/src/agents/tracing/processor_interface.py index 0a05bcae2..d0f18bde3 100644 --- a/src/agents/tracing/processor_interface.py +++ b/src/agents/tracing/processor_interface.py @@ -7,52 +7,125 @@ class TracingProcessor(abc.ABC): - """Interface for processing spans.""" + """Interface for processing and monitoring traces and spans in the OpenAI Agents system. + + This abstract class defines the interface that all tracing processors must implement. + Processors receive notifications when traces and spans start and end, allowing them + to collect, process, and export tracing data. + + Example: + ```python + class CustomProcessor(TracingProcessor): + def __init__(self): + self.active_traces = {} + self.active_spans = {} + + def on_trace_start(self, trace): + self.active_traces[trace.trace_id] = trace + + def on_trace_end(self, trace): + # Process completed trace + del self.active_traces[trace.trace_id] + + def on_span_start(self, span): + self.active_spans[span.span_id] = span + + def on_span_end(self, span): + # Process completed span + del self.active_spans[span.span_id] + + def shutdown(self): + # Clean up resources + self.active_traces.clear() + self.active_spans.clear() + + def force_flush(self): + # Force processing of any queued items + pass + ``` + + Notes: + - All methods should be thread-safe + - Methods should not block for long periods + - Handle errors gracefully to prevent disrupting agent execution + """ @abc.abstractmethod def on_trace_start(self, trace: "Trace") -> None: - """Called when a trace is started. + """Called when a new trace begins execution. Args: - trace: The trace that started. + trace: The trace that started. Contains workflow name and metadata. + + Notes: + - Called synchronously on trace start + - Should return quickly to avoid blocking execution + - Any errors should be caught and handled internally """ pass @abc.abstractmethod def on_trace_end(self, trace: "Trace") -> None: - """Called when a trace is finished. + """Called when a trace completes execution. Args: - trace: The trace that finished. + trace: The completed trace containing all spans and results. + + Notes: + - Called synchronously when trace finishes + - Good time to export/process the complete trace + - Should handle cleanup of any trace-specific resources """ pass @abc.abstractmethod def on_span_start(self, span: "Span[Any]") -> None: - """Called when a span is started. + """Called when a new span begins execution. Args: - span: The span that started. + span: The span that started. Contains operation details and context. + + Notes: + - Called synchronously on span start + - Should return quickly to avoid blocking execution + - Spans are automatically nested under current trace/span """ pass @abc.abstractmethod def on_span_end(self, span: "Span[Any]") -> None: - """Called when a span is finished. Should not block or raise exceptions. + """Called when a span completes execution. Args: - span: The span that finished. + span: The completed span containing execution results. + + Notes: + - Called synchronously when span finishes + - Should not block or raise exceptions + - Good time to export/process the individual span """ pass @abc.abstractmethod def shutdown(self) -> None: - """Called when the application stops.""" + """Called when the application stops to clean up resources. + + Should perform any necessary cleanup like: + - Flushing queued traces/spans + - Closing connections + - Releasing resources + """ pass @abc.abstractmethod def force_flush(self) -> None: - """Forces an immediate flush of all queued spans/traces.""" + """Forces immediate processing of any queued traces/spans. + + Notes: + - Should process all queued items before returning + - Useful before shutdown or when immediate processing is needed + - May block while processing completes + """ pass diff --git a/src/agents/tracing/spans.py b/src/agents/tracing/spans.py index ee933e730..d0a416b8c 100644 --- a/src/agents/tracing/spans.py +++ b/src/agents/tracing/spans.py @@ -16,24 +16,83 @@ class SpanError(TypedDict): + """Represents an error that occurred during span execution. + + Attributes: + message: A human-readable error description + data: Optional dictionary containing additional error context + """ message: str data: dict[str, Any] | None class Span(abc.ABC, Generic[TSpanData]): + """Base class for representing traceable operations with timing and context. + + A span represents a single operation within a trace (e.g., an LLM call, tool execution, + or agent run). Spans track timing, relationships between operations, and operation-specific + data. + + Type Args: + TSpanData: The type of span-specific data this span contains. + + Example: + ```python + # Creating a custom span + with custom_span("database_query", { + "operation": "SELECT", + "table": "users" + }) as span: + results = await db.query("SELECT * FROM users") + span.set_output({"count": len(results)}) + + # Handling errors in spans + with custom_span("risky_operation") as span: + try: + result = perform_risky_operation() + except Exception as e: + span.set_error({ + "message": str(e), + "data": {"operation": "risky_operation"} + }) + raise + ``` + + Notes: + - Spans automatically nest under the current trace + - Use context managers for reliable start/finish + - Include relevant data but avoid sensitive information + - Handle errors properly using set_error() + """ + @property @abc.abstractmethod def trace_id(self) -> str: + """The ID of the trace this span belongs to. + + Returns: + str: Unique identifier of the parent trace. + """ pass @property @abc.abstractmethod def span_id(self) -> str: + """Unique identifier for this span. + + Returns: + str: The span's unique ID within its trace. + """ pass @property @abc.abstractmethod def span_data(self) -> TSpanData: + """Operation-specific data for this span. + + Returns: + TSpanData: Data specific to this type of span (e.g., LLM generation data). + """ pass @abc.abstractmethod @@ -67,6 +126,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): @property @abc.abstractmethod def parent_id(self) -> str | None: + """ID of the parent span, if any. + + Returns: + str | None: The parent span's ID, or None if this is a root span. + """ pass @abc.abstractmethod @@ -76,6 +140,11 @@ def set_error(self, error: SpanError) -> None: @property @abc.abstractmethod def error(self) -> SpanError | None: + """Any error that occurred during span execution. + + Returns: + SpanError | None: Error details if an error occurred, None otherwise. + """ pass @abc.abstractmethod @@ -85,15 +154,33 @@ def export(self) -> dict[str, Any] | None: @property @abc.abstractmethod def started_at(self) -> str | None: + """When the span started execution. + + Returns: + str | None: ISO format timestamp of span start, None if not started. + """ pass @property @abc.abstractmethod def ended_at(self) -> str | None: + """When the span finished execution. + + Returns: + str | None: ISO format timestamp of span end, None if not finished. + """ pass class NoOpSpan(Span[TSpanData]): + """A no-op implementation of Span that doesn't record any data. + + Used when tracing is disabled but span operations still need to work. + + Args: + span_data: The operation-specific data for this span. + """ + __slots__ = ("_span_data", "_prev_span_token") def __init__(self, span_data: TSpanData): diff --git a/src/agents/tracing/traces.py b/src/agents/tracing/traces.py index 00aa0ba19..ff286de4f 100644 --- a/src/agents/tracing/traces.py +++ b/src/agents/tracing/traces.py @@ -11,8 +11,35 @@ class Trace(abc.ABC): - """ - A trace is the root level object that tracing creates. It represents a logical "workflow". + """A complete end-to-end workflow containing related spans and metadata. + + A trace represents a logical workflow or operation (e.g., "Customer Service Query" + or "Code Generation") and contains all the spans (individual operations) that occur + during that workflow. + + Example: + ```python + # Basic trace usage + with trace("Order Processing") as t: + validation_result = await Runner.run(validator, order_data) + if validation_result.approved: + await Runner.run(processor, order_data) + + # Trace with metadata and grouping + with trace( + "Customer Service", + group_id="chat_123", + metadata={"customer": "user_456"} + ) as t: + result = await Runner.run(support_agent, query) + ``` + + Notes: + - Use descriptive workflow names + - Group related traces with consistent group_ids + - Add relevant metadata for filtering/analysis + - Use context managers for reliable cleanup + - Consider privacy when adding trace data """ @abc.abstractmethod @@ -25,51 +52,92 @@ def __exit__(self, exc_type, exc_val, exc_tb): @abc.abstractmethod def start(self, mark_as_current: bool = False): - """ - Start the trace. + """Start the trace and optionally mark it as the current trace. Args: - mark_as_current: If true, the trace will be marked as the current trace. + mark_as_current: If true, marks this trace as the current trace + in the execution context. + + Notes: + - Must be called before any spans can be added + - Only one trace can be current at a time + - Thread-safe when using mark_as_current """ pass @abc.abstractmethod def finish(self, reset_current: bool = False): - """ - Finish the trace. + """Finish the trace and optionally reset the current trace. Args: - reset_current: If true, the trace will be reset as the current trace. + reset_current: If true, resets the current trace to the previous + trace in the execution context. + + Notes: + - Must be called to complete the trace + - Finalizes all open spans + - Thread-safe when using reset_current """ pass @property @abc.abstractmethod def trace_id(self) -> str: - """ - The trace ID. + """Get the unique identifier for this trace. + + Returns: + str: The trace's unique ID in the format 'trace_<32_alphanumeric>' + + Notes: + - IDs are globally unique + - Used to link spans to their parent trace + - Can be used to look up traces in the dashboard """ pass @property @abc.abstractmethod def name(self) -> str: - """ - The name of the workflow being traced. + """Get the human-readable name of this workflow trace. + + Returns: + str: The workflow name (e.g., "Customer Service", "Data Processing") + + Notes: + - Should be descriptive and meaningful + - Used for grouping and filtering in the dashboard + - Helps identify the purpose of the trace """ pass @abc.abstractmethod def export(self) -> dict[str, Any] | None: - """ - Export the trace as a dictionary. + """Export the trace data as a serializable dictionary. + + Returns: + dict | None: Dictionary containing trace data, or None if tracing is disabled. + + Notes: + - Includes all spans and their data + - Used for sending traces to backends + - May include metadata and group ID """ pass class NoOpTrace(Trace): - """ - A no-op trace that will not be recorded. + """A no-op implementation of Trace that doesn't record any data. + + Used when tracing is disabled but trace operations still need to work. + Maintains proper context management but doesn't store or export any data. + + Example: + ```python + # When tracing is disabled, traces become NoOpTrace + with trace("Disabled Workflow") as t: + # Operations still work but nothing is recorded + await Runner.run(agent, "query") + ``` """ def __init__(self): @@ -101,13 +169,28 @@ def finish(self, reset_current: bool = False): @property def trace_id(self) -> str: + """The trace's unique identifier. + + Returns: + str: A unique ID for this trace. + """ return "no-op" @property def name(self) -> str: + """The workflow name for this trace. + + Returns: + str: Human-readable name describing this workflow. + """ return "no-op" def export(self) -> dict[str, Any] | None: + """Export the trace data as a dictionary. + + Returns: + dict | None: Trace data in exportable format, or None if no data. + """ return None