Skip to content

feat(pydantic_ai): Add instrumentation for Agent.iter()#5607

Open
dfm88 wants to merge 1 commit intogetsentry:masterfrom
dfm88:feat/pydantic-ai-iter-instrumentation
Open

feat(pydantic_ai): Add instrumentation for Agent.iter()#5607
dfm88 wants to merge 1 commit intogetsentry:masterfrom
dfm88:feat/pydantic-ai-iter-instrumentation

Conversation

@dfm88
Copy link

@dfm88 dfm88 commented Mar 6, 2026

Description

When using Agent.iter() to drive an agent step by step (common in streaming or WebSocket scenarios), no Sentry spans are created at all. Tool calls, model requests, and the agent invocation itself are completely invisible. Only run() and run_stream() were instrumented.

sentry_sdk/integrations/pydantic_ai/patches/agent_run.py

  • Patched Agent.iter using the existing _create_streaming_wrapper, with is_streaming=False since iter() is node-by-node iteration, not token streaming.

  • Added passthrough detection in _StreamingContextManagerWrapper.__aenter__: if an agent is already on the contextvar stack (meaning we're inside a run() or run_stream() call that already set up instrumentation), we skip creating a new invoke_agent span and just forward to the original context manager. This is needed because in pydantic-ai v1.x, both run() and run_stream() internally call iter().

  • Used isinstance(self._result, AgentRun) to distinguish between the two result types that flow through the wrapper: AgentRun (from iter()) wraps the final result in its .result property, while StreamedRunResult (from run_stream()) can be passed directly to update_invoke_agent_span. This follows the project's guideline of preferring isinstance() over hasattr().

  • Parameterized is_streaming in _create_streaming_wrapper — it was previously hardcoded to True, which was correct for run_stream() but wrong for iter().

Issues

Reminders

@dfm88 dfm88 requested a review from a team as a code owner March 6, 2026 14:42
@github-actions
Copy link
Contributor

github-actions bot commented Mar 6, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (pydantic_ai) Add instrumentation for Agent.iter() by dfm88 in #5607

Bug Fixes 🐛

  • (celery) Propagate user-set headers by sentrivana in #5581
  • (utils) Avoid double serialization of strings in safe_serialize by ericapisani in #5587

Documentation 📚

  • (openai-agents) Remove inapplicable comment by alexander-alderman-webb in #5495
  • Add AGENTS.md by sentrivana in #5579
  • Add set_attribute example to changelog by sentrivana in #5578

Internal Changes 🔧

  • (openai-agents) Expect namespace tool field for new openai versions by alexander-alderman-webb in #5599

🤖 This preview updates automatically when you update the PR.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

# AgentRun (from iter()) wraps the final result in .result;
# StreamedRunResult (from run_stream()) is used directly.
if isinstance(self._result, AgentRun):
result = self._result.result
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unprotected AgentRun.result access may raise in __aexit__

Medium Severity

Accessing self._result.result on an AgentRun in __aexit__ is unprotected. According to pydantic-ai docs, AgentRun.result is only available after the run reaches an End node. If a user exits the iter() context manager early (e.g., break in the iteration loop), __aexit__ is called with exc_type=None but the run hasn't completed, so .result may raise. This causes instrumentation code to introduce an unexpected exception to the user. The access needs a try/except guard, similar to how update_invoke_agent_span protects .usage() and .response access.

Fix in Cursor Fix in Web

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .result property on AgentRun safely returns None when the iteration hasn't ended — it does not raise. This has been the case since pydantic-ai v1.0.0, which is the minimum version supported by Sentry for this integration (docs).

Here's the implementation from the v1.0.0 tag: pydantic_ai/run.py#L122-L138

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PydanticAI Agent.iter() not instrumented — missing tool execution and chat spans

1 participant