Skip to content

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

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

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
Copy Markdown

@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
Copy Markdown
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 ✨

Anthropic

  • Record finish reasons in AI monitoring spans by ericapisani in #5678
  • Emit gen_ai.chat spans for asynchronous messages.stream() by alexander-alderman-webb in #5572
  • Emit AI Client Spans for synchronous messages.stream() by alexander-alderman-webb in #5565
  • Set gen_ai.response.id span attribute by ericapisani in #5662
  • Add gen_ai.system attribute to spans by ericapisani in #5661

Langchain

  • Set gen_ai.operation.name and gen_ai.pipeline.name on LLM spans by ericapisani in #5849
  • Broaden AI provider detection beyond OpenAI and Anthropic by ericapisani in #5707
  • Update LLM span operation to gen_ai.generate_text by ericapisani in #5796

Pydantic Ai

  • Add instrumentation for Agent.iter() by dfm88 in #5607
  • Support ImageUrl content type in span instrumentation by ericapisani in #5629
  • Add tool description to execute_tool spans by ericapisani in #5596

Other

  • (asgi) Add option to disable suppressing chained exceptions by alexander-alderman-webb in #5714
  • (crons) Add owner field to MonitorConfig by julwhitney13 in #5610
  • (logging) Separate ignore lists for events/breadcrumbs and sentry logs by sl0thentr0py in #5698
  • (otlp) Add collector_url option to OTLPIntegration by sl0thentr0py in #5603

Bug Fixes 🐛

Anthropic

  • Set exception info on streaming span when applicable by alexander-alderman-webb in #5683
  • Patch AsyncStream.close() and AsyncMessageStream.close() to finish spans by alexander-alderman-webb in #5675
  • Patch Stream.close() and MessageStream.close() to finish spans by alexander-alderman-webb in #5674
  • Close span on GeneratorExit by alexander-alderman-webb in #5643

Ci

  • Use gh CLI to convert PR to draft by stephanie-anderson in #5874
  • Use GitHub App token for draft PR enforcement by stephanie-anderson in #5871

Openai

  • Always set gen_ai.response.streaming for Responses by alexander-alderman-webb in #5697
  • Simplify Responses input handling by alexander-alderman-webb in #5695
  • Use max_output_tokens for Responses API by alexander-alderman-webb in #5693
  • Always set gen_ai.response.streaming for Completions by alexander-alderman-webb in #5692
  • Simplify Completions input handling by alexander-alderman-webb in #5690
  • Simplify embeddings input handling by alexander-alderman-webb in #5688

Other

  • (ai) Truncate list-based message content in AI monitoring by ericapisani in #5631
  • (celery) Propagate user-set headers by sentrivana in #5581
  • (google-genai) Guard response extraction by alexander-alderman-webb in #5869
  • (langchain) Wrap finish_reason in array for gen_ai span attribute by ericapisani in #5666
  • (logging) Fix deadlock in log batcher by sentrivana in #5684
  • (profiler) Prevent buffer race condition during rapid start/stop cycles by ericapisani in #5622
  • (starlette) Catch Jinja2Templates ImportError by alexander-alderman-webb in #5741
  • (utils) Avoid double serialization of strings in safe_serialize by ericapisani in #5587
  • (workflow) Fix permission issue with github app and PR draft graphql endpoint by Jeffreyhung in #5887
  • Enable unused import ruff check and fix unused imports by sentrivana in #5652

Documentation 📚

  • (openai-agents) Remove inapplicable comment by alexander-alderman-webb in #5495
  • Update CONTRIBUTING.md with contribution requirements and TOC by stephanie-anderson in #5896
  • Add note on AI PRs to CONTRIBUTING.md by sentrivana in #5696
  • Add AGENTS.md by sentrivana in #5579
  • Add set_attribute example to changelog by sentrivana in #5578

Internal Changes 🔧

Anthropic

  • Check system and response ID attributes on spans created by stream() by alexander-alderman-webb in #5665
  • Skip accumulation logic for unexpected types in streamed response by alexander-alderman-webb in #5564
  • Factor out streamed result handling by alexander-alderman-webb in #5563
  • Stream valid JSON by alexander-alderman-webb in #5641
  • Stop mocking response iterator by alexander-alderman-webb in #5573

Docs

  • Remove agentic codebase documentation workflows by dingsdax in #5655
  • Switch agentic workflows from Copilot to Claude engine by dingsdax in #5654
  • Add agentic workflows for codebase documentation by dingsdax in #5649

