Skip to content

Commit 9c306de

Browse files
committed
fix(pydantic-ai): Adapt to missing ToolManager._call_tool
1 parent bd0fc70 commit 9c306de

1 file changed

Lines changed: 76 additions & 1 deletion

File tree

  • sentry_sdk/integrations/pydantic_ai/patches

sentry_sdk/integrations/pydantic_ai/patches/tools.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,82 @@
2828

2929

3030
def _patch_tool_execution() -> None:
31+
if hasattr(ToolManager, "execute_tool_call"):
32+
_patch_execute_tool_call()
33+
34+
elif hasattr(ToolManager, "_call_tool"):
35+
# older versions
36+
_patch_call_tool()
37+
38+
39+
def _patch_execute_tool_call() -> None:
40+
original_execute_tool_call = ToolManager.execute_tool_call
41+
42+
@wraps(original_execute_tool_call)
43+
async def wrapped_execute_tool_call(
44+
self: "Any", validated: "Any", *args: "Any", **kwargs: "Any"
45+
) -> "Any":
46+
# Extract tool info before calling original
47+
call = validated.call
48+
name = call.tool_name
49+
tool = self.tools.get(name) if self.tools else None
50+
51+
# Determine tool type by checking tool.toolset
52+
tool_type = "function" # default
53+
if tool and HAS_MCP and isinstance(tool.toolset, MCPServer):
54+
tool_type = "mcp"
55+
56+
# Get agent from contextvar
57+
agent = get_current_agent()
58+
59+
if agent and tool:
60+
try:
61+
args_dict = call.args_as_dict()
62+
except Exception:
63+
args_dict = call.args if isinstance(call.args, dict) else {}
64+
65+
# Create execute_tool span
66+
# Nesting is handled by isolation_scope() to ensure proper parent-child relationships
67+
with sentry_sdk.isolation_scope():
68+
with execute_tool_span(
69+
name,
70+
args_dict,
71+
agent,
72+
tool_type=tool_type,
73+
) as span:
74+
try:
75+
result = await original_execute_tool_call(
76+
self,
77+
validated,
78+
*args,
79+
**kwargs,
80+
)
81+
update_execute_tool_span(span, result)
82+
return result
83+
except ToolRetryError as exc:
84+
exc_info = sys.exc_info()
85+
with capture_internal_exceptions():
86+
# Avoid circular import due to multi-file integration structure
87+
from sentry_sdk.integrations.pydantic_ai import (
88+
PydanticAIIntegration,
89+
)
90+
91+
integration = sentry_sdk.get_client().get_integration(
92+
PydanticAIIntegration
93+
)
94+
if (
95+
integration is not None
96+
and integration.handled_tool_call_exceptions
97+
):
98+
_capture_exception(exc, handled=True)
99+
reraise(*exc_info)
100+
101+
return await original_execute_tool_call(self, validated, *args, **kwargs)
102+
103+
ToolManager.execute_tool_call = wrapped_execute_tool_call
104+
105+
106+
def _patch_call_tool() -> None:
31107
"""
32108
Patch ToolManager._call_tool to create execute_tool spans.
33109
@@ -39,7 +115,6 @@ def _patch_tool_execution() -> None:
39115
- Dealing with signature mismatches from instrumented MCP servers
40116
- Complex nested toolset handling
41117
"""
42-
43118
original_call_tool = ToolManager._call_tool
44119

45120
@wraps(original_call_tool)

0 commit comments

Comments
 (0)