Skip to content

Commit 46c6f01

Browse files
fix(openai-agents): Stop setting transaction status when child span fails (getsentry#6303)
Stop modifying the transaction, since the transaction may not be managed by the OpenAI Agents integration. Stop closing spans inside `update_invoke_agent_span()`. Change the function to accept a span parameter and finish the span at call sites with `Span.__exit__()`, passing in the exception tuple if relevant.
1 parent 9084c9a commit 46c6f01

7 files changed

Lines changed: 120 additions & 88 deletions

File tree

sentry_sdk/integrations/openai_agents/patches/agent_run.py

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33

44
from sentry_sdk.consts import SPANDATA
55
from sentry_sdk.integrations import DidNotEnable
6-
from sentry_sdk.tracing_utils import set_span_errored
76
from sentry_sdk.utils import capture_internal_exceptions, reraise
87

98
from ..spans import (
10-
end_invoke_agent_span,
119
handoff_span,
1210
invoke_agent_span,
11+
update_invoke_agent_span,
1312
)
1413

1514
if TYPE_CHECKING:
@@ -65,7 +64,13 @@ def _maybe_start_agent_span(
6564
if _has_active_agent_span(context_wrapper):
6665
current_agent = _get_current_agent(context_wrapper)
6766
if current_agent and current_agent != agent:
68-
end_invoke_agent_span(context_wrapper, current_agent)
67+
span = getattr(context_wrapper, "_sentry_agent_span", None)
68+
if span:
69+
update_invoke_agent_span(
70+
span=span, context=context_wrapper, agent=agent
71+
)
72+
span.__exit__(None, None, None)
73+
delattr(context_wrapper, "_sentry_agent_span")
6974

7075
# Store the agent on the context wrapper so we can access it later
7176
context_wrapper._sentry_current_agent = agent
@@ -103,13 +108,22 @@ async def _run_single_turn(
103108
context_wrapper, agent, should_run_agent_start_hooks, kwargs
104109
)
105110

111+
if span is None or span.timestamp is not None:
112+
return await original_run_single_turn(*args, **kwargs)
113+
106114
try:
107115
result = await original_run_single_turn(*args, **kwargs)
108116
except Exception:
109-
if span is not None and span.timestamp is None:
110-
set_span_errored(span)
111-
end_invoke_agent_span(context_wrapper, agent)
112-
reraise(*sys.exc_info())
117+
exc_info = sys.exc_info()
118+
with capture_internal_exceptions():
119+
span = getattr(context_wrapper, "_sentry_agent_span", None)
120+
if span:
121+
update_invoke_agent_span(
122+
span=span, context=context_wrapper, agent=agent
123+
)
124+
span.__exit__(*exc_info)
125+
delattr(context_wrapper, "_sentry_agent_span")
126+
reraise(*exc_info)
113127

114128
return result
115129

@@ -174,14 +188,21 @@ async def _run_single_turn_streamed(
174188
is_streaming=True,
175189
)
176190

191+
if span is None or span.timestamp is not None:
192+
return await original_run_single_turn_streamed(*args, **kwargs)
193+
177194
try:
178195
result = await original_run_single_turn_streamed(*args, **kwargs)
179196
except Exception:
180197
exc_info = sys.exc_info()
181198
with capture_internal_exceptions():
182-
if span is not None and span.timestamp is None:
183-
set_span_errored(span)
184-
end_invoke_agent_span(context_wrapper, agent)
199+
span = getattr(context_wrapper, "_sentry_agent_span", None)
200+
if span:
201+
update_invoke_agent_span(
202+
span=span, context=context_wrapper, agent=agent
203+
)
204+
span.__exit__(*exc_info)
205+
delattr(context_wrapper, "_sentry_agent_span")
185206
_close_streaming_workflow_span(agent)
186207
reraise(*exc_info)
187208

@@ -211,18 +232,37 @@ async def _execute_handoffs(
211232
handoff_agent_name = first_handoff.handoff.agent_name
212233
handoff_span(context_wrapper, agent, handoff_agent_name)
213234

235+
if not agent or not context_wrapper or not _has_active_agent_span(context_wrapper):
236+
# Call original method with all parameters
237+
try:
238+
return await original_execute_handoffs(*args, **kwargs)
239+
except Exception:
240+
exc_info = sys.exc_info()
241+
with capture_internal_exceptions():
242+
_close_streaming_workflow_span(agent)
243+
reraise(*exc_info)
244+
214245
# Call original method with all parameters
215246
try:
216247
result = await original_execute_handoffs(*args, **kwargs)
217248
except Exception:
218249
exc_info = sys.exc_info()
219250
with capture_internal_exceptions():
220251
_close_streaming_workflow_span(agent)
252+
span = getattr(context_wrapper, "_sentry_agent_span", None)
253+
if span:
254+
update_invoke_agent_span(
255+
span=span, context=context_wrapper, agent=agent
256+
)
257+
span.__exit__(*exc_info)
258+
delattr(context_wrapper, "_sentry_agent_span")
221259
reraise(*exc_info)
222-
finally:
223-
# End span for current agent after handoff processing is complete
224-
if agent and context_wrapper and _has_active_agent_span(context_wrapper):
225-
end_invoke_agent_span(context_wrapper, agent)
260+
261+
span = getattr(context_wrapper, "_sentry_agent_span", None)
262+
if span:
263+
update_invoke_agent_span(span=span, context=context_wrapper, agent=agent)
264+
span.__exit__(None, None, None)
265+
delattr(context_wrapper, "_sentry_agent_span")
226266

227267
return result
228268

@@ -243,13 +283,36 @@ async def _execute_final_output(
243283
context_wrapper = kwargs.get("context_wrapper")
244284
final_output = kwargs.get("final_output")
245285

286+
if not agent or not context_wrapper or not _has_active_agent_span(context_wrapper):
287+
try:
288+
return await original_execute_final_output(*args, **kwargs)
289+
finally:
290+
with capture_internal_exceptions():
291+
# For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper)
292+
_close_streaming_workflow_span(agent)
293+
246294
try:
247295
result = await original_execute_final_output(*args, **kwargs)
248-
finally:
296+
except Exception:
297+
exc_info = sys.exc_info()
249298
with capture_internal_exceptions():
250-
if agent and context_wrapper and _has_active_agent_span(context_wrapper):
251-
end_invoke_agent_span(context_wrapper, agent, final_output)
252299
# For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper)
253300
_close_streaming_workflow_span(agent)
301+
span = getattr(context_wrapper, "_sentry_agent_span", None)
302+
if span:
303+
update_invoke_agent_span(
304+
span=span, context=context_wrapper, agent=agent, output=final_output
305+
)
306+
span.__exit__(*exc_info)
307+
delattr(context_wrapper, "_sentry_agent_span")
308+
reraise(*exc_info)
309+
310+
span = getattr(context_wrapper, "_sentry_agent_span", None)
311+
if span:
312+
update_invoke_agent_span(
313+
span=span, context=context_wrapper, agent=agent, output=final_output
314+
)
315+
span.__exit__(None, None, None)
316+
delattr(context_wrapper, "_sentry_agent_span")
254317

255318
return result

sentry_sdk/integrations/openai_agents/patches/error_tracing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import TYPE_CHECKING
33

44
import sentry_sdk
5-
from sentry_sdk.tracing_utils import set_span_errored
5+
from sentry_sdk.consts import SPANSTATUS
66

77
if TYPE_CHECKING:
88
from typing import Any
@@ -56,7 +56,7 @@ def sentry_attach_error_to_current_span(
5656
# Set the current Sentry span to errored
5757
current_span = sentry_sdk.get_current_span()
5858
if current_span is not None:
59-
set_span_errored(current_span)
59+
current_span.set_status(SPANSTATUS.INTERNAL_ERROR)
6060

6161
# Call the original function
6262
return original_attach_error(error, *args, **kwargs)

sentry_sdk/integrations/openai_agents/patches/runner.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
import sentry_sdk
55
from sentry_sdk.consts import SPANDATA
66
from sentry_sdk.integrations import DidNotEnable
7-
from sentry_sdk.tracing_utils import set_span_errored
87
from sentry_sdk.utils import capture_internal_exceptions, reraise
98

10-
from ..spans import agent_workflow_span, end_invoke_agent_span
9+
from ..spans import agent_workflow_span, update_invoke_agent_span
1110
from ..utils import _capture_exception
1211

1312
try:
@@ -66,8 +65,14 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any":
6665
invoke_agent_span is not None
6766
and invoke_agent_span.timestamp is None
6867
):
69-
set_span_errored(invoke_agent_span)
70-
end_invoke_agent_span(context_wrapper, agent)
68+
update_invoke_agent_span(
69+
span=invoke_agent_span,
70+
context=context_wrapper,
71+
agent=agent,
72+
)
73+
74+
invoke_agent_span.__exit__(*exc_info)
75+
delattr(context_wrapper, "_sentry_agent_span")
7176
reraise(*exc_info)
7277
except Exception as exc:
7378
exc_info = sys.exc_info()
@@ -78,7 +83,20 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any":
7883
_capture_exception(exc)
7984
reraise(*exc_info)
8085

81-
end_invoke_agent_span(run_result.context_wrapper, agent)
86+
invoke_agent_span = getattr(
87+
run_result.context_wrapper, "_sentry_agent_span", None
88+
)
89+
if not invoke_agent_span:
90+
return run_result
91+
92+
update_invoke_agent_span(
93+
span=invoke_agent_span,
94+
context=run_result.context_wrapper,
95+
agent=agent,
96+
)
97+
98+
invoke_agent_span.__exit__(None, None, None)
99+
delattr(run_result.context_wrapper, "_sentry_agent_span")
82100
return run_result
83101

84102
return wrapper

sentry_sdk/integrations/openai_agents/spans/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from .execute_tool import execute_tool_span, update_execute_tool_span # noqa: F401
44
from .handoff import handoff_span # noqa: F401
55
from .invoke_agent import (
6-
end_invoke_agent_span, # noqa: F401
76
invoke_agent_span, # noqa: F401
87
update_invoke_agent_span, # noqa: F401
98
)

sentry_sdk/integrations/openai_agents/spans/invoke_agent.py

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from ..utils import _set_agent_data, _set_usage_data
1616

1717
if TYPE_CHECKING:
18-
from typing import Any, Optional
18+
from typing import Any
1919

2020
import agents
2121

@@ -85,37 +85,19 @@ def invoke_agent_span(
8585

8686

8787
def update_invoke_agent_span(
88-
context: "agents.RunContextWrapper", agent: "agents.Agent", output: "Any"
89-
) -> None:
90-
span = getattr(context, "_sentry_agent_span", None)
91-
92-
if span:
93-
# Add aggregated usage data from context_wrapper
94-
if hasattr(context, "usage"):
95-
_set_usage_data(span, context.usage)
96-
97-
if should_send_default_pii():
98-
set_data_normalized(
99-
span, SPANDATA.GEN_AI_RESPONSE_TEXT, output, unpack=False
100-
)
101-
102-
# Add conversation ID from agent
103-
conv_id = getattr(agent, "_sentry_conversation_id", None)
104-
if conv_id:
105-
span.set_data(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id)
106-
107-
span.__exit__(None, None, None)
108-
delattr(context, "_sentry_agent_span")
109-
110-
111-
def end_invoke_agent_span(
112-
context_wrapper: "agents.RunContextWrapper",
88+
span: "sentry_sdk.tracing.Span",
89+
context: "agents.RunContextWrapper",
11390
agent: "agents.Agent",
114-
output: "Optional[Any]" = None,
91+
output: "Any" = None,
11592
) -> None:
116-
"""End the agent invocation span"""
117-
# Clear the stored agent
118-
if hasattr(context_wrapper, "_sentry_current_agent"):
119-
delattr(context_wrapper, "_sentry_current_agent")
93+
# Add aggregated usage data from context_wrapper
94+
if hasattr(context, "usage"):
95+
_set_usage_data(span, context.usage)
96+
97+
if should_send_default_pii():
98+
set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output, unpack=False)
12099

121-
update_invoke_agent_span(context_wrapper, agent, output)
100+
# Add conversation ID from agent
101+
conv_id = getattr(agent, "_sentry_conversation_id", None)
102+
if conv_id:
103+
span.set_data(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id)

sentry_sdk/integrations/openai_agents/utils.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
1818
from sentry_sdk.integrations import DidNotEnable
1919
from sentry_sdk.scope import should_send_default_pii
20-
from sentry_sdk.tracing_utils import set_span_errored
2120
from sentry_sdk.utils import event_from_exception, safe_serialize
2221

2322
if TYPE_CHECKING:
@@ -35,8 +34,6 @@
3534

3635

3736
def _capture_exception(exc: "Any") -> None:
38-
set_span_errored()
39-
4037
event, hint = event_from_exception(
4138
exc,
4239
client_options=sentry_sdk.get_client().options,

sentry_sdk/tracing_utils.py

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from typing import TYPE_CHECKING
2121

2222
import sentry_sdk
23-
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS, SPANTEMPLATE
23+
from sentry_sdk.consts import OP, SPANDATA, SPANTEMPLATE
2424
from sentry_sdk.utils import (
2525
_is_external_source,
2626
_is_in_project_root,
@@ -1153,33 +1153,6 @@ def get_current_span(
11531153
return current_span
11541154

11551155

1156-
def set_span_errored(span: "Optional[Union[Span, StreamedSpan]]" = None) -> None:
1157-
"""
1158-
Set the status of the current or given span to INTERNAL_ERROR.
1159-
Also sets the status of the transaction (root span) to INTERNAL_ERROR.
1160-
"""
1161-
from sentry_sdk.traces import SpanStatus, StreamedSpan, _get_current_streamed_span
1162-
1163-
client = sentry_sdk.get_client()
1164-
1165-
if not span:
1166-
span = (
1167-
_get_current_streamed_span()
1168-
if has_span_streaming_enabled(client.options)
1169-
else sentry_sdk.get_current_span()
1170-
)
1171-
1172-
if span is not None:
1173-
if isinstance(span, Span):
1174-
span.set_status(SPANSTATUS.INTERNAL_ERROR)
1175-
if span.containing_transaction is not None:
1176-
span.containing_transaction.set_status(SPANSTATUS.INTERNAL_ERROR)
1177-
elif isinstance(span, StreamedSpan):
1178-
span.status = SpanStatus.ERROR
1179-
if span._segment is not None:
1180-
span._segment.status = SpanStatus.ERROR
1181-
1182-
11831156
def _generate_sample_rand(
11841157
trace_id: "Optional[str]",
11851158
*,

0 commit comments

Comments
 (0)