Langchain

  • Add text completion test by alexander-alderman-webb in #5740
  • Add tool execution test by alexander-alderman-webb in #5739
  • Add basic agent test with Responses call by alexander-alderman-webb in #5726
  • Replace mocks with httpx types by alexander-alderman-webb in #5724
  • Consolidate span origin assertion by alexander-alderman-webb in #5723
  • Consolidate available tools assertion by alexander-alderman-webb in #5721

Openai

  • Replace mocks with httpx types for streaming Responses by alexander-alderman-webb in #5882
  • Replace mocks with httpx types for streaming Completions by alexander-alderman-webb in #5879
  • Move input handling code into API-specific functions by alexander-alderman-webb in #5687

Openai Agents

  • Do not fail on new tool fields by alexander-alderman-webb in #5625
  • Stop expecting a specific function name by alexander-alderman-webb in #5623
  • Set streaming header when library uses with_streaming_response() by alexander-alderman-webb in #5583
  • Replace mocks with httpx for streamed responses by alexander-alderman-webb in #5580
  • Replace mocks with httpx in non-MCP tool tests by alexander-alderman-webb in #5602
  • Replace mocks with httpx in MCP tool tests by alexander-alderman-webb in #5605
  • Replace mocks with httpx in handoff tests by alexander-alderman-webb in #5604
  • Replace mocks with httpx in API error test by alexander-alderman-webb in #5601
  • Replace mocks with httpx in non-error single-response tests by alexander-alderman-webb in #5600
  • Remove test for unreachable state by alexander-alderman-webb in #5584
  • Expect namespace tool field for new openai versions by alexander-alderman-webb in #5599

Other

  • (ai) Rename generate_text to text_completion by ericapisani in #5885
  • (asyncpg) Normalize query whitespace in integration by ericapisani in #5855
  • (graphene) Simplify span creation by sentrivana in #5648
  • (httpx) Resolve type checking failures by alexander-alderman-webb in #5626
  • (pyramid) Support alpha suffixes in version parsing by alexander-alderman-webb in #5618
  • (rust) Don't implement separate scope management by sentrivana in #5639
  • (strawberry) Simplify span creation by sentrivana in #5647
  • Add workflow to close unvetted non-maintainer PRs by stephanie-anderson in #5895
  • Exclude compromised litellm versions by alexander-alderman-webb in #5876
  • Reactivate litellm tests by alexander-alderman-webb in #5853
  • Add note to coordinate with assignee before PR submission by sentrivana in #5868
  • Temporarily stop running litellm tests by alexander-alderman-webb in #5851
  • Pin GitHub Actions to full-length commit SHAs by joshuarli in #5781
  • Add -latest alias for each integration test suite by sentrivana in #5706
  • Use date-based branch names for toxgen PRs by sentrivana in #5704
  • 🤖 Update test matrix with new releases (03/19) by github-actions in #5703
  • Add client report tests for span streaming by sentrivana in #5677
  • 🤖 Update test matrix with new releases (03/16) by github-actions in #5671
  • Remove custom warden action by sentrivana in #5653
  • Add httpx to linting requirements by alexander-alderman-webb in #5644
  • Remove CodeQL action by sentrivana in #5616
  • Normalize dots in package names in populate_tox.py by alexander-alderman-webb in #5574
  • Do not run actions on potel-base by sentrivana in #5614

Other

  • ci+docs: Add draft PR enforcement by stephanie-anderson in #5867
  • release: 2.56.0 by alexander-alderman-webb in f5e93ad0
  • Update CHANGELOG.md by sentrivana in #5685
  • release: 2.55.0 by sentrivana in 038a4290

🤖 This preview updates automatically when you update the PR.

Copy link
Copy Markdown

@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
Copy Markdown

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
Copy Markdown
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

@stephanie-anderson stephanie-anderson added the violating-contribution-guidelines Used for automated community contribution checks. label Mar 27, 2026
@stephanie-anderson
Copy link
Copy Markdown
Contributor

This PR has been closed. The referenced issue does not show a discussion between you and a maintainer.

To avoid wasted effort on both sides, please discuss your proposed approach in the issue first and wait for a maintainer to respond before opening a PR.

Please review our contributing guidelines for more details.

@stephanie-anderson stephanie-anderson added the missing-maintainer-discussion Used for automated community contribution checks. label Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

missing-maintainer-discussion Used for automated community contribution checks. violating-contribution-guidelines Used for automated community contribution checks.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants