Skip to content

Commit f71a604

Browse files
fix(openai-agents): Patch execute_handoffs() functions following library refactor (#5452)
`RunImpl.execute_handoffs()` was moved to `agents.run_internal.turn_resolution.execute_handoffs()`. Patch the new function by reusing existing wrapper logic.
1 parent bea608c commit f71a604

File tree

3 files changed

+74
-45
lines changed

3 files changed

+74
-45
lines changed

sentry_sdk/integrations/openai_agents/__init__.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
_get_all_tools,
99
_run_single_turn,
1010
_run_single_turn_streamed,
11+
_execute_handoffs,
1112
_create_run_wrapper,
1213
_create_run_streamed_wrapper,
1314
_patch_agent_run,
@@ -32,10 +33,11 @@
3233
try:
3334
# AgentRunner methods moved in v0.8
3435
# https://github.com/openai/openai-agents-python/commit/3ce7c24d349b77bb750062b7e0e856d9ff48a5d5#diff-7470b3a5c5cbe2fcbb2703dc24f326f45a5819d853be2b1f395d122d278cd911
35-
from agents.run_internal import run_loop, turn_preparation
36+
from agents.run_internal import run_loop, turn_preparation, turn_resolution
3637
except ImportError:
3738
run_loop = None
3839
turn_preparation = None
40+
turn_resolution = None
3941

4042
from typing import TYPE_CHECKING
4143

@@ -86,7 +88,7 @@ class OpenAIAgentsIntegration(Integration):
8688
Hosted MCP Tools are run as part of the Responses API call, and involve OpenAI reaching out to an external MCP server.
8789
An agent can handoff to another agent, also directed by the return value of the Responses API and run post-API call in the loop.
8890
Handoffs are a way to switch agent-wide configuration.
89-
- Handoffs are executed by calling `RunImpl.execute_handoffs()`. The method is patched in `patched_execute_handoffs()`
91+
- Handoffs are executed by calling `RunImpl.execute_handoffs()`. The method is patched with `patches._execute_handoffs()`
9092
"""
9193

9294
identifier = "openai_agents"
@@ -139,6 +141,20 @@ async def new_wrapped_run_single_turn_streamed(
139141

140142
agents.run.run_single_turn_streamed = new_wrapped_run_single_turn_streamed
141143

144+
original_execute_handoffs = turn_resolution.execute_handoffs
145+
146+
@wraps(original_execute_handoffs)
147+
async def new_wrapped_execute_handoffs(
148+
*args: "Any", **kwargs: "Any"
149+
) -> "SingleStepResult":
150+
return await _execute_handoffs(
151+
original_execute_handoffs, *args, **kwargs
152+
)
153+
154+
agents.run_internal.turn_resolution.execute_handoffs = (
155+
new_wrapped_execute_handoffs
156+
)
157+
142158
return
143159

144160
original_get_all_tools = AgentRunner._get_all_tools
@@ -188,3 +204,15 @@ async def old_wrapped_run_single_turn_streamed(
188204
agents.run.AgentRunner._run_single_turn_streamed = classmethod(
189205
old_wrapped_run_single_turn_streamed
190206
)
207+
208+
original_execute_handoffs = agents._run_impl.RunImpl.execute_handoffs
209+
210+
@wraps(agents._run_impl.RunImpl.execute_handoffs.__func__)
211+
async def old_wrapped_execute_handoffs(
212+
cls: "agents.Runner", *args: "Any", **kwargs: "Any"
213+
) -> "SingleStepResult":
214+
return await _execute_handoffs(original_execute_handoffs, *args, **kwargs)
215+
216+
agents._run_impl.RunImpl.execute_handoffs = classmethod(
217+
old_wrapped_execute_handoffs
218+
)
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
from .models import _get_model # noqa: F401
22
from .tools import _get_all_tools # noqa: F401
33
from .runner import _create_run_wrapper, _create_run_streamed_wrapper # noqa: F401
4-
from .agent_run import _run_single_turn, _run_single_turn_streamed, _patch_agent_run # noqa: F401
4+
from .agent_run import (
5+
_run_single_turn,
6+
_run_single_turn_streamed,
7+
_execute_handoffs,
8+
_patch_agent_run,
9+
) # noqa: F401
510
from .error_tracing import _patch_error_tracing # noqa: F401

sentry_sdk/integrations/openai_agents/patches/agent_run.py

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -166,55 +166,52 @@ async def _run_single_turn_streamed(
166166
return result
167167

168168

169-
def _patch_agent_run() -> None:
169+
async def _execute_handoffs(
170+
original_execute_handoffs: "Callable[..., SingleStepResult]",
171+
*args: "Any",
172+
**kwargs: "Any",
173+
) -> "SingleStepResult":
170174
"""
171-
Patches AgentRunner methods to create agent invocation spans.
172-
This directly patches the execution flow to track when agents start and stop.
175+
Patched execute_handoffs that
176+
- creates and manages handoff spans.
177+
- ends the agent invocation span.
178+
- ends the workflow span if the response is streamed and an exception is raised in `execute_handoffs()`.
173179
"""
174180

175-
# Store original methods
176-
original_execute_handoffs = agents._run_impl.RunImpl.execute_handoffs
177-
original_execute_final_output = agents._run_impl.RunImpl.execute_final_output
181+
context_wrapper = kwargs.get("context_wrapper")
182+
run_handoffs = kwargs.get("run_handoffs")
183+
agent = kwargs.get("agent")
178184

179-
@wraps(
180-
original_execute_handoffs.__func__
181-
if hasattr(original_execute_handoffs, "__func__")
182-
else original_execute_handoffs
183-
)
184-
async def patched_execute_handoffs(
185-
cls: "agents.Runner", *args: "Any", **kwargs: "Any"
186-
) -> "Any":
187-
"""
188-
Patched execute_handoffs that
189-
- creates and manages handoff spans.
190-
- ends the agent invocation span.
191-
- ends the workflow span if the response is streamed and an exception is raised in `execute_handoffs()`.
192-
"""
185+
# Create Sentry handoff span for the first handoff (agents library only processes the first one)
186+
if run_handoffs:
187+
first_handoff = run_handoffs[0]
188+
handoff_agent_name = first_handoff.handoff.agent_name
189+
handoff_span(context_wrapper, agent, handoff_agent_name)
193190

194-
context_wrapper = kwargs.get("context_wrapper")
195-
run_handoffs = kwargs.get("run_handoffs")
196-
agent = kwargs.get("agent")
191+
# Call original method with all parameters
192+
try:
193+
result = await original_execute_handoffs(*args, **kwargs)
194+
except Exception:
195+
exc_info = sys.exc_info()
196+
with capture_internal_exceptions():
197+
_close_streaming_workflow_span(agent)
198+
reraise(*exc_info)
199+
finally:
200+
# End span for current agent after handoff processing is complete
201+
if agent and context_wrapper and _has_active_agent_span(context_wrapper):
202+
end_invoke_agent_span(context_wrapper, agent)
197203

198-
# Create Sentry handoff span for the first handoff (agents library only processes the first one)
199-
if run_handoffs:
200-
first_handoff = run_handoffs[0]
201-
handoff_agent_name = first_handoff.handoff.agent_name
202-
handoff_span(context_wrapper, agent, handoff_agent_name)
204+
return result
203205

204-
# Call original method with all parameters
205-
try:
206-
result = await original_execute_handoffs(*args, **kwargs)
207-
except Exception:
208-
exc_info = sys.exc_info()
209-
with capture_internal_exceptions():
210-
_close_streaming_workflow_span(agent)
211-
reraise(*exc_info)
212-
finally:
213-
# End span for current agent after handoff processing is complete
214-
if agent and context_wrapper and _has_active_agent_span(context_wrapper):
215-
end_invoke_agent_span(context_wrapper, agent)
216206

217-
return result
207+
def _patch_agent_run() -> None:
208+
"""
209+
Patches AgentRunner methods to create agent invocation spans.
210+
This directly patches the execution flow to track when agents start and stop.
211+
"""
212+
213+
# Store original methods
214+
original_execute_final_output = agents._run_impl.RunImpl.execute_final_output
218215

219216
@wraps(
220217
original_execute_final_output.__func__
@@ -250,7 +247,6 @@ async def patched_execute_final_output(
250247
return result
251248

252249
# Apply patches
253-
agents._run_impl.RunImpl.execute_handoffs = classmethod(patched_execute_handoffs)
254250
agents._run_impl.RunImpl.execute_final_output = classmethod(
255251
patched_execute_final_output
256252
)

0 commit comments

Comments
 (0)