Skip to content

Commit 44f1ddb

Browse files
fix(pydantic-ai): Stop capturing tool execution spans when validation fails
1 parent 1e758fa commit 44f1ddb

3 files changed

Lines changed: 47 additions & 35 deletions

File tree

‎sentry_sdk/integrations/pydantic_ai/__init__.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,41 @@ async def on_model_request(
5151
update_ai_client_span(span, response)
5252
return response
5353

54+
# @hooks.on.tool_execute
55+
# async def sentry_wrap_tool_execute(
56+
# ctx: "RunContext[Any]",
57+
# *,
58+
# call: "ToolCallPart",
59+
# tool_def: "ToolDefinition",
60+
# args: "ValidatedToolArgs",
61+
# handler: "WrapToolExecuteHandler",
62+
# ) -> "Any":
63+
# with execute_tool_span(
64+
# call.tool_name,
65+
# args,
66+
# ctx.agent,
67+
# tool_definition=tool_def,
68+
# ) as span:
69+
# try:
70+
# result = await handler(args)
71+
# update_execute_tool_span(span, result)
72+
# return result
73+
# except ToolRetryError as exc:
74+
# exc_info = sys.exc_info()
75+
# with capture_internal_exceptions():
76+
# from sentry_sdk.integrations.pydantic_ai import (
77+
# PydanticAIIntegration,
78+
# )
79+
# integration = sentry_sdk.get_client().get_integration(
80+
# PydanticAIIntegration,
81+
# )
82+
# if (
83+
# integration is not None
84+
# and integration.handled_tool_call_exceptions
85+
# ):
86+
# _capture_exception(exc, handled=True)
87+
# reraise(*exc_info)
88+
5489
original_init = Agent.__init__
5590

5691
@functools.wraps(original_init)

‎sentry_sdk/integrations/pydantic_ai/patches/tools.py‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sys
2+
from contextlib import nullcontext
23
from functools import wraps
34
from typing import TYPE_CHECKING
45

@@ -60,11 +61,15 @@ async def wrapped_execute_tool_call(
6061
# Create execute_tool span
6162
# Nesting is handled by isolation_scope() to ensure proper parent-child relationships
6263
with sentry_sdk.isolation_scope():
63-
with execute_tool_span(
64-
name,
65-
args_dict,
66-
agent,
67-
tool_definition=selected_tool_definition,
64+
with (
65+
execute_tool_span(
66+
name,
67+
args_dict,
68+
agent,
69+
tool_definition=selected_tool_definition,
70+
)
71+
if validated.args_valid
72+
else nullcontext()
6873
) as span:
6974
try:
7075
result = await original_execute_tool_call(
@@ -73,7 +78,8 @@ async def wrapped_execute_tool_call(
7378
*args,
7479
**kwargs,
7580
)
76-
update_execute_tool_span(span, result)
81+
if span is not None:
82+
update_execute_tool_span(span, result)
7783
return result
7884
except ToolRetryError as exc:
7985
exc_info = sys.exc_info()

‎tests/integrations/pydantic_ai/test_pydantic_ai.py‎

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,24 +1022,6 @@ def add_numbers(a: Annotated[int, Field(gt=0, lt=0)], b: int) -> int:
10221022
chat_spans = [
10231023
s for s in spans if s["attributes"].get("sentry.op", "") == "gen_ai.chat"
10241024
]
1025-
tool_spans = [
1026-
s
1027-
for s in spans
1028-
if s["attributes"].get("sentry.op", "") == "gen_ai.execute_tool"
1029-
]
1030-
1031-
# Should have tool spans
1032-
assert len(tool_spans) >= 1
1033-
1034-
# Check tool spans
1035-
model_retry_tool_span = tool_spans[0]
1036-
assert "execute_tool" in model_retry_tool_span["name"]
1037-
assert (
1038-
model_retry_tool_span["attributes"]["gen_ai.operation.name"]
1039-
== "execute_tool"
1040-
)
1041-
assert model_retry_tool_span["attributes"]["gen_ai.tool.name"] == "add_numbers"
1042-
assert "gen_ai.tool.input" in model_retry_tool_span["attributes"]
10431025

10441026
# Check chat spans have available_tools
10451027
assert "gen_ai.request.available_tools" in chat_spans[0]["attributes"]
@@ -1073,17 +1055,6 @@ def add_numbers(a: Annotated[int, Field(gt=0, lt=0)], b: int) -> int:
10731055

10741056
# Find child span types (invoke_agent is the transaction, not a child span)
10751057
chat_spans = [s for s in spans if s["op"] == "gen_ai.chat"]
1076-
tool_spans = [s for s in spans if s["op"] == "gen_ai.execute_tool"]
1077-
1078-
# Should have tool spans
1079-
assert len(tool_spans) >= 1
1080-
1081-
# Check tool spans
1082-
model_retry_tool_span = tool_spans[0]
1083-
assert "execute_tool" in model_retry_tool_span["description"]
1084-
assert model_retry_tool_span["data"]["gen_ai.operation.name"] == "execute_tool"
1085-
assert model_retry_tool_span["data"]["gen_ai.tool.name"] == "add_numbers"
1086-
assert "gen_ai.tool.input" in model_retry_tool_span["data"]
10871058

10881059
# Check chat spans have available_tools
10891060
assert "gen_ai.request.available_tools" in chat_spans[0]["data"]

0 commit comments

Comments
 (0)