33
44from sentry_sdk .consts import SPANDATA
55from sentry_sdk .integrations import DidNotEnable
6- from sentry_sdk .tracing_utils import set_span_errored
76from sentry_sdk .utils import capture_internal_exceptions , reraise
87
98from ..spans import (
10- end_invoke_agent_span ,
9+ _AgentInvocationSpanContext ,
1110 handoff_span ,
1211 invoke_agent_span ,
12+ update_invoke_agent_span ,
1313)
1414
1515if TYPE_CHECKING :
@@ -65,7 +65,9 @@ def _maybe_start_agent_span(
6565 if _has_active_agent_span (context_wrapper ):
6666 current_agent = _get_current_agent (context_wrapper )
6767 if current_agent and current_agent != agent :
68- end_invoke_agent_span (context_wrapper , current_agent )
68+ _AgentInvocationSpanContext (
69+ context_wrapper = context_wrapper , agent = current_agent
70+ ).__exit__ (None , None , None )
6971
7072 # Store the agent on the context wrapper so we can access it later
7173 context_wrapper ._sentry_current_agent = agent
@@ -103,15 +105,23 @@ async def _run_single_turn(
103105 context_wrapper , agent , should_run_agent_start_hooks , kwargs
104106 )
105107
108+ if span is None or span .timestamp is not None :
109+ return await original_run_single_turn (* args , ** kwargs )
110+
106111 try :
107- result = await original_run_single_turn (* args , ** kwargs )
112+ return await original_run_single_turn (* args , ** kwargs )
108113 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 ())
114+ exc_info = sys .exc_info ()
115+ with capture_internal_exceptions ():
116+ span = getattr (context_wrapper , "_sentry_agent_span" , None )
117+ if not span :
118+ return
113119
114- return result
120+ update_invoke_agent_span (span = span , context = context_wrapper , agent = agent )
121+
122+ span .__exit__ (* exc_info )
123+ delattr (context_wrapper , "_sentry_agent_span" )
124+ reraise (* exc_info )
115125
116126
117127async def _run_single_turn_streamed (
@@ -174,18 +184,20 @@ async def _run_single_turn_streamed(
174184 is_streaming = True ,
175185 )
176186
177- try :
178- result = await original_run_single_turn_streamed (* args , ** kwargs )
179- except Exception :
180- exc_info = sys .exc_info ()
181- 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 )
185- _close_streaming_workflow_span (agent )
186- reraise (* exc_info )
187+ if span is None or span .timestamp is not None :
188+ return await original_run_single_turn_streamed (* args , ** kwargs )
187189
188- return result
190+ with _AgentInvocationSpanContext (
191+ context_wrapper = context_wrapper ,
192+ agent = agent ,
193+ ):
194+ try :
195+ return await original_run_single_turn_streamed (* args , ** kwargs )
196+ except Exception :
197+ exc_info = sys .exc_info ()
198+ with capture_internal_exceptions ():
199+ _close_streaming_workflow_span (agent )
200+ reraise (* exc_info )
189201
190202
191203async def _execute_handoffs (
@@ -211,18 +223,30 @@ async def _execute_handoffs(
211223 handoff_agent_name = first_handoff .handoff .agent_name
212224 handoff_span (context_wrapper , agent , handoff_agent_name )
213225
214- # Call original method with all parameters
215- try :
216- result = await original_execute_handoffs (* args , ** kwargs )
217- except Exception :
218- exc_info = sys .exc_info ()
219- with capture_internal_exceptions ():
220- _close_streaming_workflow_span (agent )
221- 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 )
226+ if not agent or not context_wrapper or not _has_active_agent_span (context_wrapper ):
227+ # Call original method with all parameters
228+ try :
229+ result = await original_execute_handoffs (* args , ** kwargs )
230+ except Exception :
231+ exc_info = sys .exc_info ()
232+ with capture_internal_exceptions ():
233+ _close_streaming_workflow_span (agent )
234+ reraise (* exc_info )
235+
236+ return result
237+
238+ with _AgentInvocationSpanContext (
239+ context_wrapper = context_wrapper ,
240+ agent = agent ,
241+ ):
242+ # Call original method with all parameters
243+ try :
244+ result = await original_execute_handoffs (* args , ** kwargs )
245+ except Exception :
246+ exc_info = sys .exc_info ()
247+ with capture_internal_exceptions ():
248+ _close_streaming_workflow_span (agent )
249+ reraise (* exc_info )
226250
227251 return result
228252
@@ -243,13 +267,22 @@ async def _execute_final_output(
243267 context_wrapper = kwargs .get ("context_wrapper" )
244268 final_output = kwargs .get ("final_output" )
245269
246- try :
247- result = await original_execute_final_output (* args , ** kwargs )
248- finally :
249- 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 )
252- # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper)
253- _close_streaming_workflow_span (agent )
270+ if not agent or not context_wrapper or not _has_active_agent_span (context_wrapper ):
271+ try :
272+ result = await original_execute_final_output (* args , ** kwargs )
273+ finally :
274+ with capture_internal_exceptions ():
275+ # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper)
276+ _close_streaming_workflow_span (agent )
277+
278+ with _AgentInvocationSpanContext (
279+ context_wrapper = context_wrapper , agent = agent , output = final_output
280+ ):
281+ try :
282+ result = await original_execute_final_output (* args , ** kwargs )
283+ finally :
284+ with capture_internal_exceptions ():
285+ # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper)
286+ _close_streaming_workflow_span (agent )
254287
255288 return result
0 commit comments