From 12c28ac532c398c553b13bd46c2b8aa7b9f57bd0 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 10:41:41 +0200 Subject: [PATCH 01/13] ref(openai-agents): Use first class tool hooks when available --- .../integrations/openai_agents/__init__.py | 46 +++++----- .../openai_agents/patches/runner.py | 83 ++++++++++++++++++- .../openai_agents/patches/tools.py | 11 +++ .../openai_agents/spans/execute_tool.py | 8 -- .../openai_agents/test_openai_agents.py | 4 + 5 files changed, 117 insertions(+), 35 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index 5895f53ad3..24513be3fe 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -46,12 +46,13 @@ from agents.run_internal.run_steps import SingleStepResult -def _patch_runner() -> None: +def _patch_runner(use_tool_hooks: "bool") -> None: # Create the root span for one full agent run (including eventual handoffs) # Note agents.run.DEFAULT_AGENT_RUNNER.run_sync is a wrapper around # agents.run.DEFAULT_AGENT_RUNNER.run. It does not need to be wrapped separately. agents.run.DEFAULT_AGENT_RUNNER.run = _create_run_wrapper( - agents.run.DEFAULT_AGENT_RUNNER.run + agents.run.DEFAULT_AGENT_RUNNER.run, + use_tool_hooks=use_tool_hooks, ) # Patch streaming runner @@ -92,26 +93,18 @@ class OpenAIAgentsIntegration(Integration): @staticmethod def setup_once() -> None: _patch_error_tracing() - _patch_runner() library_version = parse_version(OPENAI_AGENTS_VERSION) + use_tool_hooks = library_version >= (0, 3, 2) + + _patch_runner(use_tool_hooks=use_tool_hooks) + if library_version is not None and library_version >= ( 0, 8, ): if run_loop is not None: - @wraps(run_loop.get_all_tools) - async def new_wrapped_get_all_tools( - agent: "agents.Agent", - context_wrapper: "agents.RunContextWrapper", - ) -> "list[agents.Tool]": - return await _get_all_tools( - run_loop.get_all_tools, agent, context_wrapper - ) - - agents.run.get_all_tools = new_wrapped_get_all_tools - @wraps(run_loop.run_single_turn) async def new_wrapped_run_single_turn( *args: "Any", **kwargs: "Any" @@ -175,17 +168,22 @@ async def new_wrapped_final_output( return - original_get_all_tools = AgentRunner._get_all_tools - - @wraps(AgentRunner._get_all_tools.__func__) - async def old_wrapped_get_all_tools( - cls: "agents.Runner", - agent: "agents.Agent", - context_wrapper: "agents.RunContextWrapper", - ) -> "list[agents.Tool]": - return await _get_all_tools(original_get_all_tools, agent, context_wrapper) + if not use_tool_hooks: + original_get_all_tools = AgentRunner._get_all_tools + + @wraps(AgentRunner._get_all_tools.__func__) + async def old_wrapped_get_all_tools( + cls: "agents.Runner", + agent: "agents.Agent", + context_wrapper: "agents.RunContextWrapper", + ) -> "list[agents.Tool]": + return await _get_all_tools( + original_get_all_tools, agent, context_wrapper + ) - agents.run.AgentRunner._get_all_tools = classmethod(old_wrapped_get_all_tools) + agents.run.AgentRunner._get_all_tools = classmethod( + old_wrapped_get_all_tools + ) original_get_model = AgentRunner._get_model diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 5f9996595f..da036c3921 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -7,21 +7,31 @@ from sentry_sdk.traces import StreamedSpan from sentry_sdk.utils import capture_internal_exceptions, reraise -from ..spans import agent_workflow_span, update_invoke_agent_span +from ..spans import ( + agent_workflow_span, + execute_tool_span, + update_execute_tool_span, + update_invoke_agent_span, +) from ..utils import _capture_exception try: + from agents import FunctionTool, RunHooks from agents.exceptions import AgentsException except ImportError: raise DidNotEnable("OpenAI Agents not installed") -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeVar if TYPE_CHECKING: from typing import Any, AsyncIterator, Callable + from agents import Agent, Tool, ToolContext -def _create_run_wrapper(original_func: "Callable[..., Any]") -> "Callable[..., Any]": + +def _create_run_wrapper( + original_func: "Callable[..., Any]", use_tool_hooks: "bool" +) -> "Callable[..., Any]": """ Wraps the agents.Runner.run methods to - create and manage a root span for the agent workflow runs. @@ -30,9 +40,76 @@ def _create_run_wrapper(original_func: "Callable[..., Any]") -> "Callable[..., A Note agents.Runner.run_sync() is a wrapper around agents.Runner.run(), so it does not need to be wrapped separately. """ + TContext = TypeVar("TContext") + + class _SentryRunHooks(RunHooks[TContext]): + async def on_tool_start( + self, + context: "ToolContext[TContext]", + agent: "Agent[TContext]", + tool: "Tool", + ) -> "None": + if not isinstance(tool, FunctionTool): + return + + span = execute_tool_span(tool, agent) + + if isinstance(span, StreamedSpan): + span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) + else: + span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) + + span.__enter__() + context.sentry_tool_span = span + + async def on_tool_end( + self, + context: "ToolContext[TContext]", + agent: "Agent[TContext]", + tool: "Tool", + result: "object", + ) -> "None": + if not isinstance(tool, FunctionTool): + return + + span = getattr(context, "sentry_tool_span", None) + if span: + del context.sentry_tool_span + update_execute_tool_span(span, agent, tool, result) + span.__exit__(None, None, None) @wraps(original_func) async def wrapper(*args: "Any", **kwargs: "Any") -> "Any": + if use_tool_hooks: + sentry_hooks = _SentryRunHooks() + hooks = kwargs.get("hooks") + if hooks is not None: + original_on_tool_start = hooks.on_tool_start + original_on_tool_end = hooks.on_tool_end + + @wraps(original_on_tool_start) + async def on_tool_start( + context: "ToolContext[Any]", agent: "Agent[Any]", tool: "Tool" + ) -> "None": + await original_on_tool_start(context, agent, tool) + await sentry_hooks.on_tool_start(context, agent, tool) + + @wraps(original_on_tool_end) + async def on_tool_end( + context: "ToolContext[Any]", + agent: "Agent[Any]", + tool: "Tool", + result: "object", + ) -> "None": + await original_on_tool_end(context, agent, tool, result) + await sentry_hooks.on_tool_end(context, agent, tool, result) + + hooks.on_tool_start = on_tool_start + hooks.on_tool_end = on_tool_end + kwargs["hooks"] = hooks + else: + kwargs["hooks"] = sentry_hooks + # Isolate each workflow so that when agents are run in asyncio tasks they # don't touch each other's scopes with sentry_sdk.isolation_scope(): diff --git a/sentry_sdk/integrations/openai_agents/patches/tools.py b/sentry_sdk/integrations/openai_agents/patches/tools.py index ab49df8b9e..2cb0a972f8 100644 --- a/sentry_sdk/integrations/openai_agents/patches/tools.py +++ b/sentry_sdk/integrations/openai_agents/patches/tools.py @@ -1,7 +1,10 @@ from functools import wraps from typing import TYPE_CHECKING +from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.traces import StreamedSpan from ..spans import execute_tool_span, update_execute_tool_span @@ -53,6 +56,14 @@ async def sentry_wrapped_on_invoke_tool( result = await current_on_invoke(*args, **kwargs) update_execute_tool_span(span, agent, current_tool, result) + if not should_send_default_pii(): + return result + + if isinstance(span, StreamedSpan): + span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, args[1]) + else: + span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, args[1]) + return result return sentry_wrapped_on_invoke_tool diff --git a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py index fd3a430951..7e1861757d 100644 --- a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py +++ b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py @@ -30,8 +30,6 @@ def execute_tool_span( SPANDATA.GEN_AI_TOOL_DESCRIPTION: tool.description, }, ) - - set_on_span = span.set_attribute else: span = sentry_sdk.start_span( op=OP.GEN_AI_EXECUTE_TOOL, @@ -44,12 +42,6 @@ def execute_tool_span( span.set_data(SPANDATA.GEN_AI_TOOL_NAME, tool.name) span.set_data(SPANDATA.GEN_AI_TOOL_DESCRIPTION, tool.description) - set_on_span = span.set_data - - if should_send_default_pii(): - input = args[1] - set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, input) - return span diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 6b0aaea9f8..33bdcecdd4 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -12,6 +12,7 @@ Agent, ModelResponse, ModelSettings, + RunHooks, Usage, ) from agents.computer import Computer @@ -2160,6 +2161,7 @@ async def test_max_turns_before_handoff_span( assert handoff_span["data"]["gen_ai.operation.name"] == "handoff" +@pytest.mark.parametrize("user_hooks", [True, False]) @pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize("stream_gen_ai_spans", [True, False]) @pytest.mark.asyncio @@ -2172,6 +2174,7 @@ async def test_tool_execution_span( responses_tool_call_model_responses, stream_gen_ai_spans, span_streaming, + user_hooks, ): """ Test tool execution span creation. @@ -2252,6 +2255,7 @@ def simple_test_tool(message: str) -> str: agent_with_tool, "Please use the simple test tool", run_config=test_run_config, + hooks=RunHooks() if user_hooks else None, ) sentry_sdk.flush() From d98200662aa325ef5e6cdd3c1d53906c1f70c7d3 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 12:05:20 +0200 Subject: [PATCH 02/13] support streamed responses as well --- .../integrations/openai_agents/__init__.py | 3 +- .../openai_agents/patches/runner.py | 141 ++-- tests/conftest.py | 124 +++- .../openai_agents/test_openai_agents.py | 616 +++++++++++++++++- 4 files changed, 816 insertions(+), 68 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index 24513be3fe..22c441c24d 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -57,7 +57,8 @@ def _patch_runner(use_tool_hooks: "bool") -> None: # Patch streaming runner agents.run.DEFAULT_AGENT_RUNNER.run_streamed = _create_run_streamed_wrapper( - agents.run.DEFAULT_AGENT_RUNNER.run_streamed + agents.run.DEFAULT_AGENT_RUNNER.run_streamed, + use_tool_hooks=use_tool_hooks, ) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index da036c3921..d05f40321d 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -29,6 +29,73 @@ from agents import Agent, Tool, ToolContext +TContext = TypeVar("TContext") + + +class _SentryRunHooks(RunHooks[TContext]): + async def on_tool_start( + self, + context: "ToolContext[TContext]", + agent: "Agent[TContext]", + tool: "Tool", + ) -> "None": + if not isinstance(tool, FunctionTool): + return + + span = execute_tool_span(tool, agent) + + if isinstance(span, StreamedSpan): + span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) + else: + span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) + + span.__enter__() + context.sentry_tool_span = span + + async def on_tool_end( + self, + context: "ToolContext[TContext]", + agent: "Agent[TContext]", + tool: "Tool", + result: "object", + ) -> "None": + if not isinstance(tool, FunctionTool): + return + + span = getattr(context, "sentry_tool_span", None) + if span is not None: + del context.sentry_tool_span + update_execute_tool_span(span, agent, tool, result) + span.__exit__(None, None, None) + + +def _patch_run_hooks(hooks: "RunHooks[TContext]"): + original_on_tool_start = hooks.on_tool_start + original_on_tool_end = hooks.on_tool_end + + sentry_hooks = _SentryRunHooks() + + @wraps(original_on_tool_start) + async def on_tool_start( + context: "ToolContext[TContext]", agent: "Agent[TContext]", tool: "Tool" + ) -> "None": + await original_on_tool_start(context, agent, tool) + await sentry_hooks.on_tool_start(context, agent, tool) + + @wraps(original_on_tool_end) + async def on_tool_end( + context: "ToolContext[TContext]", + agent: "Agent[TContext]", + tool: "Tool", + result: "object", + ) -> "None": + await original_on_tool_end(context, agent, tool, result) + await sentry_hooks.on_tool_end(context, agent, tool, result) + + hooks.on_tool_start = on_tool_start + hooks.on_tool_end = on_tool_end + + def _create_run_wrapper( original_func: "Callable[..., Any]", use_tool_hooks: "bool" ) -> "Callable[..., Any]": @@ -40,75 +107,15 @@ def _create_run_wrapper( Note agents.Runner.run_sync() is a wrapper around agents.Runner.run(), so it does not need to be wrapped separately. """ - TContext = TypeVar("TContext") - - class _SentryRunHooks(RunHooks[TContext]): - async def on_tool_start( - self, - context: "ToolContext[TContext]", - agent: "Agent[TContext]", - tool: "Tool", - ) -> "None": - if not isinstance(tool, FunctionTool): - return - - span = execute_tool_span(tool, agent) - - if isinstance(span, StreamedSpan): - span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) - else: - span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) - - span.__enter__() - context.sentry_tool_span = span - - async def on_tool_end( - self, - context: "ToolContext[TContext]", - agent: "Agent[TContext]", - tool: "Tool", - result: "object", - ) -> "None": - if not isinstance(tool, FunctionTool): - return - - span = getattr(context, "sentry_tool_span", None) - if span: - del context.sentry_tool_span - update_execute_tool_span(span, agent, tool, result) - span.__exit__(None, None, None) @wraps(original_func) async def wrapper(*args: "Any", **kwargs: "Any") -> "Any": if use_tool_hooks: - sentry_hooks = _SentryRunHooks() hooks = kwargs.get("hooks") if hooks is not None: - original_on_tool_start = hooks.on_tool_start - original_on_tool_end = hooks.on_tool_end - - @wraps(original_on_tool_start) - async def on_tool_start( - context: "ToolContext[Any]", agent: "Agent[Any]", tool: "Tool" - ) -> "None": - await original_on_tool_start(context, agent, tool) - await sentry_hooks.on_tool_start(context, agent, tool) - - @wraps(original_on_tool_end) - async def on_tool_end( - context: "ToolContext[Any]", - agent: "Agent[Any]", - tool: "Tool", - result: "object", - ) -> "None": - await original_on_tool_end(context, agent, tool, result) - await sentry_hooks.on_tool_end(context, agent, tool, result) - - hooks.on_tool_start = on_tool_start - hooks.on_tool_end = on_tool_end - kwargs["hooks"] = hooks + _patch_run_hooks(hooks=hooks) else: - kwargs["hooks"] = sentry_hooks + kwargs["hooks"] = _SentryRunHooks() # Isolate each workflow so that when agents are run in asyncio tasks they # don't touch each other's scopes @@ -200,7 +207,7 @@ async def on_tool_end( def _create_run_streamed_wrapper( - original_func: "Callable[..., Any]", + original_func: "Callable[..., Any]", use_tool_hooks: "bool" ) -> "Callable[..., Any]": """ Wraps the agents.Runner.run_streamed method to @@ -250,6 +257,14 @@ def wrapper(*args: "Any", **kwargs: "Any") -> "Any": else: args = (agent, *args[1:]) + if use_tool_hooks: + sentry_hooks = _SentryRunHooks() + hooks = kwargs.get("hooks") + if hooks is not None: + _patch_run_hooks(hooks=hooks) + else: + kwargs["hooks"] = sentry_hooks + try: # Call original function to get RunResultStreaming run_result = original_func(*args, **kwargs) diff --git a/tests/conftest.py b/tests/conftest.py index 6b406d6a06..f3ae302057 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1500,7 +1500,7 @@ def nonstreaming_google_genai_model_response(): @pytest.fixture -def responses_tool_call_model_responses(): +def nonstreaming_responses_tool_call_model_responses(): def inner( tool_name: str, arguments: str, @@ -1558,6 +1558,128 @@ def inner( return inner +@pytest.fixture +def streaming_responses_tool_call_model_responses(): + def inner( + tool_name: str, + arguments: str, + response_model: str, + response_text: str, + response_ids: "Iterator[str]", + usages: "Iterator[openai.types.responses.ResponseUsage]", + ): + first_id = next(response_ids) + second_id = next(response_ids) + first_usage = next(usages) + second_usage = next(usages) + + yield [ + openai.types.responses.ResponseCreatedEvent( + response=openai.types.responses.Response( + id=first_id, + output=[ + openai.types.responses.ResponseFunctionToolCall( + id="call_123", + call_id="call_123", + name=tool_name, + type="function_call", + arguments=arguments, + ) + ], + parallel_tool_calls=False, + tool_choice="none", + tools=[], + created_at=10000000, + model=response_model, + object="response", + usage=first_usage, + ), + sequence_number=0, + type="response.created", + ), + openai.types.responses.ResponseCompletedEvent( + response=openai.types.responses.Response( + id=first_id, + output=[ + openai.types.responses.ResponseFunctionToolCall( + id="call_123", + call_id="call_123", + name=tool_name, + type="function_call", + arguments=arguments, + ) + ], + parallel_tool_calls=False, + tool_choice="none", + tools=[], + created_at=10000000, + model=response_model, + object="response", + usage=first_usage, + ), + sequence_number=5, + type="response.completed", + ), + ] + + yield [ + openai.types.responses.ResponseCreatedEvent( + response=openai.types.responses.Response( + id=second_id, + output=[ + openai.types.responses.ResponseOutputMessage( + id="msg_final", + type="message", + status="in_progress", + content=[], + role="assistant", + ) + ], + parallel_tool_calls=False, + tool_choice="none", + tools=[], + created_at=10000000, + model=response_model, + object="response", + usage=second_usage, + ), + sequence_number=0, + type="response.created", + ), + openai.types.responses.ResponseCompletedEvent( + response=openai.types.responses.Response( + id=second_id, + output=[ + openai.types.responses.ResponseOutputMessage( + id="msg_final", + type="message", + status="completed", + content=[ + openai.types.responses.ResponseOutputText( + text=response_text, + type="output_text", + annotations=[], + ) + ], + role="assistant", + ) + ], + parallel_tool_calls=False, + tool_choice="none", + tools=[], + created_at=10000000, + model=response_model, + object="response", + usage=second_usage, + ), + sequence_number=7, + type="response.completed", + ), + ] + + return inner + + class MockServerRequestHandler(BaseHTTPRequestHandler): def do_GET(self): # noqa: N802 # Process an HTTP GET request and return a response. diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 33bdcecdd4..01f9364e29 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -2171,13 +2171,13 @@ async def test_tool_execution_span( capture_items, test_agent, get_model_response, - responses_tool_call_model_responses, + nonstreaming_responses_tool_call_model_responses, stream_gen_ai_spans, span_streaming, user_hooks, ): """ - Test tool execution span creation. + Test tool execution span creation with `AgentRunner.run()`. """ @agents.function_tool @@ -2190,7 +2190,7 @@ def simple_test_tool(message: str) -> str: model = OpenAIResponsesModel(model="gpt-4", openai_client=client) agent_with_tool = test_agent.clone(tools=[simple_test_tool], model=model) - responses = responses_tool_call_model_responses( + responses = nonstreaming_responses_tool_call_model_responses( tool_name="simple_test_tool", arguments='{"message": "hello"}', response_model="gpt-4", @@ -2455,6 +2455,7 @@ def simple_test_tool(message: str) -> str: agent_with_tool, "Please use the simple test tool", run_config=test_run_config, + hooks=RunHooks() if user_hooks else None, ) (transaction,) = (item.payload for item in items if item.type == "transaction") @@ -2648,6 +2649,7 @@ def simple_test_tool(message: str) -> str: agent_with_tool, "Please use the simple test tool", run_config=test_run_config, + hooks=RunHooks() if user_hooks else None, ) (transaction,) = events @@ -2787,6 +2789,614 @@ def simple_test_tool(message: str) -> str: assert ai_client_span2["data"]["gen_ai.usage.total_tokens"] == 25 +@pytest.mark.parametrize("user_hooks", [True, False]) +@pytest.mark.parametrize("span_streaming", [True, False]) +@pytest.mark.parametrize("stream_gen_ai_spans", [True, False]) +@pytest.mark.asyncio +async def test_run_streamed_tool_execution_span( + sentry_init, + capture_events, + capture_items, + test_agent, + get_model_response, + async_iterator, + server_side_event_chunks, + streaming_responses_tool_call_model_responses, + stream_gen_ai_spans, + span_streaming, + user_hooks, +): + """ + Test tool execution span creation with `AgentRunner.run_streamed()`. + """ + + @agents.function_tool + def simple_test_tool(message: str) -> str: + """A simple tool""" + return f"Tool executed with: {message}" + + # Create agent with the tool + client = AsyncOpenAI(api_key="test-key") + model = OpenAIResponsesModel(model="gpt-4", openai_client=client) + agent_with_tool = test_agent.clone(tools=[simple_test_tool], model=model) + + responses = streaming_responses_tool_call_model_responses( + tool_name="simple_test_tool", + arguments='{"message": "hello"}', + response_model="gpt-4", + response_text="Task completed using the tool", + response_ids=iter(["resp_tool_123", "resp_final_123"]), + usages=iter( + [ + ResponseUsage( + input_tokens=10, + input_tokens_details=InputTokensDetails( + cached_tokens=0, + cache_write_tokens=0, + ), + output_tokens=5, + output_tokens_details=OutputTokensDetails( + reasoning_tokens=0, + ), + total_tokens=15, + ), + ResponseUsage( + input_tokens=15, + input_tokens_details=InputTokensDetails( + cached_tokens=0, + cache_write_tokens=0, + ), + output_tokens=10, + output_tokens_details=OutputTokensDetails( + reasoning_tokens=0, + ), + total_tokens=25, + ), + ] + ), + ) + + request_headers = {} + # openai-agents calls with_streaming_response() if available starting with + # https://github.com/openai/openai-agents-python/commit/159beb56130f7d85192acfd593c9168757984dc0. + # When using with_streaming_response() the header set below changes the response type: + # https://github.com/openai/openai-python/blob/656e3cab4a18262a49b961d41293367e45ee71b9/src/openai/_response.py#L67. + if parse_version(OPENAI_AGENTS_VERSION) >= (0, 10, 3) and hasattr( + agent_with_tool.model._client.responses, "with_streaming_response" + ): + request_headers["X-Stainless-Raw-Response"] = "stream" + + tool_response = get_model_response( + async_iterator(server_side_event_chunks(next(responses))), + request_headers=request_headers, + ) + final_response = get_model_response( + async_iterator(server_side_event_chunks(next(responses))), + request_headers=request_headers, + ) + + if span_streaming: + with patch.object( + agent_with_tool.model._client._client, + "send", + side_effect=[tool_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + stream_gen_ai_spans=stream_gen_ai_spans, + trace_lifecycle="stream", + ) + + items = capture_items("span") + + result = agents.Runner.run_streamed( + agent_with_tool, + "Please use the simple test tool", + run_config=test_run_config, + hooks=RunHooks() if user_hooks else None, + ) + + async for event in result.stream_events(): + pass + + sentry_sdk.flush() + spans = [item.payload for item in items] + + assert spans[3]["name"] == "test_agent workflow" + assert spans[3]["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + + ai_client_span1, ai_client_span2 = ( + span + for span in spans + if span["attributes"].get("sentry.op") == OP.GEN_AI_CHAT + ) + tool_span = next( + span + for span in spans + if span["attributes"].get("sentry.op") == OP.GEN_AI_EXECUTE_TOOL + ) + + available_tool = { + "name": "simple_test_tool", + "description": "A simple tool", + "parameters": { + "properties": {"message": {"title": "Message", "type": "string"}}, + "required": ["message"], + "title": "simple_test_tool_args", + "type": "object", + "additionalProperties": False, + }, + } + + assert ai_client_span1["name"] == "chat gpt-4" + assert ai_client_span1["attributes"]["gen_ai.operation.name"] == "chat" + assert ai_client_span1["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span1["attributes"]["gen_ai.agent.name"] == "test_agent" + + ai_client_span1_available_tool = json.loads( + ai_client_span1["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] + )[0] + + assert all( + ai_client_span1_available_tool[k] == v for k, v in available_tool.items() + ) + + assert ai_client_span1["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span1["attributes"][ + "gen_ai.request.messages" + ] == safe_serialize( + [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Please use the simple test tool"} + ], + }, + ] + ) + assert ai_client_span1["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span1["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span1["attributes"]["gen_ai.request.top_p"] == 1.0 + assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens"] == 10 + assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 + assert ai_client_span1["attributes"]["gen_ai.usage.output_tokens"] == 5 + assert ( + ai_client_span1["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 + ) + assert ai_client_span1["attributes"]["gen_ai.usage.total_tokens"] == 15 + + tool_call = { + "arguments": '{"message": "hello"}', + "call_id": "call_123", + "name": "simple_test_tool", + "type": "function_call", + "id": "call_123", + "status": None, + } + + if OPENAI_VERSION >= (2, 25, 0): + tool_call["namespace"] = None + + parsed_tool_calls = json.loads( + ai_client_span1["attributes"]["gen_ai.response.tool_calls"] + ) + assert len(parsed_tool_calls) == 1 + assert tool_call.items() <= parsed_tool_calls[0].items() + + assert tool_span["name"] == "execute_tool simple_test_tool" + assert tool_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert tool_span["attributes"]["gen_ai.operation.name"] == "execute_tool" + + assert tool_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert tool_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert tool_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert tool_span["attributes"]["gen_ai.request.top_p"] == 1.0 + assert tool_span["attributes"]["gen_ai.system"] == "openai" + assert tool_span["attributes"]["gen_ai.tool.description"] == "A simple tool" + assert tool_span["attributes"]["gen_ai.tool.input"] == '{"message": "hello"}' + assert tool_span["attributes"]["gen_ai.tool.name"] == "simple_test_tool" + assert ( + tool_span["attributes"]["gen_ai.tool.output"] == "Tool executed with: hello" + ) + assert ai_client_span2["name"] == "chat gpt-4" + assert ai_client_span2["attributes"]["gen_ai.agent.name"] == "test_agent" + assert ai_client_span2["attributes"]["gen_ai.operation.name"] == "chat" + + ai_client_span2_available_tool = json.loads( + ai_client_span2["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] + )[0] + + assert all( + ai_client_span2_available_tool[k] == v for k, v in available_tool.items() + ) + + assert ai_client_span2["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span2["attributes"][ + "gen_ai.request.messages" + ] == safe_serialize( + [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Please use the simple test tool"} + ], + }, + { + "role": "assistant", + "content": [ + { + "arguments": '{"message": "hello"}', + "call_id": "call_123", + "name": "simple_test_tool", + "type": "function_call", + "id": "call_123", + "caller": "None", + "namespace": "None", + } + ], + }, + { + "role": "tool", + "content": [ + { + "call_id": "call_123", + "output": "Tool executed with: hello", + "type": "function_call_output", + } + ], + }, + ] + ) + assert ai_client_span2["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span2["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span2["attributes"]["gen_ai.request.top_p"] == 1.0 + assert ( + ai_client_span2["attributes"]["gen_ai.response.text"] + == "Task completed using the tool" + ) + assert ai_client_span2["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 + assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens"] == 15 + assert ( + ai_client_span2["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 + ) + assert ai_client_span2["attributes"]["gen_ai.usage.output_tokens"] == 10 + assert ai_client_span2["attributes"]["gen_ai.usage.total_tokens"] == 25 + + elif stream_gen_ai_spans: + with patch.object( + agent_with_tool.model._client._client, + "send", + side_effect=[tool_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + traces_sample_rate=1.0, + send_default_pii=True, + stream_gen_ai_spans=stream_gen_ai_spans, + ) + + items = capture_items("transaction", "span") + + result = agents.Runner.run_streamed( + agent_with_tool, + "Please use the simple test tool", + run_config=test_run_config, + hooks=RunHooks() if user_hooks else None, + ) + + async for event in result.stream_events(): + pass + + (transaction,) = (item.payload for item in items if item.type == "transaction") + assert transaction["transaction"] == "test_agent workflow" + assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" + + spans = [item.payload for item in items if item.type == "span"] + ai_client_span1, ai_client_span2 = ( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) + tool_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_EXECUTE_TOOL + ) + + available_tool = { + "name": "simple_test_tool", + "description": "A simple tool", + "parameters": { + "properties": {"message": {"title": "Message", "type": "string"}}, + "required": ["message"], + "title": "simple_test_tool_args", + "type": "object", + "additionalProperties": False, + }, + } + + assert ai_client_span1["name"] == "chat gpt-4" + assert ai_client_span1["attributes"]["gen_ai.operation.name"] == "chat" + assert ai_client_span1["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span1["attributes"]["gen_ai.agent.name"] == "test_agent" + + ai_client_span1_available_tool = json.loads( + ai_client_span1["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] + )[0] + + assert all( + ai_client_span1_available_tool[k] == v for k, v in available_tool.items() + ) + + assert ai_client_span1["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span1["attributes"][ + "gen_ai.request.messages" + ] == safe_serialize( + [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Please use the simple test tool"} + ], + }, + ] + ) + assert ai_client_span1["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span1["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span1["attributes"]["gen_ai.request.top_p"] == 1.0 + assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens"] == 10 + assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 + assert ai_client_span1["attributes"]["gen_ai.usage.output_tokens"] == 5 + assert ( + ai_client_span1["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 + ) + assert ai_client_span1["attributes"]["gen_ai.usage.total_tokens"] == 15 + + tool_call = { + "arguments": '{"message": "hello"}', + "call_id": "call_123", + "name": "simple_test_tool", + "type": "function_call", + "id": "call_123", + "status": None, + } + + parsed_tool_calls = json.loads( + ai_client_span1["attributes"]["gen_ai.response.tool_calls"] + ) + assert len(parsed_tool_calls) == 1 + assert tool_call.items() <= parsed_tool_calls[0].items() + + assert tool_span["name"] == "execute_tool simple_test_tool" + assert tool_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert tool_span["attributes"]["gen_ai.operation.name"] == "execute_tool" + + assert tool_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert tool_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert tool_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert tool_span["attributes"]["gen_ai.request.top_p"] == 1.0 + assert tool_span["attributes"]["gen_ai.system"] == "openai" + assert tool_span["attributes"]["gen_ai.tool.description"] == "A simple tool" + assert tool_span["attributes"]["gen_ai.tool.input"] == '{"message": "hello"}' + assert tool_span["attributes"]["gen_ai.tool.name"] == "simple_test_tool" + assert ( + tool_span["attributes"]["gen_ai.tool.output"] == "Tool executed with: hello" + ) + assert ai_client_span2["name"] == "chat gpt-4" + assert ai_client_span2["attributes"]["gen_ai.agent.name"] == "test_agent" + assert ai_client_span2["attributes"]["gen_ai.operation.name"] == "chat" + + ai_client_span2_available_tool = json.loads( + ai_client_span2["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] + )[0] + + assert all( + ai_client_span2_available_tool[k] == v for k, v in available_tool.items() + ) + + assert ai_client_span2["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span2["attributes"][ + "gen_ai.request.messages" + ] == safe_serialize( + [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Please use the simple test tool"} + ], + }, + { + "role": "assistant", + "content": [ + { + "arguments": '{"message": "hello"}', + "call_id": "call_123", + "name": "simple_test_tool", + "type": "function_call", + "id": "call_123", + "caller": "None", + "namespace": "None", + } + ], + }, + { + "role": "tool", + "content": [ + { + "call_id": "call_123", + "output": "Tool executed with: hello", + "type": "function_call_output", + } + ], + }, + ] + ) + assert ai_client_span2["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span2["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span2["attributes"]["gen_ai.request.top_p"] == 1.0 + assert ( + ai_client_span2["attributes"]["gen_ai.response.text"] + == "Task completed using the tool" + ) + assert ai_client_span2["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 + assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens"] == 15 + assert ( + ai_client_span2["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 + ) + assert ai_client_span2["attributes"]["gen_ai.usage.output_tokens"] == 10 + assert ai_client_span2["attributes"]["gen_ai.usage.total_tokens"] == 25 + + else: + with patch.object( + agent_with_tool.model._client._client, + "send", + side_effect=[tool_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + traces_sample_rate=1.0, + send_default_pii=True, + stream_gen_ai_spans=stream_gen_ai_spans, + ) + + events = capture_events() + + result = agents.Runner.run_streamed( + agent_with_tool, + "Please use the simple test tool", + run_config=test_run_config, + hooks=RunHooks() if user_hooks else None, + ) + + async for event in result.stream_events(): + pass + + (transaction,) = events + spans = transaction["spans"] + ai_client_span1, ai_client_span2 = ( + span for span in spans if span["op"] == OP.GEN_AI_CHAT + ) + tool_span = next(span for span in spans if span["op"] == OP.GEN_AI_EXECUTE_TOOL) + + available_tool = { + "name": "simple_test_tool", + "description": "A simple tool", + "parameters": { + "properties": {"message": {"title": "Message", "type": "string"}}, + "required": ["message"], + "title": "simple_test_tool_args", + "type": "object", + "additionalProperties": False, + }, + } + + assert transaction["transaction"] == "test_agent workflow" + assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" + + assert ai_client_span1["description"] == "chat gpt-4" + assert ai_client_span1["data"]["gen_ai.operation.name"] == "chat" + assert ai_client_span1["data"]["gen_ai.system"] == "openai" + assert ai_client_span1["data"]["gen_ai.agent.name"] == "test_agent" + + ai_client_span1_available_tool = json.loads( + ai_client_span1["data"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] + )[0] + assert all( + ai_client_span1_available_tool[k] == v for k, v in available_tool.items() + ) + + assert ai_client_span1["data"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span1["data"]["gen_ai.request.messages"] == safe_serialize( + [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Please use the simple test tool"} + ], + }, + ] + ) + assert ai_client_span1["data"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span1["data"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span1["data"]["gen_ai.request.top_p"] == 1.0 + assert ai_client_span1["data"]["gen_ai.usage.input_tokens"] == 10 + assert ai_client_span1["data"]["gen_ai.usage.input_tokens.cached"] == 0 + assert ai_client_span1["data"]["gen_ai.usage.output_tokens"] == 5 + assert ai_client_span1["data"]["gen_ai.usage.output_tokens.reasoning"] == 0 + assert ai_client_span1["data"]["gen_ai.usage.total_tokens"] == 15 + + tool_call = { + "arguments": '{"message": "hello"}', + "call_id": "call_123", + "name": "simple_test_tool", + "type": "function_call", + "id": "call_123", + "status": None, + } + + parsed_tool_calls = json.loads( + ai_client_span1["data"]["gen_ai.response.tool_calls"] + ) + assert len(parsed_tool_calls) == 1 + assert tool_call.items() <= parsed_tool_calls[0].items() + + assert tool_span["description"] == "execute_tool simple_test_tool" + assert tool_span["data"]["gen_ai.agent.name"] == "test_agent" + assert tool_span["data"]["gen_ai.operation.name"] == "execute_tool" + + assert tool_span["data"]["gen_ai.request.max_tokens"] == 100 + assert tool_span["data"]["gen_ai.request.model"] == "gpt-4" + assert tool_span["data"]["gen_ai.request.temperature"] == 0.7 + assert tool_span["data"]["gen_ai.request.top_p"] == 1.0 + assert tool_span["data"]["gen_ai.system"] == "openai" + assert tool_span["data"]["gen_ai.tool.description"] == "A simple tool" + assert tool_span["data"]["gen_ai.tool.input"] == '{"message": "hello"}' + assert tool_span["data"]["gen_ai.tool.name"] == "simple_test_tool" + assert tool_span["data"]["gen_ai.tool.output"] == "Tool executed with: hello" + assert ai_client_span2["description"] == "chat gpt-4" + assert ai_client_span2["data"]["gen_ai.agent.name"] == "test_agent" + assert ai_client_span2["data"]["gen_ai.operation.name"] == "chat" + + ai_client_span2_available_tool = json.loads( + ai_client_span2["data"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] + )[0] + assert all( + ai_client_span2_available_tool[k] == v for k, v in available_tool.items() + ) + + assert ai_client_span2["data"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span2["data"]["gen_ai.request.messages"] == safe_serialize( + [ + { + "role": "tool", + "content": [ + { + "call_id": "call_123", + "output": "Tool executed with: hello", + "type": "function_call_output", + } + ], + }, + ] + ) + assert ai_client_span2["data"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span2["data"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span2["data"]["gen_ai.request.top_p"] == 1.0 + assert ( + ai_client_span2["data"]["gen_ai.response.text"] + == "Task completed using the tool" + ) + assert ai_client_span2["data"]["gen_ai.system"] == "openai" + assert ai_client_span2["data"]["gen_ai.usage.input_tokens.cached"] == 0 + assert ai_client_span2["data"]["gen_ai.usage.input_tokens"] == 15 + assert ai_client_span2["data"]["gen_ai.usage.output_tokens.reasoning"] == 0 + assert ai_client_span2["data"]["gen_ai.usage.output_tokens"] == 10 + assert ai_client_span2["data"]["gen_ai.usage.total_tokens"] == 25 + + @pytest.mark.asyncio async def test_hosted_mcp_tool_propagation_header_streamed( sentry_init, From 0fa5385784f76db7d7d50369dd6a32abe98dbec6 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 12:09:40 +0200 Subject: [PATCH 03/13] fix langchain tests --- tests/integrations/langchain/test_langchain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 84ad453f90..71bda0b130 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -845,7 +845,7 @@ def test_tool_execution_span( send_default_pii, include_prompts, get_model_response, - responses_tool_call_model_responses, + nonstreaming_responses_tool_call_model_responses, stream_gen_ai_spans, span_streaming, ): @@ -862,7 +862,7 @@ def test_tool_execution_span( trace_lifecycle="stream" if span_streaming else "static", ) - responses = responses_tool_call_model_responses( + responses = nonstreaming_responses_tool_call_model_responses( tool_name="get_word_length", arguments='{"word": "eudca"}', response_model="gpt-4-0613", From 4501d3465b15a7b5159dd5434353901792a9c89b Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 12:21:54 +0200 Subject: [PATCH 04/13] make mypy happy --- sentry_sdk/integrations/openai_agents/__init__.py | 2 +- sentry_sdk/integrations/openai_agents/patches/runner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index 22c441c24d..179e13c16e 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -96,7 +96,7 @@ def setup_once() -> None: _patch_error_tracing() library_version = parse_version(OPENAI_AGENTS_VERSION) - use_tool_hooks = library_version >= (0, 3, 2) + use_tool_hooks = library_version is not None and library_version >= (0, 3, 2) _patch_runner(use_tool_hooks=use_tool_hooks) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index d05f40321d..faa85d50de 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -69,7 +69,7 @@ async def on_tool_end( span.__exit__(None, None, None) -def _patch_run_hooks(hooks: "RunHooks[TContext]"): +def _patch_run_hooks(hooks: "RunHooks[TContext]") -> None: original_on_tool_start = hooks.on_tool_start original_on_tool_end = hooks.on_tool_end From 47a19148365f05361cf2a531a98fd7144434ec04 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 12:29:47 +0200 Subject: [PATCH 05/13] fix inheritance --- sentry_sdk/integrations/openai_agents/patches/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index faa85d50de..6e6b326ce7 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -32,7 +32,7 @@ TContext = TypeVar("TContext") -class _SentryRunHooks(RunHooks[TContext]): +class _SentryRunHooks(RunHooks[TContext]): # type: ignore[misc] async def on_tool_start( self, context: "ToolContext[TContext]", From b9c3cb0129b0ddb04fb8d6cc09896fefefa992f0 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 12:34:32 +0200 Subject: [PATCH 06/13] make idempotent --- .../integrations/openai_agents/patches/runner.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 6e6b326ce7..5270acb4cf 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -4,6 +4,7 @@ import sentry_sdk from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import StreamedSpan from sentry_sdk.utils import capture_internal_exceptions, reraise @@ -44,9 +45,9 @@ async def on_tool_start( span = execute_tool_span(tool, agent) - if isinstance(span, StreamedSpan): + if should_send_default_pii() and isinstance(span, StreamedSpan): span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) - else: + elif should_send_default_pii(): span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) span.__enter__() @@ -70,10 +71,14 @@ async def on_tool_end( def _patch_run_hooks(hooks: "RunHooks[TContext]") -> None: + is_already_patched = getattr(hooks, "_sentry_is_patched", False) + if is_already_patched: + return + original_on_tool_start = hooks.on_tool_start original_on_tool_end = hooks.on_tool_end - sentry_hooks = _SentryRunHooks() + sentry_hooks = _SentryRunHooks() # type: ignore[var-annotated] @wraps(original_on_tool_start) async def on_tool_start( @@ -92,6 +97,7 @@ async def on_tool_end( await original_on_tool_end(context, agent, tool, result) await sentry_hooks.on_tool_end(context, agent, tool, result) + hooks._sentry_is_patched = True hooks.on_tool_start = on_tool_start hooks.on_tool_end = on_tool_end @@ -258,7 +264,7 @@ def wrapper(*args: "Any", **kwargs: "Any") -> "Any": args = (agent, *args[1:]) if use_tool_hooks: - sentry_hooks = _SentryRunHooks() + sentry_hooks = _SentryRunHooks() # type: ignore[var-annotated] hooks = kwargs.get("hooks") if hooks is not None: _patch_run_hooks(hooks=hooks) From 1f1e5d3cd99d2ca43690eccedc89d5ad4f4f3700 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 12:37:15 +0200 Subject: [PATCH 07/13] some reordering --- .../integrations/openai_agents/patches/runner.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 5270acb4cf..d9e076e451 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -4,7 +4,6 @@ import sentry_sdk from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable -from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import StreamedSpan from sentry_sdk.utils import capture_internal_exceptions, reraise @@ -44,15 +43,14 @@ async def on_tool_start( return span = execute_tool_span(tool, agent) + span.__enter__() + context.sentry_tool_span = span - if should_send_default_pii() and isinstance(span, StreamedSpan): + if isinstance(span, StreamedSpan): span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) - elif should_send_default_pii(): + else: span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) - span.__enter__() - context.sentry_tool_span = span - async def on_tool_end( self, context: "ToolContext[TContext]", From 881ff72ebe63802c800f89ea7fd6643311f6371c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 12:38:37 +0200 Subject: [PATCH 08/13] early return for pii --- sentry_sdk/integrations/openai_agents/patches/runner.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index d9e076e451..6d1644266a 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -4,6 +4,7 @@ import sentry_sdk from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import StreamedSpan from sentry_sdk.utils import capture_internal_exceptions, reraise @@ -46,6 +47,9 @@ async def on_tool_start( span.__enter__() context.sentry_tool_span = span + if not should_send_default_pii(): + return + if isinstance(span, StreamedSpan): span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) else: From 982298261158ae8d6fdd17d37c51be9159cb5d1c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 12:41:52 +0200 Subject: [PATCH 09/13] update callsite --- tests/integrations/openai_agents/test_openai_agents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 01f9364e29..2757e5c4be 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -4396,7 +4396,7 @@ async def test_tool_execution_error_tracing( capture_items, test_agent, get_model_response, - responses_tool_call_model_responses, + nonstreaming_responses_tool_call_model_responses, stream_gen_ai_spans, span_streaming, ): @@ -4421,7 +4421,7 @@ def failing_tool(message: str) -> str: model = OpenAIResponsesModel(model="gpt-4", openai_client=client) agent_with_tool = test_agent.clone(tools=[failing_tool], model=model) - responses = responses_tool_call_model_responses( + responses = nonstreaming_responses_tool_call_model_responses( tool_name="failing_tool", arguments='{"message": "test"}', response_model="gpt-4-0613", From 537e9968ba098bd8f05b293f5f09797e1cf481a7 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 13:52:23 +0200 Subject: [PATCH 10/13] simplify test --- .../openai_agents/test_openai_agents.py | 369 ------------------ 1 file changed, 369 deletions(-) diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 2757e5c4be..eb1893d451 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -2908,84 +2908,12 @@ def simple_test_tool(message: str) -> str: assert spans[3]["name"] == "test_agent workflow" assert spans[3]["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - ai_client_span1, ai_client_span2 = ( - span - for span in spans - if span["attributes"].get("sentry.op") == OP.GEN_AI_CHAT - ) tool_span = next( span for span in spans if span["attributes"].get("sentry.op") == OP.GEN_AI_EXECUTE_TOOL ) - available_tool = { - "name": "simple_test_tool", - "description": "A simple tool", - "parameters": { - "properties": {"message": {"title": "Message", "type": "string"}}, - "required": ["message"], - "title": "simple_test_tool_args", - "type": "object", - "additionalProperties": False, - }, - } - - assert ai_client_span1["name"] == "chat gpt-4" - assert ai_client_span1["attributes"]["gen_ai.operation.name"] == "chat" - assert ai_client_span1["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span1["attributes"]["gen_ai.agent.name"] == "test_agent" - - ai_client_span1_available_tool = json.loads( - ai_client_span1["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] - - assert all( - ai_client_span1_available_tool[k] == v for k, v in available_tool.items() - ) - - assert ai_client_span1["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span1["attributes"][ - "gen_ai.request.messages" - ] == safe_serialize( - [ - { - "role": "user", - "content": [ - {"type": "text", "text": "Please use the simple test tool"} - ], - }, - ] - ) - assert ai_client_span1["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span1["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span1["attributes"]["gen_ai.request.top_p"] == 1.0 - assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens"] == 10 - assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span1["attributes"]["gen_ai.usage.output_tokens"] == 5 - assert ( - ai_client_span1["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 - ) - assert ai_client_span1["attributes"]["gen_ai.usage.total_tokens"] == 15 - - tool_call = { - "arguments": '{"message": "hello"}', - "call_id": "call_123", - "name": "simple_test_tool", - "type": "function_call", - "id": "call_123", - "status": None, - } - - if OPENAI_VERSION >= (2, 25, 0): - tool_call["namespace"] = None - - parsed_tool_calls = json.loads( - ai_client_span1["attributes"]["gen_ai.response.tool_calls"] - ) - assert len(parsed_tool_calls) == 1 - assert tool_call.items() <= parsed_tool_calls[0].items() - assert tool_span["name"] == "execute_tool simple_test_tool" assert tool_span["attributes"]["gen_ai.agent.name"] == "test_agent" assert tool_span["attributes"]["gen_ai.operation.name"] == "execute_tool" @@ -3001,70 +2929,6 @@ def simple_test_tool(message: str) -> str: assert ( tool_span["attributes"]["gen_ai.tool.output"] == "Tool executed with: hello" ) - assert ai_client_span2["name"] == "chat gpt-4" - assert ai_client_span2["attributes"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span2["attributes"]["gen_ai.operation.name"] == "chat" - - ai_client_span2_available_tool = json.loads( - ai_client_span2["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] - - assert all( - ai_client_span2_available_tool[k] == v for k, v in available_tool.items() - ) - - assert ai_client_span2["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span2["attributes"][ - "gen_ai.request.messages" - ] == safe_serialize( - [ - { - "role": "user", - "content": [ - {"type": "text", "text": "Please use the simple test tool"} - ], - }, - { - "role": "assistant", - "content": [ - { - "arguments": '{"message": "hello"}', - "call_id": "call_123", - "name": "simple_test_tool", - "type": "function_call", - "id": "call_123", - "caller": "None", - "namespace": "None", - } - ], - }, - { - "role": "tool", - "content": [ - { - "call_id": "call_123", - "output": "Tool executed with: hello", - "type": "function_call_output", - } - ], - }, - ] - ) - assert ai_client_span2["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span2["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span2["attributes"]["gen_ai.request.top_p"] == 1.0 - assert ( - ai_client_span2["attributes"]["gen_ai.response.text"] - == "Task completed using the tool" - ) - assert ai_client_span2["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens"] == 15 - assert ( - ai_client_span2["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 - ) - assert ai_client_span2["attributes"]["gen_ai.usage.output_tokens"] == 10 - assert ai_client_span2["attributes"]["gen_ai.usage.total_tokens"] == 25 elif stream_gen_ai_spans: with patch.object( @@ -3096,79 +2960,12 @@ def simple_test_tool(message: str) -> str: assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" spans = [item.payload for item in items if item.type == "span"] - ai_client_span1, ai_client_span2 = ( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT - ) tool_span = next( span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_EXECUTE_TOOL ) - available_tool = { - "name": "simple_test_tool", - "description": "A simple tool", - "parameters": { - "properties": {"message": {"title": "Message", "type": "string"}}, - "required": ["message"], - "title": "simple_test_tool_args", - "type": "object", - "additionalProperties": False, - }, - } - - assert ai_client_span1["name"] == "chat gpt-4" - assert ai_client_span1["attributes"]["gen_ai.operation.name"] == "chat" - assert ai_client_span1["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span1["attributes"]["gen_ai.agent.name"] == "test_agent" - - ai_client_span1_available_tool = json.loads( - ai_client_span1["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] - - assert all( - ai_client_span1_available_tool[k] == v for k, v in available_tool.items() - ) - - assert ai_client_span1["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span1["attributes"][ - "gen_ai.request.messages" - ] == safe_serialize( - [ - { - "role": "user", - "content": [ - {"type": "text", "text": "Please use the simple test tool"} - ], - }, - ] - ) - assert ai_client_span1["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span1["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span1["attributes"]["gen_ai.request.top_p"] == 1.0 - assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens"] == 10 - assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span1["attributes"]["gen_ai.usage.output_tokens"] == 5 - assert ( - ai_client_span1["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 - ) - assert ai_client_span1["attributes"]["gen_ai.usage.total_tokens"] == 15 - - tool_call = { - "arguments": '{"message": "hello"}', - "call_id": "call_123", - "name": "simple_test_tool", - "type": "function_call", - "id": "call_123", - "status": None, - } - - parsed_tool_calls = json.loads( - ai_client_span1["attributes"]["gen_ai.response.tool_calls"] - ) - assert len(parsed_tool_calls) == 1 - assert tool_call.items() <= parsed_tool_calls[0].items() - assert tool_span["name"] == "execute_tool simple_test_tool" assert tool_span["attributes"]["gen_ai.agent.name"] == "test_agent" assert tool_span["attributes"]["gen_ai.operation.name"] == "execute_tool" @@ -3184,71 +2981,6 @@ def simple_test_tool(message: str) -> str: assert ( tool_span["attributes"]["gen_ai.tool.output"] == "Tool executed with: hello" ) - assert ai_client_span2["name"] == "chat gpt-4" - assert ai_client_span2["attributes"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span2["attributes"]["gen_ai.operation.name"] == "chat" - - ai_client_span2_available_tool = json.loads( - ai_client_span2["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] - - assert all( - ai_client_span2_available_tool[k] == v for k, v in available_tool.items() - ) - - assert ai_client_span2["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span2["attributes"][ - "gen_ai.request.messages" - ] == safe_serialize( - [ - { - "role": "user", - "content": [ - {"type": "text", "text": "Please use the simple test tool"} - ], - }, - { - "role": "assistant", - "content": [ - { - "arguments": '{"message": "hello"}', - "call_id": "call_123", - "name": "simple_test_tool", - "type": "function_call", - "id": "call_123", - "caller": "None", - "namespace": "None", - } - ], - }, - { - "role": "tool", - "content": [ - { - "call_id": "call_123", - "output": "Tool executed with: hello", - "type": "function_call_output", - } - ], - }, - ] - ) - assert ai_client_span2["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span2["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span2["attributes"]["gen_ai.request.top_p"] == 1.0 - assert ( - ai_client_span2["attributes"]["gen_ai.response.text"] - == "Task completed using the tool" - ) - assert ai_client_span2["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens"] == 15 - assert ( - ai_client_span2["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 - ) - assert ai_client_span2["attributes"]["gen_ai.usage.output_tokens"] == 10 - assert ai_client_span2["attributes"]["gen_ai.usage.total_tokens"] == 25 - else: with patch.object( agent_with_tool.model._client._client, @@ -3276,73 +3008,11 @@ def simple_test_tool(message: str) -> str: (transaction,) = events spans = transaction["spans"] - ai_client_span1, ai_client_span2 = ( - span for span in spans if span["op"] == OP.GEN_AI_CHAT - ) tool_span = next(span for span in spans if span["op"] == OP.GEN_AI_EXECUTE_TOOL) - available_tool = { - "name": "simple_test_tool", - "description": "A simple tool", - "parameters": { - "properties": {"message": {"title": "Message", "type": "string"}}, - "required": ["message"], - "title": "simple_test_tool_args", - "type": "object", - "additionalProperties": False, - }, - } - assert transaction["transaction"] == "test_agent workflow" assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" - assert ai_client_span1["description"] == "chat gpt-4" - assert ai_client_span1["data"]["gen_ai.operation.name"] == "chat" - assert ai_client_span1["data"]["gen_ai.system"] == "openai" - assert ai_client_span1["data"]["gen_ai.agent.name"] == "test_agent" - - ai_client_span1_available_tool = json.loads( - ai_client_span1["data"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] - assert all( - ai_client_span1_available_tool[k] == v for k, v in available_tool.items() - ) - - assert ai_client_span1["data"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span1["data"]["gen_ai.request.messages"] == safe_serialize( - [ - { - "role": "user", - "content": [ - {"type": "text", "text": "Please use the simple test tool"} - ], - }, - ] - ) - assert ai_client_span1["data"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span1["data"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span1["data"]["gen_ai.request.top_p"] == 1.0 - assert ai_client_span1["data"]["gen_ai.usage.input_tokens"] == 10 - assert ai_client_span1["data"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span1["data"]["gen_ai.usage.output_tokens"] == 5 - assert ai_client_span1["data"]["gen_ai.usage.output_tokens.reasoning"] == 0 - assert ai_client_span1["data"]["gen_ai.usage.total_tokens"] == 15 - - tool_call = { - "arguments": '{"message": "hello"}', - "call_id": "call_123", - "name": "simple_test_tool", - "type": "function_call", - "id": "call_123", - "status": None, - } - - parsed_tool_calls = json.loads( - ai_client_span1["data"]["gen_ai.response.tool_calls"] - ) - assert len(parsed_tool_calls) == 1 - assert tool_call.items() <= parsed_tool_calls[0].items() - assert tool_span["description"] == "execute_tool simple_test_tool" assert tool_span["data"]["gen_ai.agent.name"] == "test_agent" assert tool_span["data"]["gen_ai.operation.name"] == "execute_tool" @@ -3356,45 +3026,6 @@ def simple_test_tool(message: str) -> str: assert tool_span["data"]["gen_ai.tool.input"] == '{"message": "hello"}' assert tool_span["data"]["gen_ai.tool.name"] == "simple_test_tool" assert tool_span["data"]["gen_ai.tool.output"] == "Tool executed with: hello" - assert ai_client_span2["description"] == "chat gpt-4" - assert ai_client_span2["data"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span2["data"]["gen_ai.operation.name"] == "chat" - - ai_client_span2_available_tool = json.loads( - ai_client_span2["data"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] - assert all( - ai_client_span2_available_tool[k] == v for k, v in available_tool.items() - ) - - assert ai_client_span2["data"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span2["data"]["gen_ai.request.messages"] == safe_serialize( - [ - { - "role": "tool", - "content": [ - { - "call_id": "call_123", - "output": "Tool executed with: hello", - "type": "function_call_output", - } - ], - }, - ] - ) - assert ai_client_span2["data"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span2["data"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span2["data"]["gen_ai.request.top_p"] == 1.0 - assert ( - ai_client_span2["data"]["gen_ai.response.text"] - == "Task completed using the tool" - ) - assert ai_client_span2["data"]["gen_ai.system"] == "openai" - assert ai_client_span2["data"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span2["data"]["gen_ai.usage.input_tokens"] == 15 - assert ai_client_span2["data"]["gen_ai.usage.output_tokens.reasoning"] == 0 - assert ai_client_span2["data"]["gen_ai.usage.output_tokens"] == 10 - assert ai_client_span2["data"]["gen_ai.usage.total_tokens"] == 25 @pytest.mark.asyncio From 78d5d6f0e3d929ef944621cc4221f823d65bc3a4 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 13:54:06 +0200 Subject: [PATCH 11/13] remove two more assertions --- tests/integrations/openai_agents/test_openai_agents.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index eb1893d451..a27921a628 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -2905,9 +2905,6 @@ def simple_test_tool(message: str) -> str: sentry_sdk.flush() spans = [item.payload for item in items] - assert spans[3]["name"] == "test_agent workflow" - assert spans[3]["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - tool_span = next( span for span in spans From 364b1727ccb23faf6a0b86f4b539f3d872e9f625 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 16:22:20 +0200 Subject: [PATCH 12/13] add a comment --- sentry_sdk/integrations/openai_agents/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index 179e13c16e..f42751bf1b 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -96,6 +96,7 @@ def setup_once() -> None: _patch_error_tracing() library_version = parse_version(OPENAI_AGENTS_VERSION) + # ToolContext.tool_arguments added in https://github.com/openai/openai-agents-python/commit/5e1db14da542c77f8fdd5e2e26017977ae415813 use_tool_hooks = library_version is not None and library_version >= (0, 3, 2) _patch_runner(use_tool_hooks=use_tool_hooks) From bb76a15088166cf9b8216560fd83cc7c45c06ee9 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 3 Aug 2026 16:29:01 +0200 Subject: [PATCH 13/13] gracefully fail --- sentry_sdk/integrations/openai_agents/patches/runner.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 6d1644266a..78a4bcbb26 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -86,8 +86,9 @@ def _patch_run_hooks(hooks: "RunHooks[TContext]") -> None: async def on_tool_start( context: "ToolContext[TContext]", agent: "Agent[TContext]", tool: "Tool" ) -> "None": + with capture_internal_exceptions(): + await sentry_hooks.on_tool_start(context, agent, tool) await original_on_tool_start(context, agent, tool) - await sentry_hooks.on_tool_start(context, agent, tool) @wraps(original_on_tool_end) async def on_tool_end( @@ -96,8 +97,9 @@ async def on_tool_end( tool: "Tool", result: "object", ) -> "None": + with capture_internal_exceptions(): + await sentry_hooks.on_tool_end(context, agent, tool, result) await original_on_tool_end(context, agent, tool, result) - await sentry_hooks.on_tool_end(context, agent, tool, result) hooks._sentry_is_patched = True hooks.on_tool_start = on_tool_start