From 932993ddcd44bd454f6713a18bdb3763a8018fa4 Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Tue, 4 Nov 2025 15:46:44 +0100 Subject: [PATCH 1/3] fix(integrations): properly handle exceptions in tool calls --- .../pydantic_ai/patches/agent_run.py | 15 +-------------- .../integrations/pydantic_ai/patches/tools.py | 15 ++++++++++----- sentry_sdk/integrations/pydantic_ai/utils.py | 19 +++++++++++++++---- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/sentry_sdk/integrations/pydantic_ai/patches/agent_run.py b/sentry_sdk/integrations/pydantic_ai/patches/agent_run.py index 7c403c7ba3..5e0515b3c3 100644 --- a/sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +++ b/sentry_sdk/integrations/pydantic_ai/patches/agent_run.py @@ -1,10 +1,9 @@ from functools import wraps import sentry_sdk -from sentry_sdk.tracing_utils import set_span_errored -from sentry_sdk.utils import event_from_exception from ..spans import invoke_agent_span, update_invoke_agent_span +from ..utils import _capture_exception from typing import TYPE_CHECKING from pydantic_ai.agent import Agent # type: ignore @@ -13,18 +12,6 @@ from typing import Any, Callable, Optional -def _capture_exception(exc): - # type: (Any) -> None - set_span_errored() - - event, hint = event_from_exception( - exc, - client_options=sentry_sdk.get_client().options, - mechanism={"type": "pydantic_ai", "handled": False}, - ) - sentry_sdk.capture_event(event, hint=hint) - - class _StreamingContextManagerWrapper: """Wrapper for streaming methods that return async context managers.""" diff --git a/sentry_sdk/integrations/pydantic_ai/patches/tools.py b/sentry_sdk/integrations/pydantic_ai/patches/tools.py index 41708c1be9..e90674cab6 100644 --- a/sentry_sdk/integrations/pydantic_ai/patches/tools.py +++ b/sentry_sdk/integrations/pydantic_ai/patches/tools.py @@ -5,6 +5,7 @@ import sentry_sdk from ..spans import execute_tool_span, update_execute_tool_span +from ..utils import _capture_exception from typing import TYPE_CHECKING @@ -61,11 +62,15 @@ async def wrapped_call_tool(self, call, allow_partial, wrap_validation_errors): args_dict = call.args if isinstance(call.args, dict) else {} with execute_tool_span(name, args_dict, agent, tool_type=tool_type) as span: - result = await original_call_tool( - self, call, allow_partial, wrap_validation_errors - ) - update_execute_tool_span(span, result) - return result + try: + result = await original_call_tool( + self, call, allow_partial, wrap_validation_errors + ) + update_execute_tool_span(span, result) + return result + except Exception as exc: + _capture_exception(exc) + raise exc from None # No span context - just call original return await original_call_tool( diff --git a/sentry_sdk/integrations/pydantic_ai/utils.py b/sentry_sdk/integrations/pydantic_ai/utils.py index 3f58869857..f321cab1d8 100644 --- a/sentry_sdk/integrations/pydantic_ai/utils.py +++ b/sentry_sdk/integrations/pydantic_ai/utils.py @@ -1,14 +1,13 @@ import sentry_sdk -from sentry_sdk.ai.utils import set_data_normalized from sentry_sdk.consts import SPANDATA from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.utils import safe_serialize +from sentry_sdk.tracing_utils import set_span_errored +from sentry_sdk.utils import event_from_exception, safe_serialize from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, List, Dict - from pydantic_ai.usage import RequestUsage # type: ignore + from typing import Any # type: ignore def _should_send_prompts(): @@ -173,3 +172,15 @@ def _set_available_tools(span, agent): except Exception: # If we can't extract tools, just skip it pass + + +def _capture_exception(exc): + # type: (Any) -> None + set_span_errored() + + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "pydantic_ai", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) From 1db3a250d896cef3aa281109539253583b37eded Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Tue, 4 Nov 2025 16:08:48 +0100 Subject: [PATCH 2/3] fix: tool call arg resolution --- sentry_sdk/integrations/pydantic_ai/patches/tools.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/pydantic_ai/patches/tools.py b/sentry_sdk/integrations/pydantic_ai/patches/tools.py index e90674cab6..25c2cd6afd 100644 --- a/sentry_sdk/integrations/pydantic_ai/patches/tools.py +++ b/sentry_sdk/integrations/pydantic_ai/patches/tools.py @@ -57,9 +57,10 @@ async def wrapped_call_tool(self, call, allow_partial, wrap_validation_errors): ) agent = agent_data.get("_agent") - # Get args for span (before validation) - # call.args can be a string (JSON) or dict - args_dict = call.args if isinstance(call.args, dict) else {} + try: + args_dict = call.args_as_dict() + except Exception: + args_dict = call.args if isinstance(call.args, dict) else {} with execute_tool_span(name, args_dict, agent, tool_type=tool_type) as span: try: From ea170e631af86cf7639b18b9f02eb04021bfed74 Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Tue, 4 Nov 2025 16:15:28 +0100 Subject: [PATCH 3/3] fix: linting issue --- sentry_sdk/integrations/pydantic_ai/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/pydantic_ai/utils.py b/sentry_sdk/integrations/pydantic_ai/utils.py index f321cab1d8..a7f5290d50 100644 --- a/sentry_sdk/integrations/pydantic_ai/utils.py +++ b/sentry_sdk/integrations/pydantic_ai/utils.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any # type: ignore + from typing import Any def _should_send_prompts():