From 7f84c886accf9f563a48da2a2c7f55313bac1420 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 21 May 2026 13:55:05 +0200 Subject: [PATCH] fix(pydantic_ai): Support pydantic-ai v2.0.0b1 API changes In v2, `result.usage` changed from a method to a property, `run_stream_events` now returns an async context manager, and `MCPServerStdio` was replaced by `MCPToolset` + `StdioTransport`. Handle both v1 and v2 APIs. Co-Authored-By: Claude Opus 4.6 --- .../pydantic_ai/spans/invoke_agent.py | 7 +-- .../pydantic_ai/test_pydantic_ai.py | 47 +++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py b/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py index 03cb707141..e8fdcd754e 100644 --- a/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +++ b/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py @@ -151,13 +151,14 @@ def update_invoke_agent_span(span: "sentry_sdk.tracing.Span", result: "Any") -> ) # Set token usage data if available - if hasattr(result, "usage") and callable(result.usage): + if hasattr(result, "usage"): try: - usage = result.usage() + usage = result.usage + if callable(usage): + usage = usage() if usage: _set_usage_data(span, usage) except Exception: - # If usage() call fails, continue without setting usage data pass # Set model name from response if available diff --git a/tests/integrations/pydantic_ai/test_pydantic_ai.py b/tests/integrations/pydantic_ai/test_pydantic_ai.py index 9a1fe79167..10ebc523a3 100644 --- a/tests/integrations/pydantic_ai/test_pydantic_ai.py +++ b/tests/integrations/pydantic_ai/test_pydantic_ai.py @@ -1,5 +1,6 @@ import asyncio import json +from contextlib import asynccontextmanager from typing import Annotated from unittest.mock import MagicMock @@ -19,6 +20,21 @@ from sentry_sdk.integrations.pydantic_ai.spans.utils import _set_usage_data +@asynccontextmanager +async def consume_stream_events(agent, prompt): + """Handle both v1 (async iterator) and v2 (async context manager) APIs.""" + result = agent.run_stream_events(prompt) + if hasattr(result, "__aenter__"): + async with result as events: + async for _ in events: + pass + yield + else: + async for _ in result: + pass + yield + + @pytest.fixture def get_test_agent(): def inner(): @@ -517,8 +533,9 @@ async def test_agent_run_stream_events( if stream_gen_ai_spans: items = capture_items("transaction", "span") - async for _ in test_agent.run_stream_events( - ["Message demonstrating the absence of truncation.", "Test input"] + async with consume_stream_events( + test_agent, + ["Message demonstrating the absence of truncation.", "Test input"], ): pass @@ -541,7 +558,7 @@ async def test_agent_run_stream_events( else: events = capture_events() - async for _ in test_agent.run_stream_events("Test input"): + async with consume_stream_events(test_agent, "Test input"): pass (transaction,) = events @@ -1751,16 +1768,30 @@ async def test_mcp_tool_execution_spans( from unittest.mock import MagicMock from pydantic_ai import Agent - from pydantic_ai.mcp import MCPServerStdio from pydantic_ai.toolsets.combined import CombinedToolset import sentry_sdk + try: + from pydantic_ai.mcp import MCPServerStdio + except ImportError: + from pydantic_ai.mcp import MCPToolset, StdioTransport + + MCPServerStdio = None + # Create mock MCP server - mock_server = MCPServerStdio( - command="python", - args=["-m", "test_server"], - ) + if MCPServerStdio is not None: + mock_server = MCPServerStdio( + command="python", + args=["-m", "test_server"], + ) + else: + mock_server = MCPToolset( + StdioTransport( + command="python", + args=["-m", "test_server"], + ) + ) # Mock the server's internal methods mock_server._client = MagicMock()