Files
2026-07-13 12:39:17 +08:00

128 lines
3.6 KiB
Python

import logging
from typing import Any, cast
from agents.tracing.processor_interface import TracingProcessor
from agents.tracing.scope import Scope
from agents.tracing.spans import Span
from agents.tracing.traces import NoOpTrace, Trace, TraceImpl, TraceState, reattach_trace
class DummyProcessor(TracingProcessor):
def __init__(self) -> None:
self.started: list[str] = []
self.ended: list[str] = []
def on_trace_start(self, trace: Trace) -> None:
self.started.append(trace.trace_id)
def on_trace_end(self, trace: Trace) -> None:
self.ended.append(trace.trace_id)
def on_span_start(self, span: Span[Any]) -> None:
return None
def on_span_end(self, span: Span[Any]) -> None:
return None
def shutdown(self) -> None:
return None
def force_flush(self) -> None:
return None
def test_no_op_trace_double_enter_logs_error(caplog) -> None:
Scope.set_current_trace(None)
trace = NoOpTrace()
with caplog.at_level(logging.ERROR):
trace.start()
trace.__enter__()
trace.__enter__() # Second entry should log missing context token error
assert trace._started is True
trace.__exit__(None, None, None)
def test_trace_impl_lifecycle_sets_scope() -> None:
Scope.set_current_trace(None)
processor = DummyProcessor()
trace = TraceImpl(
name="test-trace",
trace_id="trace-123",
group_id="group-1",
metadata={"k": "v"},
processor=processor,
)
assert Scope.get_current_trace() is None
with trace as current:
assert current.trace_id == "trace-123"
assert Scope.get_current_trace() is trace
assert processor.started == ["trace-123"]
assert processor.ended == ["trace-123"]
assert Scope.get_current_trace() is None
assert trace.export() == {
"object": "trace",
"id": "trace-123",
"workflow_name": "test-trace",
"group_id": "group-1",
"metadata": {"k": "v"},
}
def test_trace_impl_double_start_and_finish_without_start(caplog) -> None:
Scope.set_current_trace(None)
processor = DummyProcessor()
trace = TraceImpl(
name="double-start",
trace_id=None,
group_id=None,
metadata=None,
processor=processor,
)
trace.start()
trace.start() # should no-op when already started
trace.finish(reset_current=True)
with caplog.at_level(logging.ERROR):
trace._started = True
trace._prev_context_token = None
trace.__enter__() # logs when started but no context token
trace.finish(reset_current=True)
fresh = TraceImpl(
name="finish-no-start",
trace_id=None,
group_id=None,
metadata=None,
processor=processor,
)
fresh.finish(reset_current=True) # should not raise when never started
def test_reattached_trace_restores_scope_without_reemitting_processor_events() -> None:
Scope.set_current_trace(None)
processor = DummyProcessor()
original = TraceImpl(
name="test-trace",
trace_id="trace-123",
group_id="group-1",
metadata={"k": "v"},
processor=processor,
)
with original:
pass
restored = reattach_trace(cast(TraceState, TraceState.from_trace(original)))
assert restored is not None
with restored as current:
assert current.trace_id == "trace-123"
assert Scope.get_current_trace() is restored
assert processor.started == ["trace-123"]
assert processor.ended == ["trace-123"]
assert Scope.get_current_trace() is None