From 02af5df52ce3230041e188f0bd52460937bbe3a0 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 11:29:06 +0100 Subject: [PATCH 1/5] fix(openai-agents): Patch tool functions following library refactor --- .../integrations/openai_agents/__init__.py | 34 ++++- .../openai_agents/patches/__init__.py | 5 +- .../openai_agents/patches/tools.py | 123 ++++++++++-------- 3 files changed, 104 insertions(+), 58 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index deb136de01..b93d835dc7 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -1,8 +1,10 @@ from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.utils import parse_version from .patches import ( _create_get_model_wrapper, - _create_get_all_tools_wrapper, + _create_runner_get_all_tools_wrapper, + _create_run_loop_get_all_tools_wrapper, _create_run_wrapper, _create_run_streamed_wrapper, _patch_agent_run, @@ -17,11 +19,21 @@ # after it, even if we don't use it. import agents from agents.run import DEFAULT_AGENT_RUNNER + from agents.version import __version__ as OPENAI_AGENTS_VERSION except ImportError: raise DidNotEnable("OpenAI Agents not installed") +try: + # AgentRunner methods moved in v0.8 + # https://github.com/openai/openai-agents-python/commit/3ce7c24d349b77bb750062b7e0e856d9ff48a5d5#diff-7470b3a5c5cbe2fcbb2703dc24f326f45a5819d853be2b1f395d122d278cd911 + from agents.run_internal import run_loop, turn_preparation +except ImportError: + run_loop = None + turn_preparation = None + + def _patch_runner() -> 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 @@ -45,9 +57,15 @@ def _patch_model() -> None: ) -def _patch_tools() -> None: +def _patch_agent_runner_get_all_tools() -> None: agents.run.AgentRunner._get_all_tools = classmethod( - _create_get_all_tools_wrapper(agents.run.AgentRunner._get_all_tools), + _create_runner_get_all_tools_wrapper(agents.run.AgentRunner._get_all_tools), + ) + + +def _patch_run_get_all_tools() -> None: + agents.run.get_all_tools = _create_run_loop_get_all_tools_wrapper( + run_loop.get_all_tools ) @@ -57,6 +75,14 @@ class OpenAIAgentsIntegration(Integration): @staticmethod def setup_once() -> None: _patch_error_tracing() - _patch_tools() _patch_model() _patch_runner() + + library_version = parse_version(OPENAI_AGENTS_VERSION) + if library_version is not None and library_version >= ( + 0, + 8, + ): + _patch_run_get_all_tools() + + _patch_agent_runner_get_all_tools() diff --git a/sentry_sdk/integrations/openai_agents/patches/__init__.py b/sentry_sdk/integrations/openai_agents/patches/__init__.py index b53ca79e19..675f8c4fc4 100644 --- a/sentry_sdk/integrations/openai_agents/patches/__init__.py +++ b/sentry_sdk/integrations/openai_agents/patches/__init__.py @@ -1,5 +1,8 @@ from .models import _create_get_model_wrapper # noqa: F401 -from .tools import _create_get_all_tools_wrapper # noqa: F401 +from .tools import ( + _create_runner_get_all_tools_wrapper, + _create_run_loop_get_all_tools_wrapper, +) # noqa: F401 from .runner import _create_run_wrapper, _create_run_streamed_wrapper # noqa: F401 from .agent_run import _patch_agent_run # noqa: F401 from .error_tracing import _patch_error_tracing # noqa: F401 diff --git a/sentry_sdk/integrations/openai_agents/patches/tools.py b/sentry_sdk/integrations/openai_agents/patches/tools.py index d14a3019aa..bb72949139 100644 --- a/sentry_sdk/integrations/openai_agents/patches/tools.py +++ b/sentry_sdk/integrations/openai_agents/patches/tools.py @@ -1,4 +1,4 @@ -from functools import wraps +from functools import wraps, partial from sentry_sdk.integrations import DidNotEnable @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, Callable + from typing import Any, Callable, Awaitable try: import agents @@ -15,13 +15,62 @@ raise DidNotEnable("OpenAI Agents not installed") -def _create_get_all_tools_wrapper( +async def _get_all_tools( + original_get_all_tools: "Callable[..., Awaitable[list[agents.Tool]]]", + agent: "agents.Agent", + context_wrapper: "agents.RunContextWrapper", +) -> "list[agents.Tool]": + # Get the original tools + tools = await original_get_all_tools(agent, context_wrapper) + + wrapped_tools = [] + for tool in tools: + # Wrap only the function tools (for now) + if tool.__class__.__name__ != "FunctionTool": + wrapped_tools.append(tool) + continue + + # Create a new FunctionTool with our wrapped invoke method + original_on_invoke = tool.on_invoke_tool + + def create_wrapped_invoke( + current_tool: "agents.Tool", current_on_invoke: "Callable[..., Any]" + ) -> "Callable[..., Any]": + @wraps(current_on_invoke) + async def sentry_wrapped_on_invoke_tool( + *args: "Any", **kwargs: "Any" + ) -> "Any": + with execute_tool_span(current_tool, *args, **kwargs) as span: + # We can not capture exceptions in tool execution here because + # `_on_invoke_tool` is swallowing the exception here: + # https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py#L409-L422 + # And because function_tool is a decorator with `default_tool_error_function` set as a default parameter + # I was unable to monkey patch it because those are evaluated at module import time + # and the SDK is too late to patch it. I was also unable to patch `_on_invoke_tool_impl` + # because it is nested inside this import time code. As if they made it hard to patch on purpose... + result = await current_on_invoke(*args, **kwargs) + update_execute_tool_span(span, agent, current_tool, result) + + return result + + return sentry_wrapped_on_invoke_tool + + wrapped_tool = agents.FunctionTool( + name=tool.name, + description=tool.description, + params_json_schema=tool.params_json_schema, + on_invoke_tool=create_wrapped_invoke(tool, original_on_invoke), + strict_json_schema=tool.strict_json_schema, + is_enabled=tool.is_enabled, + ) + wrapped_tools.append(wrapped_tool) + + return wrapped_tools + + +def _create_runner_get_all_tools_wrapper( original_get_all_tools: "Callable[..., Any]", ) -> "Callable[..., Any]": - """ - Wraps the agents.Runner._get_all_tools method of the Runner class to wrap all function tools with Sentry instrumentation. - """ - @wraps( original_get_all_tools.__func__ if hasattr(original_get_all_tools, "__func__") @@ -32,51 +81,19 @@ async def wrapped_get_all_tools( agent: "agents.Agent", context_wrapper: "agents.RunContextWrapper", ) -> "list[agents.Tool]": - # Get the original tools - tools = await original_get_all_tools(agent, context_wrapper) - - wrapped_tools = [] - for tool in tools: - # Wrap only the function tools (for now) - if tool.__class__.__name__ != "FunctionTool": - wrapped_tools.append(tool) - continue - - # Create a new FunctionTool with our wrapped invoke method - original_on_invoke = tool.on_invoke_tool - - def create_wrapped_invoke( - current_tool: "agents.Tool", current_on_invoke: "Callable[..., Any]" - ) -> "Callable[..., Any]": - @wraps(current_on_invoke) - async def sentry_wrapped_on_invoke_tool( - *args: "Any", **kwargs: "Any" - ) -> "Any": - with execute_tool_span(current_tool, *args, **kwargs) as span: - # We can not capture exceptions in tool execution here because - # `_on_invoke_tool` is swallowing the exception here: - # https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py#L409-L422 - # And because function_tool is a decorator with `default_tool_error_function` set as a default parameter - # I was unable to monkey patch it because those are evaluated at module import time - # and the SDK is too late to patch it. I was also unable to patch `_on_invoke_tool_impl` - # because it is nested inside this import time code. As if they made it hard to patch on purpose... - result = await current_on_invoke(*args, **kwargs) - update_execute_tool_span(span, agent, current_tool, result) - - return result - - return sentry_wrapped_on_invoke_tool - - wrapped_tool = agents.FunctionTool( - name=tool.name, - description=tool.description, - params_json_schema=tool.params_json_schema, - on_invoke_tool=create_wrapped_invoke(tool, original_on_invoke), - strict_json_schema=tool.strict_json_schema, - is_enabled=tool.is_enabled, - ) - wrapped_tools.append(wrapped_tool) - - return wrapped_tools + return await _get_all_tools(original_get_all_tools, agent, context_wrapper) + + return wrapped_get_all_tools + + +def _create_run_loop_get_all_tools_wrapper( + original_get_all_tools: "Callable[..., Any]", +) -> "Callable[..., Any]": + @wraps(original_get_all_tools) + async def wrapped_get_all_tools( + agent: "agents.Agent", + context_wrapper: "agents.RunContextWrapper", + ) -> "list[agents.Tool]": + return await _get_all_tools(original_get_all_tools, agent, context_wrapper) return wrapped_get_all_tools From a7f08cc705fec4a69efdf24eb9836ebcad231156 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 11:30:35 +0100 Subject: [PATCH 2/5] . --- sentry_sdk/integrations/openai_agents/patches/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/tools.py b/sentry_sdk/integrations/openai_agents/patches/tools.py index bb72949139..561d9449e2 100644 --- a/sentry_sdk/integrations/openai_agents/patches/tools.py +++ b/sentry_sdk/integrations/openai_agents/patches/tools.py @@ -1,4 +1,4 @@ -from functools import wraps, partial +from functools import wraps from sentry_sdk.integrations import DidNotEnable From 3679c96648e0effdd8047b12423a80d52703f07c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 11:31:57 +0100 Subject: [PATCH 3/5] add early return --- 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 b93d835dc7..4395e54eb7 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -84,5 +84,6 @@ def setup_once() -> None: 8, ): _patch_run_get_all_tools() + return _patch_agent_runner_get_all_tools() From 0047eb3c932138fd3ee972fb2b274c55e86e3e3d Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 13:37:18 +0100 Subject: [PATCH 4/5] remove indirection --- .../integrations/openai_agents/__init__.py | 45 +++++++++++-------- .../openai_agents/patches/__init__.py | 5 +-- .../openai_agents/patches/tools.py | 31 ------------- 3 files changed, 28 insertions(+), 53 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index 4395e54eb7..93e43f8bb3 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -1,10 +1,11 @@ from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.utils import parse_version +from functools import wraps + from .patches import ( _create_get_model_wrapper, - _create_runner_get_all_tools_wrapper, - _create_run_loop_get_all_tools_wrapper, + _get_all_tools, _create_run_wrapper, _create_run_streamed_wrapper, _patch_agent_run, @@ -19,6 +20,7 @@ # after it, even if we don't use it. import agents from agents.run import DEFAULT_AGENT_RUNNER + from agents.run import AgentRunner from agents.version import __version__ as OPENAI_AGENTS_VERSION except ImportError: @@ -28,10 +30,9 @@ try: # AgentRunner methods moved in v0.8 # https://github.com/openai/openai-agents-python/commit/3ce7c24d349b77bb750062b7e0e856d9ff48a5d5#diff-7470b3a5c5cbe2fcbb2703dc24f326f45a5819d853be2b1f395d122d278cd911 - from agents.run_internal import run_loop, turn_preparation + from agents.run_internal import run_loop except ImportError: run_loop = None - turn_preparation = None def _patch_runner() -> None: @@ -57,18 +58,6 @@ def _patch_model() -> None: ) -def _patch_agent_runner_get_all_tools() -> None: - agents.run.AgentRunner._get_all_tools = classmethod( - _create_runner_get_all_tools_wrapper(agents.run.AgentRunner._get_all_tools), - ) - - -def _patch_run_get_all_tools() -> None: - agents.run.get_all_tools = _create_run_loop_get_all_tools_wrapper( - run_loop.get_all_tools - ) - - class OpenAIAgentsIntegration(Integration): identifier = "openai_agents" @@ -83,7 +72,27 @@ def setup_once() -> None: 0, 8, ): - _patch_run_get_all_tools() + + @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 return - _patch_agent_runner_get_all_tools() + 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) diff --git a/sentry_sdk/integrations/openai_agents/patches/__init__.py b/sentry_sdk/integrations/openai_agents/patches/__init__.py index 675f8c4fc4..ab3948bdc1 100644 --- a/sentry_sdk/integrations/openai_agents/patches/__init__.py +++ b/sentry_sdk/integrations/openai_agents/patches/__init__.py @@ -1,8 +1,5 @@ from .models import _create_get_model_wrapper # noqa: F401 -from .tools import ( - _create_runner_get_all_tools_wrapper, - _create_run_loop_get_all_tools_wrapper, -) # noqa: F401 +from .tools import _get_all_tools # noqa: F401 from .runner import _create_run_wrapper, _create_run_streamed_wrapper # noqa: F401 from .agent_run import _patch_agent_run # noqa: F401 from .error_tracing import _patch_error_tracing # noqa: F401 diff --git a/sentry_sdk/integrations/openai_agents/patches/tools.py b/sentry_sdk/integrations/openai_agents/patches/tools.py index 561d9449e2..7674c24a8d 100644 --- a/sentry_sdk/integrations/openai_agents/patches/tools.py +++ b/sentry_sdk/integrations/openai_agents/patches/tools.py @@ -66,34 +66,3 @@ async def sentry_wrapped_on_invoke_tool( wrapped_tools.append(wrapped_tool) return wrapped_tools - - -def _create_runner_get_all_tools_wrapper( - original_get_all_tools: "Callable[..., Any]", -) -> "Callable[..., Any]": - @wraps( - original_get_all_tools.__func__ - if hasattr(original_get_all_tools, "__func__") - else original_get_all_tools - ) - async def 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) - - return wrapped_get_all_tools - - -def _create_run_loop_get_all_tools_wrapper( - original_get_all_tools: "Callable[..., Any]", -) -> "Callable[..., Any]": - @wraps(original_get_all_tools) - async def wrapped_get_all_tools( - agent: "agents.Agent", - context_wrapper: "agents.RunContextWrapper", - ) -> "list[agents.Tool]": - return await _get_all_tools(original_get_all_tools, agent, context_wrapper) - - return wrapped_get_all_tools From 0a2ede87e8691c7b3183b34fdee44c83279c1f31 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 12 Feb 2026 09:38:02 +0100 Subject: [PATCH 5/5] . --- sentry_sdk/integrations/openai_agents/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index e12a9c2a65..0c551fd9bd 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -74,7 +74,7 @@ class OpenAIAgentsIntegration(Integration): - `DEFAULT_AGENT_RUNNER.run()` and `DEFAULT_AGENT_RUNNER.run_streamed()` are patched in `_patch_runner()` with `_create_run_wrapper()` and `_create_run_streamed_wrapper()`, respectively. 3. In a loop, the agent repeatedly calls the Responses API, maintaining a conversation history that includes previous messages and tool results, which is passed to each call. - A Model instance is created at the start of the loop by calling the `Runner._get_model()`. We patch the Model instance using `_create_get_model_wrapper()` in `_patch_model()`. - - Available tools are also deteremined at the start of the loop, with `Runner._get_all_tools()`. We patch Tool instances by iterating through the returned tools in `_get_all_tools()`. + - Available tools are also deteremined at the start of the loop, with `Runner._get_all_tools()`. We patch Tool instances by iterating through the returned tools in `patches._get_all_tools()`. - In each loop iteration, `run_single_turn()` or `run_single_turn_streamed()` is responsible for calling the Responses API, patched with `patched_run_single_turn()` and `patched_run_single_turn_streamed()`. 4. On loop termination, `RunImpl.execute_final_output()` is called. The function is patched with `patched_execute_final_output()`.