diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 01119589ad..6e7f0f2820 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -3,13 +3,12 @@ from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable -from sentry_sdk.tracing_utils import set_span_errored from sentry_sdk.utils import capture_internal_exceptions, reraise from ..spans import ( - end_invoke_agent_span, handoff_span, invoke_agent_span, + update_invoke_agent_span, ) if TYPE_CHECKING: @@ -65,7 +64,13 @@ def _maybe_start_agent_span( if _has_active_agent_span(context_wrapper): current_agent = _get_current_agent(context_wrapper) if current_agent and current_agent != agent: - end_invoke_agent_span(context_wrapper, current_agent) + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent + ) + span.__exit__(None, None, None) + delattr(context_wrapper, "_sentry_agent_span") # Store the agent on the context wrapper so we can access it later context_wrapper._sentry_current_agent = agent @@ -103,13 +108,22 @@ async def _run_single_turn( context_wrapper, agent, should_run_agent_start_hooks, kwargs ) + if span is None or span.timestamp is not None: + return await original_run_single_turn(*args, **kwargs) + try: result = await original_run_single_turn(*args, **kwargs) except Exception: - if span is not None and span.timestamp is None: - set_span_errored(span) - end_invoke_agent_span(context_wrapper, agent) - reraise(*sys.exc_info()) + exc_info = sys.exc_info() + with capture_internal_exceptions(): + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent + ) + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") + reraise(*exc_info) return result @@ -174,14 +188,21 @@ async def _run_single_turn_streamed( is_streaming=True, ) + if span is None or span.timestamp is not None: + return await original_run_single_turn_streamed(*args, **kwargs) + try: result = await original_run_single_turn_streamed(*args, **kwargs) except Exception: exc_info = sys.exc_info() with capture_internal_exceptions(): - if span is not None and span.timestamp is None: - set_span_errored(span) - end_invoke_agent_span(context_wrapper, agent) + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent + ) + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") _close_streaming_workflow_span(agent) reraise(*exc_info) @@ -211,6 +232,16 @@ async def _execute_handoffs( handoff_agent_name = first_handoff.handoff.agent_name handoff_span(context_wrapper, agent, handoff_agent_name) + if not agent or not context_wrapper or not _has_active_agent_span(context_wrapper): + # Call original method with all parameters + try: + return await original_execute_handoffs(*args, **kwargs) + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _close_streaming_workflow_span(agent) + reraise(*exc_info) + # Call original method with all parameters try: result = await original_execute_handoffs(*args, **kwargs) @@ -218,11 +249,20 @@ async def _execute_handoffs( exc_info = sys.exc_info() with capture_internal_exceptions(): _close_streaming_workflow_span(agent) + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent + ) + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") reraise(*exc_info) - finally: - # End span for current agent after handoff processing is complete - if agent and context_wrapper and _has_active_agent_span(context_wrapper): - end_invoke_agent_span(context_wrapper, agent) + + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) + span.__exit__(None, None, None) + delattr(context_wrapper, "_sentry_agent_span") return result @@ -243,13 +283,36 @@ async def _execute_final_output( context_wrapper = kwargs.get("context_wrapper") final_output = kwargs.get("final_output") + if not agent or not context_wrapper or not _has_active_agent_span(context_wrapper): + try: + return await original_execute_final_output(*args, **kwargs) + finally: + with capture_internal_exceptions(): + # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper) + _close_streaming_workflow_span(agent) + try: result = await original_execute_final_output(*args, **kwargs) - finally: + except Exception: + exc_info = sys.exc_info() with capture_internal_exceptions(): - if agent and context_wrapper and _has_active_agent_span(context_wrapper): - end_invoke_agent_span(context_wrapper, agent, final_output) # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper) _close_streaming_workflow_span(agent) + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent, output=final_output + ) + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") + reraise(*exc_info) + + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent, output=final_output + ) + span.__exit__(None, None, None) + delattr(context_wrapper, "_sentry_agent_span") return result diff --git a/sentry_sdk/integrations/openai_agents/patches/error_tracing.py b/sentry_sdk/integrations/openai_agents/patches/error_tracing.py index e22d5fbd86..269380f501 100644 --- a/sentry_sdk/integrations/openai_agents/patches/error_tracing.py +++ b/sentry_sdk/integrations/openai_agents/patches/error_tracing.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING import sentry_sdk -from sentry_sdk.tracing_utils import set_span_errored +from sentry_sdk.consts import SPANSTATUS if TYPE_CHECKING: from typing import Any @@ -56,7 +56,7 @@ def sentry_attach_error_to_current_span( # Set the current Sentry span to errored current_span = sentry_sdk.get_current_span() if current_span is not None: - set_span_errored(current_span) + current_span.set_status(SPANSTATUS.INTERNAL_ERROR) # Call the original function return original_attach_error(error, *args, **kwargs) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 0674df7e70..6828ab4855 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -4,10 +4,9 @@ import sentry_sdk from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable -from sentry_sdk.tracing_utils import set_span_errored from sentry_sdk.utils import capture_internal_exceptions, reraise -from ..spans import agent_workflow_span, end_invoke_agent_span +from ..spans import agent_workflow_span, update_invoke_agent_span from ..utils import _capture_exception try: @@ -66,8 +65,14 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any": invoke_agent_span is not None and invoke_agent_span.timestamp is None ): - set_span_errored(invoke_agent_span) - end_invoke_agent_span(context_wrapper, agent) + update_invoke_agent_span( + span=invoke_agent_span, + context=context_wrapper, + agent=agent, + ) + + invoke_agent_span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") reraise(*exc_info) except Exception as exc: exc_info = sys.exc_info() @@ -78,7 +83,20 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any": _capture_exception(exc) reraise(*exc_info) - end_invoke_agent_span(run_result.context_wrapper, agent) + invoke_agent_span = getattr( + run_result.context_wrapper, "_sentry_agent_span", None + ) + if not invoke_agent_span: + return run_result + + update_invoke_agent_span( + span=invoke_agent_span, + context=run_result.context_wrapper, + agent=agent, + ) + + invoke_agent_span.__exit__(None, None, None) + delattr(run_result.context_wrapper, "_sentry_agent_span") return run_result return wrapper diff --git a/sentry_sdk/integrations/openai_agents/spans/__init__.py b/sentry_sdk/integrations/openai_agents/spans/__init__.py index a137b13040..08802a87a4 100644 --- a/sentry_sdk/integrations/openai_agents/spans/__init__.py +++ b/sentry_sdk/integrations/openai_agents/spans/__init__.py @@ -3,7 +3,6 @@ from .execute_tool import execute_tool_span, update_execute_tool_span # noqa: F401 from .handoff import handoff_span # noqa: F401 from .invoke_agent import ( - end_invoke_agent_span, # noqa: F401 invoke_agent_span, # noqa: F401 update_invoke_agent_span, # noqa: F401 ) diff --git a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py index 99cbc0e0a4..6f7dda3982 100644 --- a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +++ b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py @@ -15,7 +15,7 @@ from ..utils import _set_agent_data, _set_usage_data if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any import agents @@ -85,37 +85,19 @@ def invoke_agent_span( def update_invoke_agent_span( - context: "agents.RunContextWrapper", agent: "agents.Agent", output: "Any" -) -> None: - span = getattr(context, "_sentry_agent_span", None) - - if span: - # Add aggregated usage data from context_wrapper - if hasattr(context, "usage"): - _set_usage_data(span, context.usage) - - if should_send_default_pii(): - set_data_normalized( - span, SPANDATA.GEN_AI_RESPONSE_TEXT, output, unpack=False - ) - - # Add conversation ID from agent - conv_id = getattr(agent, "_sentry_conversation_id", None) - if conv_id: - span.set_data(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) - - span.__exit__(None, None, None) - delattr(context, "_sentry_agent_span") - - -def end_invoke_agent_span( - context_wrapper: "agents.RunContextWrapper", + span: "sentry_sdk.tracing.Span", + context: "agents.RunContextWrapper", agent: "agents.Agent", - output: "Optional[Any]" = None, + output: "Any" = None, ) -> None: - """End the agent invocation span""" - # Clear the stored agent - if hasattr(context_wrapper, "_sentry_current_agent"): - delattr(context_wrapper, "_sentry_current_agent") + # Add aggregated usage data from context_wrapper + if hasattr(context, "usage"): + _set_usage_data(span, context.usage) + + if should_send_default_pii(): + set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output, unpack=False) - update_invoke_agent_span(context_wrapper, agent, output) + # Add conversation ID from agent + conv_id = getattr(agent, "_sentry_conversation_id", None) + if conv_id: + span.set_data(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index 1d7d9e629d..e4157418a3 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -17,7 +17,6 @@ from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS from sentry_sdk.integrations import DidNotEnable from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.tracing_utils import set_span_errored from sentry_sdk.utils import event_from_exception, safe_serialize if TYPE_CHECKING: @@ -35,8 +34,6 @@ def _capture_exception(exc: "Any") -> None: - set_span_errored() - event, hint = event_from_exception( exc, client_options=sentry_sdk.get_client().options, diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 77e0a66a8f..0eb53871ef 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -20,7 +20,7 @@ from typing import TYPE_CHECKING import sentry_sdk -from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS, SPANTEMPLATE +from sentry_sdk.consts import OP, SPANDATA, SPANTEMPLATE from sentry_sdk.utils import ( _is_external_source, _is_in_project_root, @@ -1201,33 +1201,6 @@ def get_current_span( return current_span -def set_span_errored(span: "Optional[Union[Span, StreamedSpan]]" = None) -> None: - """ - Set the status of the current or given span to INTERNAL_ERROR. - Also sets the status of the transaction (root span) to INTERNAL_ERROR. - """ - from sentry_sdk.traces import SpanStatus, StreamedSpan, _get_current_streamed_span - - client = sentry_sdk.get_client() - - if not span: - span = ( - _get_current_streamed_span() - if has_span_streaming_enabled(client.options) - else sentry_sdk.get_current_span() - ) - - if span is not None: - if isinstance(span, Span): - span.set_status(SPANSTATUS.INTERNAL_ERROR) - if span.containing_transaction is not None: - span.containing_transaction.set_status(SPANSTATUS.INTERNAL_ERROR) - elif isinstance(span, StreamedSpan): - span.status = SpanStatus.ERROR - if span._segment is not None: - span._segment.status = SpanStatus.ERROR - - def _generate_sample_rand( trace_id: "Optional[str]", *,