Skip to content

Commit 9c9510d

Browse files
feat(integrations): openai-agents: add usage and response model reporting for chat and invoke_agent spans (#5157)
#### Issues Closes https://linear.app/getsentry/issue/TET-1457/py-openai-agents-attributes-missing --------- Co-authored-by: Alexander Alderman Webb <alexander.webb@sentry.io>
1 parent 4df0056 commit 9c9510d

File tree

6 files changed

+615
-8
lines changed

6 files changed

+615
-8
lines changed

sentry_sdk/integrations/openai_agents/patches/agent_run.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
if TYPE_CHECKING:
99
from typing import Any, Optional
1010

11+
from sentry_sdk.tracing import Span
12+
1113
try:
1214
import agents
1315
except ImportError:
@@ -27,13 +29,15 @@ def _patch_agent_run():
2729
original_execute_final_output = agents._run_impl.RunImpl.execute_final_output
2830

2931
def _start_invoke_agent_span(context_wrapper, agent, kwargs):
30-
# type: (agents.RunContextWrapper, agents.Agent, dict[str, Any]) -> None
32+
# type: (agents.RunContextWrapper, agents.Agent, dict[str, Any]) -> Span
3133
"""Start an agent invocation span"""
3234
# Store the agent on the context wrapper so we can access it later
3335
context_wrapper._sentry_current_agent = agent
3436
span = invoke_agent_span(context_wrapper, agent, kwargs)
3537
context_wrapper._sentry_agent_span = span
3638

39+
return span
40+
3741
def _end_invoke_agent_span(context_wrapper, agent, output=None):
3842
# type: (agents.RunContextWrapper, agents.Agent, Optional[Any]) -> None
3943
"""End the agent invocation span"""
@@ -73,7 +77,8 @@ async def patched_run_single_turn(cls, *args, **kwargs):
7377
if current_agent and current_agent != agent:
7478
_end_invoke_agent_span(context_wrapper, current_agent)
7579

76-
_start_invoke_agent_span(context_wrapper, agent, kwargs)
80+
span = _start_invoke_agent_span(context_wrapper, agent, kwargs)
81+
agent._sentry_agent_span = span
7782

7883
# Call original method with all the correct parameters
7984
result = await original_run_single_turn(*args, **kwargs)

sentry_sdk/integrations/openai_agents/patches/models.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from sentry_sdk.integrations import DidNotEnable
44

55
from ..spans import ai_client_span, update_ai_client_span
6+
from sentry_sdk.consts import SPANDATA
67

78
from typing import TYPE_CHECKING
89

@@ -33,13 +34,37 @@ def wrapped_get_model(cls, agent, run_config):
3334
model = original_get_model(agent, run_config)
3435
original_get_response = model.get_response
3536

37+
# Wrap _fetch_response if it exists (for OpenAI models) to capture raw response model
38+
if hasattr(model, "_fetch_response"):
39+
original_fetch_response = model._fetch_response
40+
41+
@wraps(original_fetch_response)
42+
async def wrapped_fetch_response(*args, **kwargs):
43+
# type: (*Any, **Any) -> Any
44+
response = await original_fetch_response(*args, **kwargs)
45+
if hasattr(response, "model"):
46+
agent._sentry_raw_response_model = str(response.model)
47+
return response
48+
49+
model._fetch_response = wrapped_fetch_response
50+
3651
@wraps(original_get_response)
3752
async def wrapped_get_response(*args, **kwargs):
3853
# type: (*Any, **Any) -> Any
3954
with ai_client_span(agent, kwargs) as span:
4055
result = await original_get_response(*args, **kwargs)
4156

42-
update_ai_client_span(span, agent, kwargs, result)
57+
response_model = getattr(agent, "_sentry_raw_response_model", None)
58+
if response_model:
59+
agent_span = getattr(agent, "_sentry_agent_span", None)
60+
if agent_span:
61+
agent_span.set_data(
62+
SPANDATA.GEN_AI_RESPONSE_MODEL, response_model
63+
)
64+
65+
delattr(agent, "_sentry_raw_response_model")
66+
67+
update_ai_client_span(span, agent, kwargs, result, response_model)
4368

4469
return result
4570

sentry_sdk/integrations/openai_agents/patches/runner.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ async def wrapper(*args, **kwargs):
2626
# Isolate each workflow so that when agents are run in asyncio tasks they
2727
# don't touch each other's scopes
2828
with sentry_sdk.isolation_scope():
29-
agent = args[0]
29+
# Clone agent because agent invocation spans are attached per run.
30+
agent = args[0].clone()
3031
with agent_workflow_span(agent):
3132
result = None
33+
args = (agent, *args[1:])
3234
try:
3335
result = await original_func(*args, **kwargs)
3436
return result

sentry_sdk/integrations/openai_agents/spans/ai_client.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
if TYPE_CHECKING:
1616
from agents import Agent
17-
from typing import Any
17+
from typing import Any, Optional
1818

1919

2020
def ai_client_span(agent, get_response_kwargs):
@@ -35,8 +35,14 @@ def ai_client_span(agent, get_response_kwargs):
3535
return span
3636

3737

38-
def update_ai_client_span(span, agent, get_response_kwargs, result):
39-
# type: (sentry_sdk.tracing.Span, Agent, dict[str, Any], Any) -> None
38+
def update_ai_client_span(
39+
span, agent, get_response_kwargs, result, response_model=None
40+
):
41+
# type: (sentry_sdk.tracing.Span, Agent, dict[str, Any], Any, Optional[str]) -> None
4042
_set_usage_data(span, result.usage)
4143
_set_output_data(span, result)
4244
_create_mcp_execute_tool_spans(span, result)
45+
46+
# Set response model if captured from raw response
47+
if response_model is not None:
48+
span.set_data(SPANDATA.GEN_AI_RESPONSE_MODEL, response_model)

sentry_sdk/integrations/openai_agents/spans/invoke_agent.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from sentry_sdk.utils import safe_serialize
1111

1212
from ..consts import SPAN_ORIGIN
13-
from ..utils import _set_agent_data
13+
from ..utils import _set_agent_data, _set_usage_data
1414

1515
from typing import TYPE_CHECKING
1616

@@ -84,6 +84,10 @@ def update_invoke_agent_span(context, agent, output):
8484
span = getattr(context, "_sentry_agent_span", None)
8585

8686
if span:
87+
# Add aggregated usage data from context_wrapper
88+
if hasattr(context, "usage"):
89+
_set_usage_data(span, context.usage)
90+
8791
if should_send_default_pii():
8892
set_data_normalized(
8993
span, SPANDATA.GEN_AI_RESPONSE_TEXT, output, unpack=False

0 commit comments

Comments
 (0)