Skip to content

Commit 777ca59

Browse files
feat(obs): install the openai-agents bridge so Runner turns produce spans
The traces signal wired but produced nothing for the fleet's dominant agent shape. sgp_obs.init() installs most of the traces wiring itself; the openai-agents bridge is the one piece it does not. Measured on 0.16.0 after a plain init() with traces on: GenAI attempt span processor installed litellm logical adapter installed (_SgpObsLiteLLMLogger) httpx / aiohttp egress instrumented openai-agents bridge NOT installed That last one carries roughly 83% of model-calling agents, so without it a Runner turn contributes no logical model-operation spans and "traces on" looks like it does nothing at all. This is what the obs-test-* agents in agentex-agents#2183 each hand-roll: every one of the five ships an identical 114-line obs_bootstrap.py. Comparing that file against what init() already does, four of its five steps are redundant — the httpx and aiohttp instrumentors (init's _instrument_egress does them, and 0.16.0 deliberately reuses an already-instrumented one rather than warning), the attempt processor, and the litellm adapter. Only the bridge was load-bearing. With this change an adopting agent's bootstrap collapses to nothing, except the parts that are genuinely agent-specific: capture_turn's redaction allowlist, and its business span call sites. Installed unconditionally when traces are wired, because openai-agents is a hard dependency of this SDK, so `agents` is importable in every agent. The call is idempotent. A False return means `agents` was somehow not importable, which should be impossible here, so that warns rather than passing silently. Verified through the SDK's own entry point, not the library's: SDK init status: wired:metrics,traces attempt span processor : True openai-agents bridge : True litellm adapter : ['_SgpObsLiteLLMLogger'] 88 tests, ruff clean, pyright 0 errors, all with sgp-obs absent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 60daf54 commit 777ca59

2 files changed

Lines changed: 104 additions & 2 deletions

File tree

‎src/agentex/lib/core/observability/sgp_obs_setup.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,54 @@ def init_sgp_obs(app: Any = None) -> str:
141141
return _status
142142

143143
if "traces" in handles:
144+
_install_openai_agents_bridge()
144145
_warn_if_correlation_backend_mismatched()
145146

146147
_status = "wired:" + ",".join(sorted(handles))
147148
logger.info("sgp-obs wired (%s)", _status)
148149
return _status
149150

150151

