fix: document run_stream() event contract and warn on thinking + streaming - #2
Merged
Merged
Conversation
…aming run_stream() yields almost nothing to a bare consumer (only run_end): token and tool events are delivered to extensions implementing on_agent_runner_event_stream, and only their chunks reach the caller. The old docstring said "yields events one by one", misleading first-time users into expecting token chunks. - Document the precise run_stream() event contract in the docstring: runtime events go to streaming extensions; a bare consumer receives only run_end; chain-mode events are never forwarded. - Cross-reference the contract from Extension.on_agent_runner_event_stream. - AgentRunner warns once, on the first consumed run_stream() iteration, when thinking_enabled=True: pydantic-ai 2.22+ (verified on deepseek-v4-flash) drops post-tool-call text in thinking + streaming mode; run_end.output is not reliable. run() never warns — the intent to stream is only known when the caller actually consumes the stream (async generator body runs on first iteration). - README: record the pydantic-ai streaming defect under Known issues with the non-streaming run() workaround; fix the misleading run_stream() description; document the streaming-extension hook in Extensions. Tests: TestThinkingStreamingWarning covers warn-once dedup, zero warnings for run() and thinking_enabled=False, and message content; a chain-only-extension contract test pins that chain events never reach the consumer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
run_stream()event contract was undocumented and misleading, and the defaultthinking_enabled=Truesilently inherits a pydantic-ai streaming defect.Three verified problems (real-API reproduction on deepseek-v4-flash, pydantic-ai 2.22.0 and 2.25.0):
run_end.outputis not a reliable fallback. Non-streamingrun()is always complete.run_stream()forwards only chunks yielded by extensions implementingon_agent_runner_event_stream; chain-mode events never reach the consumer. A bare consumer receives onlyrun_end. The old docstring said "yields events one by one", which misled first-time users.thinking_enabled=True(default) inherits problem 1.Changes (5 files, +164/−14)
run_stream()docstring — precise event contract: runtime events delivered to streaming extensions; chain-mode events never forwarded; bare consumer receives onlyrun_end.Extension.on_agent_runner_event_streamdocstring — cross-references the contract; notes that without it no token/tool events reach callers.AgentRunner.run_stream()warns once (first iteration) whenthinking_enabled=True— the intent to stream is only known at call time, so the warning fires when the caller actually consumes the stream, not at construction.run()never warns.run()workaround; fixes the misleading Public API description; documents the streaming-extension hook.run()silence,thinking_enabled=Falsesilence) + 1 contract test (chain-only extension yields nothing to the consumer).Verification
make check: ruff ✅ mypy strict ✅ 146 passed, 7 skipped (7 = Postgres, run in CI)Known limits
run()orthinking_enabled=Falseare the workarounds.run_with_events()) is a separate proposal, not part of this PR.