chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:39:17 +08:00
commit 4ed4e9ff99
1368 changed files with 334957 additions and 0 deletions
+292
View File
@@ -0,0 +1,292 @@
from __future__ import annotations
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import cast
REPO_ROOT = Path(__file__).resolve().parents[2]
SRC_ROOT = REPO_ROOT / "src"
def _run_python(script: str) -> dict[str, object]:
env = os.environ.copy()
pythonpath = env.get("PYTHONPATH")
if pythonpath:
env["PYTHONPATH"] = f"{SRC_ROOT}:{pythonpath}"
else:
env["PYTHONPATH"] = str(SRC_ROOT)
completed = subprocess.run(
[sys.executable, "-c", script],
cwd=REPO_ROOT,
env=env,
text=True,
capture_output=True,
check=True,
)
payload = json.loads(completed.stdout)
if not isinstance(payload, dict):
raise AssertionError("Subprocess payload must be a JSON object.")
return cast(dict[str, object], payload)
def test_import_agents_has_no_tracing_side_effects() -> None:
payload = _run_python(
"""
import gc
import json
import httpx
clients_before = sum(1 for obj in gc.get_objects() if isinstance(obj, httpx.Client))
import agents # noqa: F401
from agents.tracing import processors as tracing_processors
from agents.tracing import setup as tracing_setup
clients_after = sum(1 for obj in gc.get_objects() if isinstance(obj, httpx.Client))
print(
json.dumps(
{
"client_delta": clients_after - clients_before,
"provider_initialized": tracing_setup.GLOBAL_TRACE_PROVIDER is not None,
"exporter_initialized": tracing_processors._global_exporter is not None,
"processor_initialized": tracing_processors._global_processor is not None,
"shutdown_handler_registered": tracing_setup._SHUTDOWN_HANDLER_REGISTERED,
}
)
)
"""
)
assert payload["client_delta"] == 0
assert payload["provider_initialized"] is False
assert payload["exporter_initialized"] is False
assert payload["processor_initialized"] is False
assert payload["shutdown_handler_registered"] is False
def test_import_agents_does_not_require_sqlite3() -> None:
payload = _run_python(
"""
import importlib.abc
import json
import sys
class BlockSqlite3(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path, target=None):
if fullname in {"sqlite3", "_sqlite3"}:
raise ModuleNotFoundError(f"blocked optional backend module: {fullname}")
return None
sys.meta_path.insert(0, BlockSqlite3())
import agents
from agents import Agent, Runner
from agents.memory import Session, SessionSettings
print(
json.dumps(
{
"agent_name": Agent.__name__,
"runner_name": Runner.__name__,
"session_name": Session.__name__,
"settings_name": SessionSettings.__name__,
"sqlite3_loaded": "sqlite3" in sys.modules,
"private_sqlite3_loaded": "_sqlite3" in sys.modules,
"sqlite_session_loaded": "agents.memory.sqlite_session" in sys.modules,
"sqlite_session_exported": "SQLiteSession" in agents.__all__,
}
)
)
"""
)
assert payload["agent_name"] == "Agent"
assert payload["runner_name"] == "Runner"
assert payload["session_name"] == "Session"
assert payload["settings_name"] == "SessionSettings"
assert payload["sqlite3_loaded"] is False
assert payload["private_sqlite3_loaded"] is False
assert payload["sqlite_session_loaded"] is False
assert payload["sqlite_session_exported"] is True
def test_sqlite_session_top_level_export_is_lazy() -> None:
payload = _run_python(
"""
import json
import sys
import agents
loaded_after_import = "agents.memory.sqlite_session" in sys.modules
from agents import SQLiteSession
loaded_after_export = "agents.memory.sqlite_session" in sys.modules
print(
json.dumps(
{
"sqlite_session_name": SQLiteSession.__name__,
"loaded_after_import": loaded_after_import,
"loaded_after_export": loaded_after_export,
"sqlite3_loaded": "sqlite3" in sys.modules,
}
)
)
"""
)
assert payload["sqlite_session_name"] == "SQLiteSession"
assert payload["loaded_after_import"] is False
assert payload["loaded_after_export"] is True
assert payload["sqlite3_loaded"] is True
def test_get_trace_provider_lazily_initializes_defaults() -> None:
payload = _run_python(
"""
import json
from agents.tracing import setup as tracing_setup
from agents.tracing import processors as tracing_processors
provider_before = tracing_setup.GLOBAL_TRACE_PROVIDER
exporter_before = tracing_processors._global_exporter
processor_before = tracing_processors._global_processor
shutdown_before = tracing_setup._SHUTDOWN_HANDLER_REGISTERED
provider = tracing_setup.get_trace_provider()
provider_after = tracing_setup.GLOBAL_TRACE_PROVIDER
exporter_after = tracing_processors._global_exporter
processor_after = tracing_processors._global_processor
shutdown_after = tracing_setup._SHUTDOWN_HANDLER_REGISTERED
print(
json.dumps(
{
"provider_before": provider_before is not None,
"exporter_before": exporter_before is not None,
"processor_before": processor_before is not None,
"shutdown_before": shutdown_before,
"provider_after": provider_after is not None,
"exporter_after": exporter_after is not None,
"processor_after": processor_after is not None,
"shutdown_after": shutdown_after,
"provider_matches_global": provider_after is provider,
}
)
)
"""
)
assert payload["provider_before"] is False
assert payload["exporter_before"] is False
assert payload["processor_before"] is False
assert payload["shutdown_before"] is False
assert payload["provider_after"] is True
assert payload["exporter_after"] is True
assert payload["processor_after"] is True
assert payload["shutdown_after"] is True
assert payload["provider_matches_global"] is True
def test_get_trace_provider_bootstraps_once() -> None:
payload = _run_python(
"""
import json
from agents.tracing import processors as tracing_processors
from agents.tracing import setup as tracing_setup
registrations = []
def fake_register(fn):
registrations.append(fn)
return fn
tracing_setup.atexit.register = fake_register
tracing_setup.GLOBAL_TRACE_PROVIDER = None
tracing_setup._SHUTDOWN_HANDLER_REGISTERED = False
tracing_processors._global_exporter = None
tracing_processors._global_processor = None
first = tracing_setup.get_trace_provider()
second = tracing_setup.get_trace_provider()
print(
json.dumps(
{
"same_provider": first is second,
"shutdown_registration_count": sum(
1
for fn in registrations
if getattr(fn, "__name__", "") == "_shutdown_global_trace_provider"
),
"provider_initialized": tracing_setup.GLOBAL_TRACE_PROVIDER is not None,
"exporter_initialized": tracing_processors._global_exporter is not None,
"processor_initialized": tracing_processors._global_processor is not None,
}
)
)
"""
)
assert payload["same_provider"] is True
assert payload["shutdown_registration_count"] == 1
assert payload["provider_initialized"] is True
assert payload["exporter_initialized"] is True
assert payload["processor_initialized"] is True
def test_set_trace_provider_skips_default_bootstrap() -> None:
payload = _run_python(
"""
import json
from agents.tracing import processors as tracing_processors
from agents.tracing import setup as tracing_setup
from agents.tracing.provider import DefaultTraceProvider
registrations = []
def fake_register(fn):
registrations.append(fn)
return fn
tracing_setup.atexit.register = fake_register
tracing_setup.GLOBAL_TRACE_PROVIDER = None
tracing_setup._SHUTDOWN_HANDLER_REGISTERED = False
tracing_processors._global_exporter = None
tracing_processors._global_processor = None
custom_provider = DefaultTraceProvider()
tracing_setup.set_trace_provider(custom_provider)
retrieved_provider = tracing_setup.get_trace_provider()
print(
json.dumps(
{
"custom_provider_returned": retrieved_provider is custom_provider,
"shutdown_registration_count": sum(
1
for fn in registrations
if getattr(fn, "__name__", "") == "_shutdown_global_trace_provider"
),
"exporter_initialized": tracing_processors._global_exporter is not None,
"processor_initialized": tracing_processors._global_processor is not None,
}
)
)
"""
)
assert payload["custom_provider_returned"] is True
assert payload["shutdown_registration_count"] == 1
assert payload["exporter_initialized"] is False
assert payload["processor_initialized"] is False
+5
View File
@@ -0,0 +1,5 @@
from agents.tracing import logger as tracing_logger
def test_tracing_logger_is_configured() -> None:
assert tracing_logger.logger.name == "openai.agents.tracing"
+77
View File
@@ -0,0 +1,77 @@
from __future__ import annotations
from types import SimpleNamespace
from typing import Any, cast
import pytest
from agents.tracing.processors import BackendSpanExporter
from agents.tracing.spans import Span
from agents.tracing.traces import Trace
@pytest.mark.asyncio
async def test_processor_api_key(monkeypatch):
# If the API key is not set, it should be None
monkeypatch.delenv("OPENAI_API_KEY", None)
processor = BackendSpanExporter()
assert processor.api_key is None
# If we set it afterwards, it should be the new value
processor.set_api_key("test_api_key")
assert processor.api_key == "test_api_key"
@pytest.mark.asyncio
async def test_processor_api_key_from_env(monkeypatch):
# If the API key is not set at creation time but set before access time, it should be the new
# value
monkeypatch.delenv("OPENAI_API_KEY", None)
processor = BackendSpanExporter()
# If we set it afterwards, it should be the new value
monkeypatch.setenv("OPENAI_API_KEY", "foo_bar_123")
assert processor.api_key == "foo_bar_123"
def test_exporter_uses_item_api_keys(monkeypatch):
class DummyItem:
def __init__(self, key: str | None, payload: dict[str, str]):
self.tracing_api_key = key
self._payload = payload
def export(self) -> dict[str, str]:
return self._payload
calls: list[dict[str, Any]] = []
def fake_post(*, url, headers, json):
calls.append({"url": url, "headers": headers, "json": json})
return SimpleNamespace(status_code=200, text="ok")
exporter = BackendSpanExporter()
exporter.set_api_key("global-key")
monkeypatch.setattr(exporter, "_client", SimpleNamespace(post=fake_post))
exporter.export(
cast(
list[Trace | Span[Any]],
[
DummyItem("key-a", {"id": "a"}),
DummyItem(None, {"id": "b"}),
DummyItem("key-b", {"id": "c"}),
],
)
)
assert len(calls) == 3
auth_by_first_item = {
tuple(entry["id"] for entry in call["json"]["data"]): call["headers"]["Authorization"]
for call in calls
}
assert ("a",) in auth_by_first_item
assert ("b",) in auth_by_first_item
assert ("c",) in auth_by_first_item
assert auth_by_first_item[("a",)] == "Bearer key-a"
assert auth_by_first_item[("c",)] == "Bearer key-b"
assert auth_by_first_item[("b",)] == "Bearer global-key"
+23
View File
@@ -0,0 +1,23 @@
import pytest
from agents.tracing.processors import BackendSpanExporter
def test_set_api_key_preserves_env_fallback(monkeypatch: pytest.MonkeyPatch):
"""Test that set_api_key doesn't break environment variable fallback."""
monkeypatch.setenv("OPENAI_API_KEY", "env-key")
exporter = BackendSpanExporter()
# Initially should use env var
assert exporter.api_key == "env-key"
# Set explicit key
exporter.set_api_key("explicit-key")
assert exporter.api_key == "explicit-key"
# Clear explicit key and verify env fallback works
exporter._api_key = None
if "api_key" in exporter.__dict__:
del exporter.__dict__["api_key"]
assert exporter.api_key == "env-key"
+118
View File
@@ -0,0 +1,118 @@
from __future__ import annotations
import atexit
from typing import Any, cast
import pytest
from agents.tracing import (
processors as tracing_processors,
provider as tracing_provider,
setup as tracing_setup,
)
class _DummyProvider:
def __init__(self) -> None:
self.shutdown_calls = 0
def shutdown(self) -> None:
self.shutdown_calls += 1
class _DefaultProviderWithTimeout(tracing_provider.DefaultTraceProvider):
def __init__(self) -> None:
super().__init__()
self.shutdown_timeout: float | None = None
def shutdown(self, timeout: float | None = None) -> None:
self.shutdown_timeout = timeout
class _BootstrapProvider:
def __init__(self) -> None:
self.processors: list[Any] = []
self.shutdown_calls = 0
def register_processor(self, processor: Any) -> None:
self.processors.append(processor)
def shutdown(self) -> None:
self.shutdown_calls += 1
def test_shutdown_global_trace_provider_calls_shutdown(monkeypatch: pytest.MonkeyPatch) -> None:
provider = _DummyProvider()
monkeypatch.setattr(tracing_setup, "GLOBAL_TRACE_PROVIDER", provider)
tracing_setup._shutdown_global_trace_provider()
assert provider.shutdown_calls == 1
def test_shutdown_global_trace_provider_passes_timeout_to_default_provider(
monkeypatch: pytest.MonkeyPatch,
) -> None:
provider = _DefaultProviderWithTimeout()
monkeypatch.setattr(tracing_setup, "GLOBAL_TRACE_PROVIDER", provider)
tracing_setup._shutdown_global_trace_provider()
assert provider.shutdown_timeout == tracing_setup._DEFAULT_SHUTDOWN_TIMEOUT
def test_set_trace_provider_registers_shutdown_once(monkeypatch: pytest.MonkeyPatch) -> None:
registrations: list[Any] = []
def fake_register(callback: Any) -> Any:
registrations.append(callback)
return callback
first = _DummyProvider()
second = _DummyProvider()
monkeypatch.setattr(atexit, "register", fake_register)
monkeypatch.setattr(tracing_setup, "GLOBAL_TRACE_PROVIDER", None)
monkeypatch.setattr(tracing_setup, "_SHUTDOWN_HANDLER_REGISTERED", False)
tracing_setup.set_trace_provider(cast(Any, first))
tracing_setup.set_trace_provider(cast(Any, second))
assert cast(Any, tracing_setup.GLOBAL_TRACE_PROVIDER) is second
assert registrations == [tracing_setup._shutdown_global_trace_provider]
def test_get_trace_provider_returns_existing_provider(monkeypatch: pytest.MonkeyPatch) -> None:
provider = _DummyProvider()
def fail_register(_: Any) -> None:
raise AssertionError("atexit.register should not be called for an existing provider.")
monkeypatch.setattr(atexit, "register", fail_register)
monkeypatch.setattr(tracing_setup, "GLOBAL_TRACE_PROVIDER", provider)
assert cast(Any, tracing_setup.get_trace_provider()) is provider
def test_get_trace_provider_bootstraps_provider_in_process(
monkeypatch: pytest.MonkeyPatch,
) -> None:
registrations: list[Any] = []
default_processor = object()
def fake_register(callback: Any) -> Any:
registrations.append(callback)
return callback
monkeypatch.setattr(atexit, "register", fake_register)
monkeypatch.setattr(tracing_setup, "GLOBAL_TRACE_PROVIDER", None)
monkeypatch.setattr(tracing_setup, "_SHUTDOWN_HANDLER_REGISTERED", False)
monkeypatch.setattr(tracing_processors, "default_processor", lambda: default_processor)
monkeypatch.setattr(tracing_provider, "DefaultTraceProvider", _BootstrapProvider)
provider = tracing_setup.get_trace_provider()
assert isinstance(provider, _BootstrapProvider)
assert provider.processors == [default_processor]
assert tracing_setup.GLOBAL_TRACE_PROVIDER is provider
assert registrations == [tracing_setup._shutdown_global_trace_provider]
+263
View File
@@ -0,0 +1,263 @@
from __future__ import annotations
import logging
from uuid import uuid4
import agents.tracing.traces as trace_module
from agents.tracing import TracingConfig, set_tracing_disabled, trace
from agents.tracing.context import create_trace_for_run
from agents.tracing.scope import Scope
from agents.tracing.traces import (
NoOpTrace,
ReattachedTrace,
TraceImpl,
TraceState,
_started_trace_ids,
_started_trace_ids_lock,
)
def _new_trace_id() -> str:
return f"trace_{uuid4().hex}"
def _clear_started_trace_ids() -> None:
with _started_trace_ids_lock:
_started_trace_ids.clear()
def _mark_trace_as_started(
*,
workflow_name: str = "workflow",
group_id: str | None = "group-1",
metadata: dict[str, str] | None = None,
tracing_api_key: str | None = None,
) -> TraceState:
metadata = metadata or {"key": "value"}
trace_id = _new_trace_id()
Scope.set_current_trace(None)
set_tracing_disabled(False)
original = trace(
workflow_name=workflow_name,
trace_id=trace_id,
group_id=group_id,
metadata=metadata,
tracing={"api_key": tracing_api_key} if tracing_api_key is not None else None,
)
assert isinstance(original, TraceImpl)
original.start()
original.finish()
trace_state = TraceState.from_trace(original)
assert trace_state is not None
return trace_state
def test_create_trace_for_run_reattaches_matching_started_trace() -> None:
trace_state = _mark_trace_as_started(tracing_api_key="trace-key")
created = create_trace_for_run(
workflow_name="workflow",
trace_id=trace_state.trace_id,
group_id=trace_state.group_id,
metadata=dict(trace_state.metadata or {}),
tracing={"api_key": "trace-key"},
disabled=False,
trace_state=trace_state,
reattach_resumed_trace=True,
)
assert isinstance(created, ReattachedTrace)
assert created.trace_id == trace_state.trace_id
def test_create_trace_for_run_does_not_reattach_after_trace_state_reload() -> None:
trace_state = _mark_trace_as_started()
_clear_started_trace_ids()
created = create_trace_for_run(
workflow_name="workflow",
trace_id=trace_state.trace_id,
group_id=trace_state.group_id,
metadata=dict(trace_state.metadata or {}),
tracing=None,
disabled=False,
trace_state=trace_state,
reattach_resumed_trace=True,
)
assert isinstance(created, TraceImpl)
assert not isinstance(created, ReattachedTrace)
def test_create_trace_for_run_does_not_reattach_when_trace_id_differs() -> None:
trace_state = _mark_trace_as_started()
created = create_trace_for_run(
workflow_name="workflow",
trace_id=_new_trace_id(),
group_id=trace_state.group_id,
metadata=dict(trace_state.metadata or {}),
tracing=None,
disabled=False,
trace_state=trace_state,
reattach_resumed_trace=True,
)
assert isinstance(created, TraceImpl)
assert not isinstance(created, ReattachedTrace)
def test_create_trace_for_run_reattaches_stripped_trace_key_with_matching_resume_key() -> None:
trace_state = _mark_trace_as_started(tracing_api_key="trace-key")
stripped_trace_state = TraceState.from_json(trace_state.to_json())
assert stripped_trace_state is not None
assert stripped_trace_state.tracing_api_key is None
assert stripped_trace_state.tracing_api_key_hash == trace_state.tracing_api_key_hash
created = create_trace_for_run(
workflow_name="workflow",
trace_id=stripped_trace_state.trace_id,
group_id=stripped_trace_state.group_id,
metadata=dict(stripped_trace_state.metadata or {}),
tracing={"api_key": "trace-key"},
disabled=False,
trace_state=stripped_trace_state,
reattach_resumed_trace=True,
)
assert isinstance(created, ReattachedTrace)
assert created.tracing_api_key == "trace-key"
def test_create_trace_for_run_does_not_reattach_stripped_trace_key_with_mismatch() -> None:
trace_state = _mark_trace_as_started(tracing_api_key="trace-key")
stripped_trace_state = TraceState.from_json(trace_state.to_json())
assert stripped_trace_state is not None
created = create_trace_for_run(
workflow_name="workflow",
trace_id=stripped_trace_state.trace_id,
group_id=stripped_trace_state.group_id,
metadata=dict(stripped_trace_state.metadata or {}),
tracing={"api_key": "other-trace-key"},
disabled=False,
trace_state=stripped_trace_state,
reattach_resumed_trace=True,
)
assert isinstance(created, TraceImpl)
assert not isinstance(created, ReattachedTrace)
def test_create_trace_for_run_does_not_reattach_when_settings_mismatch() -> None:
trace_state = _mark_trace_as_started(tracing_api_key="trace-key")
mismatch_cases: list[tuple[str, str | None, dict[str, str], TracingConfig]] = [
(
"workflow-override",
trace_state.group_id,
dict(trace_state.metadata or {}),
{"api_key": "trace-key"},
),
(
"workflow",
"group-override",
dict(trace_state.metadata or {}),
{"api_key": "trace-key"},
),
(
"workflow",
trace_state.group_id,
{"key": "override"},
{"api_key": "trace-key"},
),
(
"workflow",
trace_state.group_id,
dict(trace_state.metadata or {}),
{"api_key": "other-trace-key"},
),
]
for workflow_name, group_id, metadata, tracing in mismatch_cases:
Scope.set_current_trace(None)
created = create_trace_for_run(
workflow_name=workflow_name,
trace_id=trace_state.trace_id,
group_id=group_id,
metadata=metadata,
tracing=tracing,
disabled=False,
trace_state=trace_state,
reattach_resumed_trace=True,
)
assert isinstance(created, TraceImpl)
assert not isinstance(created, ReattachedTrace)
def test_create_trace_for_run_respects_disabled_flag_for_resume() -> None:
trace_state = _mark_trace_as_started()
created = create_trace_for_run(
workflow_name="workflow",
trace_id=trace_state.trace_id,
group_id=trace_state.group_id,
metadata=dict(trace_state.metadata or {}),
tracing=None,
disabled=True,
trace_state=trace_state,
reattach_resumed_trace=True,
)
assert isinstance(created, NoOpTrace)
def test_create_trace_for_run_uses_existing_current_trace() -> None:
trace_state = _mark_trace_as_started()
outer_trace = trace(workflow_name="outer", trace_id=_new_trace_id())
assert isinstance(outer_trace, TraceImpl)
with outer_trace:
created = create_trace_for_run(
workflow_name="workflow",
trace_id=trace_state.trace_id,
group_id=trace_state.group_id,
metadata=dict(trace_state.metadata or {}),
tracing=None,
disabled=False,
trace_state=trace_state,
reattach_resumed_trace=True,
)
assert created is None
def test_trace_logs_warning_when_current_trace_exists(
caplog,
) -> None:
Scope.set_current_trace(None)
outer_trace = trace(workflow_name="outer", trace_id=_new_trace_id())
assert isinstance(outer_trace, TraceImpl)
with outer_trace:
with caplog.at_level(logging.WARNING, logger="openai.agents"):
inner_trace = trace(workflow_name="inner", trace_id=_new_trace_id())
assert isinstance(inner_trace, TraceImpl)
assert "Trace already exists" in caplog.text
def test_started_trace_id_cache_is_bounded(monkeypatch) -> None:
_clear_started_trace_ids()
monkeypatch.setattr(trace_module, "_MAX_STARTED_TRACE_IDS", 2)
first = _mark_trace_as_started(metadata={"key": "first"})
second = _mark_trace_as_started(metadata={"key": "second"})
third = _mark_trace_as_started(metadata={"key": "third"})
assert len(_started_trace_ids) == 2
assert list(_started_trace_ids) == [second.trace_id, third.trace_id]
assert first.trace_id not in _started_trace_ids
+127
View File
@@ -0,0 +1,127 @@
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
+112
View File
@@ -0,0 +1,112 @@
import logging
from agents.tracing.provider import DefaultTraceProvider
from agents.tracing.scope import Scope
from agents.tracing.span_data import AgentSpanData
from agents.tracing.spans import NoOpSpan, SpanImpl
from agents.tracing.traces import NoOpTrace, TraceImpl
def test_env_read_on_first_use(monkeypatch):
"""Env flag set before first trace disables tracing."""
monkeypatch.setenv("OPENAI_AGENTS_DISABLE_TRACING", "1")
provider = DefaultTraceProvider()
trace = provider.create_trace("demo")
assert isinstance(trace, NoOpTrace)
def test_env_cached_after_first_use(monkeypatch):
"""Env flag is cached after the first trace and later env changes do not flip it."""
monkeypatch.setenv("OPENAI_AGENTS_DISABLE_TRACING", "0")
provider = DefaultTraceProvider()
first = provider.create_trace("first")
assert isinstance(first, TraceImpl)
# Change env after first use; cached value should keep tracing enabled.
monkeypatch.setenv("OPENAI_AGENTS_DISABLE_TRACING", "1")
second = provider.create_trace("second")
assert isinstance(second, TraceImpl)
def test_manual_override_after_cache(monkeypatch):
"""Manual toggle still works after env value is cached."""
monkeypatch.setenv("OPENAI_AGENTS_DISABLE_TRACING", "0")
provider = DefaultTraceProvider()
provider.create_trace("warmup")
provider.set_disabled(True)
disabled = provider.create_trace("disabled")
assert isinstance(disabled, NoOpTrace)
provider.set_disabled(False)
enabled = provider.create_trace("enabled")
assert isinstance(enabled, TraceImpl)
def test_manual_override_env_disable(monkeypatch):
"""Manual enable can override env disable flag."""
monkeypatch.setenv("OPENAI_AGENTS_DISABLE_TRACING", "1")
provider = DefaultTraceProvider()
env_disabled = provider.create_trace("env_disabled")
assert isinstance(env_disabled, NoOpTrace)
provider.set_disabled(False)
reenabled = provider.create_trace("reenabled")
assert isinstance(reenabled, TraceImpl)
def test_missing_active_trace_logs_debug_for_noop_span(caplog):
Scope.set_current_trace(None)
Scope.set_current_span(None)
provider = DefaultTraceProvider()
with caplog.at_level(logging.DEBUG, logger="openai.agents"):
span = provider.create_span(AgentSpanData(name="missing-trace"))
assert isinstance(span, NoOpSpan)
assert "No active trace" in caplog.text
assert not [record for record in caplog.records if record.levelno >= logging.ERROR]
def test_noop_span_id_returns_noop_span_with_active_trace():
Scope.set_current_trace(None)
Scope.set_current_span(None)
provider = DefaultTraceProvider()
trace = provider.create_trace("active", trace_id="trace_123")
trace_token = Scope.set_current_trace(trace)
try:
span = provider.create_span(AgentSpanData(name="invalid"), span_id="no-op")
finally:
Scope.reset_current_trace(trace_token)
assert isinstance(span, NoOpSpan)
def test_noop_current_span_id_does_not_become_parent_id():
Scope.set_current_trace(None)
Scope.set_current_span(None)
provider = DefaultTraceProvider()
trace = provider.create_trace("active", trace_id="trace_123")
invalid_parent = SpanImpl(
trace_id="trace_123",
span_id="no-op",
parent_id=None,
processor=provider._multi_processor,
span_data=AgentSpanData(name="invalid-parent"),
tracing_api_key=None,
)
trace_token = Scope.set_current_trace(trace)
span_token = Scope.set_current_span(invalid_parent)
try:
span = provider.create_span(AgentSpanData(name="child"))
finally:
Scope.reset_current_span(span_token)
Scope.reset_current_trace(trace_token)
assert isinstance(span, NoOpSpan)