Skip to content
Merged
97 changes: 80 additions & 17 deletions sentry_sdk/integrations/openai_agents/patches/agent_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
)
Comment thread
alexander-alderman-webb marked this conversation as resolved.
span.__exit__(None, None, None)
Comment thread
alexander-alderman-webb marked this conversation as resolved.
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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -211,18 +232,37 @@ 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)
except Exception:
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

Expand All @@ -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)
Comment thread
alexander-alderman-webb marked this conversation as resolved.

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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
28 changes: 23 additions & 5 deletions sentry_sdk/integrations/openai_agents/patches/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
1 change: 0 additions & 1 deletion sentry_sdk/integrations/openai_agents/spans/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
46 changes: 14 additions & 32 deletions sentry_sdk/integrations/openai_agents/spans/invoke_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
3 changes: 0 additions & 3 deletions sentry_sdk/integrations/openai_agents/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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,
Expand Down
29 changes: 1 addition & 28 deletions sentry_sdk/tracing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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]",
*,
Expand Down
Loading