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..25c2cd6afd 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 @@ -56,16 +57,21 @@ 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: - 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..a7f5290d50 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 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)