152+
def _install_openai_agents_bridge() -> bool:
153+
"""Register sgp-obs' openai-agents trace processor, so a ``Runner`` turn produces
154+
logical model-operation spans.
155+
156+
This is the one piece of traces wiring ``sgp_obs.init()`` does NOT do for itself.
157+
Measured on 0.16.0 after a plain ``init()`` with the traces signal on:
158+
159+
GenAI attempt span processor installed
160+
litellm logical adapter installed
161+
httpx / aiohttp egress instrumented
162+
openai-agents bridge NOT installed
163+
164+
which is why the obs-test agents each carry a hand-written bootstrap that calls it.
165+
It matters more than the others here: roughly 83% of model-calling agents reach the
166+
model through the openai-agents ``Runner``, so without this the dominant path
167+
contributes no logical spans and "traces on" looks like it does nothing.
168+
169+
Unconditional because ``openai-agents`` is a hard dependency of this SDK, so the
170+
``agents`` package is importable in every agent. The call is idempotent and returns
171+
False rather than raising when the SDK is somehow absent.
172+
"""
173+
try:
174+
from sgp_obs.traces import install_openai_agents_bridge # type: ignore[import-not-found]
175+
176+
installed = bool(install_openai_agents_bridge())
177+
if installed:
178+
logger.debug("sgp-obs openai-agents bridge installed")
179+
else:
180+
# Only reachable if `agents` is not importable, which should not happen
181+
# while openai-agents is a hard dependency — so say so rather than shrug.
182+
logger.warning(
183+
"sgp-obs openai-agents bridge did not install; Runner turns will "
184+
"produce no logical model-operation spans."
185+
)
186+
return installed
187+
except Exception: # pragma: no cover - telemetry must never break startup
188+
logger.debug("sgp-obs openai-agents bridge unavailable", exc_info=True)
189+
return False
190+
191+
151192
def _warn_if_correlation_backend_mismatched() -> None:
152193
"""Warn when sgp-obs is exporting OTel traces but the SDK's business-span
153194
correlation is still reading ddtrace.

‎src/agentex/lib/core/observability/tests/test_sgp_obs_setup.py‎

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,21 @@ def caplog_at(monkeypatch):
5252
yield records
5353

5454

55-
def _fake_sgp_obs(monkeypatch, init=None, shutdown=None):
56-
"""Install a stand-in ``sgp_obs`` module whose entry points we control."""
55+
def _fake_sgp_obs(monkeypatch, init=None, shutdown=None, bridge=None):
56+
"""Install a stand-in ``sgp_obs`` module whose entry points we control.
57+
58+
``bridge`` stands in for ``sgp_obs.traces.install_openai_agents_bridge``; it lives
59+
on a fake ``sgp_obs.traces`` submodule because that is how the SDK imports it.
60+
"""
5761
module = type(sys)("sgp_obs")
5862
module.init = init if init is not None else (lambda **_kwargs: {"metrics": object()})
5963
if shutdown is not None:
6064
module.shutdown = shutdown
6165
monkeypatch.setitem(sys.modules, "sgp_obs", module)
66+
67+
traces = type(sys)("sgp_obs.traces")
68+
traces.install_openai_agents_bridge = bridge if bridge is not None else (lambda: True)
69+
monkeypatch.setitem(sys.modules, "sgp_obs.traces", traces)
6270
return module
6371

6472

@@ -339,3 +347,56 @@ def test_the_json_rpc_route_is_still_mounted(self, monkeypatch):
339347
_block_sgp_obs_import(monkeypatch)
340348
routes = {getattr(r, "path", None) for r in BaseACPServer().routes}
341349
assert {"/healthz", "/api"} <= routes
350+
351+
352+
class TestOpenAIAgentsBridge:
353+
"""sgp_obs.init() installs the GenAI attempt processor, the litellm adapter and the
354+
egress instrumentors by itself, but NOT the openai-agents bridge (measured on
355+
0.16.0). That is the path ~83% of model-calling agents take, so the SDK installs it
356+
— otherwise "traces on" produces no logical model-operation spans for most agents.
357+
"""
358+
359+
def test_installed_when_traces_are_wired(self, monkeypatch):
360+
calls = []
361+
_fake_sgp_obs(
362+
monkeypatch,
363+
init=lambda **_kwargs: {"traces": object()},
364+
bridge=lambda: calls.append(True) or True,
365+
)
366+
monkeypatch.setenv("SGP_OBS_MODE", "lgtm")
367+
assert init_sgp_obs() == "wired:traces"
368+
assert calls == [True]
369+
370+
def test_not_installed_without_the_traces_signal(self, monkeypatch):
371+
"""A metrics-only agent has no span pipeline to feed, so installing an
372+
openai-agents trace processor would be pointless work at startup."""
373+
calls = []
374+
_fake_sgp_obs(
375+
monkeypatch,
376+
init=lambda **_kwargs: {"metrics": object()},
377+
bridge=lambda: calls.append(True) or True,
378+
)
379+
assert init_sgp_obs() == "wired:metrics"
380+
assert calls == []
381+
382+
def test_a_bridge_that_declines_is_reported(self, monkeypatch, caplog):
383+
"""False means the `agents` SDK was not importable. openai-agents is a hard
384+
dependency of this package, so that should be impossible — say so rather than
385+
swallow it."""
386+
monkeypatch.setenv("SGP_OBS_MODE", "lgtm")
387+
_fake_sgp_obs(
388+
monkeypatch, init=lambda **_kwargs: {"traces": object()}, bridge=lambda: False
389+
)
390+
with caplog.at_level("WARNING"):
391+
assert init_sgp_obs() == "wired:traces"
392+
assert "openai-agents bridge" in caplog.text
393+
394+
def test_a_raising_bridge_does_not_stop_startup(self, monkeypatch):
395+
def boom():
396+
raise RuntimeError("sgp-obs internals moved")
397+
398+
monkeypatch.setenv("SGP_OBS_MODE", "lgtm")
399+
_fake_sgp_obs(
400+
monkeypatch, init=lambda **_kwargs: {"traces": object()}, bridge=boom
401+
)
402+
assert init_sgp_obs() == "wired:traces"

0 commit comments

Comments
 (0)