From 8b97aa5c2f0677a588c9643880edc88678a987ad Mon Sep 17 00:00:00 2001 From: Danipulok Date: Sun, 14 Dec 2025 02:40:11 +0200 Subject: [PATCH 1/9] Docs: update docs --- docs/output.md | 6 ++++++ tests/test_agent.py | 9 ++++++++- tests/test_streaming.py | 8 +++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/output.md b/docs/output.md index 3b20109b71..b641695f6e 100644 --- a/docs/output.md +++ b/docs/output.md @@ -315,6 +315,12 @@ When the model calls other tools in parallel with an output tool, you can contro The `'exhaustive'` strategy is useful when tools have important side effects (like logging, sending notifications, or updating metrics) that should always execute. +!!! warning "Streaming vs Sync Behavior Difference" + `run_stream()` behaves differently from `run()` and `run_sync()` when choosing the final result: + + - **`run_stream()`**: The first called tool that **can** produce a final result (output or deferred) becomes the final result + - **`run()` / `run_sync()`**: The first **output** tool becomes the final result. If none are called, all **deferred** tools become the final result as `DeferredToolRequests` + #### Native Output Native Output mode uses a model's native "Structured Outputs" feature (aka "JSON Schema response format"), where the model is forced to only output text matching the provided JSON schema. Note that this is not supported by all models, and sometimes comes with restrictions. For example, Gemini cannot use tools at the same time as structured output, and attempting to do so will result in an error. diff --git a/tests/test_agent.py b/tests/test_agent.py index 6ce2d91c54..d98bedf026 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -3291,7 +3291,14 @@ def deferred_tool(x: int) -> int: # pragma: no cover ) def test_early_strategy_with_external_tool_call(self): - """Test that early strategy handles external tool calls correctly.""" + """Test that early strategy handles external tool calls correctly. + + Streaming and sync modes differ in how they choose the final result: + - Streaming: First tool call (in response order) that can produce a final result (output or deferred) + - Sync: First output tool (if none called, all deferred tools become final result) + + See https://github.com/pydantic/pydantic-ai/issues/3636#issuecomment-3618800480 for details. + """ tool_called: list[str] = [] def return_model(_: list[ModelMessage], info: AgentInfo) -> ModelResponse: diff --git a/tests/test_streaming.py b/tests/test_streaming.py index 9149d19d1b..9b137b5f8e 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -1110,9 +1110,11 @@ def deferred_tool(x: int) -> int: # pragma: no cover async def test_early_strategy_with_external_tool_call(self): """Test that early strategy handles external tool calls correctly. - Streaming mode expects the first output tool call to be the final result, - and has different behavior from sync mode in this regard. - See https://github.com/pydantic/pydantic-ai/issues/3636 for details. + Streaming and sync modes differ in how they choose the final result: + - Streaming: First tool call (in response order) that can produce a final result (output or deferred) + - Sync: First output tool (if none called, all deferred tools become final result) + + See https://github.com/pydantic/pydantic-ai/issues/3636#issuecomment-3618800480 for details. """ tool_called: list[str] = [] From 9675e091269755b2095463149e3be167eb8a9267 Mon Sep 17 00:00:00 2001 From: Danipulok Date: Tue, 23 Dec 2025 02:41:49 +0200 Subject: [PATCH 2/9] Docs: update docs --- docs/agents.md | 11 ++++++----- docs/output.md | 10 ++++++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index 28a069a0be..c4d8958526 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -124,13 +124,14 @@ It also takes an optional `event_stream_handler` argument that you can use to ga The example below shows how to stream events and text output. You can also [stream structured output](output.md#streaming-structured-output). -!!! note - The `run_stream()` method will consider the first output that matches the [output type](output.md#structured-output) to be the final output of the agent run, even when the model generates tool calls after this "final" output. +!!! note "Streaming Methods Behavior Difference" + [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from the other run methods when choosing the final result: + + - **Streaming methods** ([`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream], [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync]): The first tool call in response order that **can** produce a final result becomes the final output. This includes both [output tools](output.md#tool-output) and [deferred tools](deferred-tools.md). Since these methods process the model's response as it streams, they eagerly select the first "final" tool encountered and immediately return, stopping the agent run. - These "dangling" tool calls will not be executed unless the agent's [`end_strategy`][pydantic_ai.agent.Agent.end_strategy] is set to `'exhaustive'`, and even then their results will not be sent back to the model as the agent run will already be considered completed. + - **Non-streaming methods** ([`run()`][pydantic_ai.agent.AbstractAgent.run], [`run_sync()`][pydantic_ai.agent.AbstractAgent.run_sync], [`iter()`][pydantic_ai.agent.AbstractAgent.iter], [`run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events]): These methods get to see the complete model response before processing. They prioritize output tools first. Only if no output tools are called, all deferred tools become the final result as [`DeferredToolRequests`][pydantic_ai.tools.DeferredToolRequests]. - If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, - use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. +If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. ```python {title="run_stream_event_stream_handler.py"} import asyncio diff --git a/docs/output.md b/docs/output.md index 9560a53a6e..1453d23fd3 100644 --- a/docs/output.md +++ b/docs/output.md @@ -315,11 +315,13 @@ When the model calls other tools in parallel with an output tool, you can contro The `'exhaustive'` strategy is useful when tools have important side effects (like logging, sending notifications, or updating metrics) that should always execute. -!!! warning "Streaming vs Sync Behavior Difference" - `run_stream()` behaves differently from `run()` and `run_sync()` when choosing the final result: +!!! warning "Streaming Methods Behavior Difference" + [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from the other run methods when choosing the final result: - - **`run_stream()`**: The first called tool that **can** produce a final result (output or deferred) becomes the final result - - **`run()` / `run_sync()`**: The first **output** tool becomes the final result. If none are called, all **deferred** tools become the final result as `DeferredToolRequests` + - **Streaming methods**: The first called tool that **can** produce a final result (output or [deferred](deferred-tools.md)) becomes the final result + - **Non-streaming methods**: The first **output** tool becomes the final result. If none are called, all **[deferred][pydantic_ai.tools.DeferredToolRequests]** tools become the final result + + For a detailed explanation of this behavior, see [Streaming Events and Final Output](agents.md#streaming-events-and-final-output). #### Native Output From 34ccc369398dd21a970205add154f4db2b0ddbd5 Mon Sep 17 00:00:00 2001 From: Danipulok Date: Tue, 23 Dec 2025 03:01:40 +0200 Subject: [PATCH 3/9] Docs: update docs --- docs/agents.md | 6 +++--- docs/output.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index c4d8958526..406a88e2d4 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -125,11 +125,11 @@ It also takes an optional `event_stream_handler` argument that you can use to ga The example below shows how to stream events and text output. You can also [stream structured output](output.md#streaming-structured-output). !!! note "Streaming Methods Behavior Difference" - [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from the other run methods when choosing the final result: + [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from all [other](agents.md#running-agents) run methods when choosing the final result: - - **Streaming methods** ([`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream], [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync]): The first tool call in response order that **can** produce a final result becomes the final output. This includes both [output tools](output.md#tool-output) and [deferred tools](deferred-tools.md). Since these methods process the model's response as it streams, they eagerly select the first "final" tool encountered and immediately return, stopping the agent run. + - **Streaming methods**: The first tool call that **can** produce a final result becomes the final output. This includes both [output tools](output.md#tool-output) and [deferred tools](deferred-tools.md). Since these methods process the model's response as it streams, they eagerly select the first "final" tool encountered and immediately return, stopping the agent run. - - **Non-streaming methods** ([`run()`][pydantic_ai.agent.AbstractAgent.run], [`run_sync()`][pydantic_ai.agent.AbstractAgent.run_sync], [`iter()`][pydantic_ai.agent.AbstractAgent.iter], [`run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events]): These methods get to see the complete model response before processing. They prioritize output tools first. Only if no output tools are called, all deferred tools become the final result as [`DeferredToolRequests`][pydantic_ai.tools.DeferredToolRequests]. + - **Non-streaming methods**: These methods get to see the complete model response before processing. They prioritize output tools first. Only if no output tools are called, all deferred tools become the final result as [`DeferredToolRequests`][pydantic_ai.tools.DeferredToolRequests]. If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. diff --git a/docs/output.md b/docs/output.md index 1453d23fd3..66977235ac 100644 --- a/docs/output.md +++ b/docs/output.md @@ -316,9 +316,9 @@ When the model calls other tools in parallel with an output tool, you can contro The `'exhaustive'` strategy is useful when tools have important side effects (like logging, sending notifications, or updating metrics) that should always execute. !!! warning "Streaming Methods Behavior Difference" - [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from the other run methods when choosing the final result: + [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from all [other](agents.md#running-agents) run methods when choosing the final result: - - **Streaming methods**: The first called tool that **can** produce a final result (output or [deferred](deferred-tools.md)) becomes the final result + - **Streaming methods**: The first tool call that **can** produce a final result (output or [deferred](deferred-tools.md)) becomes the final result - **Non-streaming methods**: The first **output** tool becomes the final result. If none are called, all **[deferred][pydantic_ai.tools.DeferredToolRequests]** tools become the final result For a detailed explanation of this behavior, see [Streaming Events and Final Output](agents.md#streaming-events-and-final-output). From 7ac0c4e73b33df9c77e68cd18716f1254eefe763 Mon Sep 17 00:00:00 2001 From: Danipulok Date: Tue, 23 Dec 2025 03:11:31 +0200 Subject: [PATCH 4/9] Docs: update docs --- docs/output.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/output.md b/docs/output.md index 66977235ac..802886a9ad 100644 --- a/docs/output.md +++ b/docs/output.md @@ -316,12 +316,9 @@ When the model calls other tools in parallel with an output tool, you can contro The `'exhaustive'` strategy is useful when tools have important side effects (like logging, sending notifications, or updating metrics) that should always execute. !!! warning "Streaming Methods Behavior Difference" - [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from all [other](agents.md#running-agents) run methods when choosing the final result: + [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] select the first tool call that can produce a final result ([output](#tool-output) or [deferred](deferred-tools.md)), while other run methods prioritize [output tools](#tool-output) first. - - **Streaming methods**: The first tool call that **can** produce a final result (output or [deferred](deferred-tools.md)) becomes the final result - - **Non-streaming methods**: The first **output** tool becomes the final result. If none are called, all **[deferred][pydantic_ai.tools.DeferredToolRequests]** tools become the final result - - For a detailed explanation of this behavior, see [Streaming Events and Final Output](agents.md#streaming-events-and-final-output). + See [Streaming Events and Final Output](agents.md#streaming-events-and-final-output) for a detailed explanation. #### Native Output From 583127deef297e72539dd23cb62bc7862256c90a Mon Sep 17 00:00:00 2001 From: Danipulok Date: Tue, 23 Dec 2025 03:19:51 +0200 Subject: [PATCH 5/9] Tmp --- docs/agents.md | 17 ++++++++++------- docs/output.md | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index 406a88e2d4..fc100752c4 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -124,13 +124,6 @@ It also takes an optional `event_stream_handler` argument that you can use to ga The example below shows how to stream events and text output. You can also [stream structured output](output.md#streaming-structured-output). -!!! note "Streaming Methods Behavior Difference" - [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from all [other](agents.md#running-agents) run methods when choosing the final result: - - - **Streaming methods**: The first tool call that **can** produce a final result becomes the final output. This includes both [output tools](output.md#tool-output) and [deferred tools](deferred-tools.md). Since these methods process the model's response as it streams, they eagerly select the first "final" tool encountered and immediately return, stopping the agent run. - - - **Non-streaming methods**: These methods get to see the complete model response before processing. They prioritize output tools first. Only if no output tools are called, all deferred tools become the final result as [`DeferredToolRequests`][pydantic_ai.tools.DeferredToolRequests]. - If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. ```python {title="run_stream_event_stream_handler.py"} @@ -227,6 +220,16 @@ if __name__ == '__main__': """ ``` +_(This example is complete, it can be run "as is")_ + +!!! note "Streaming Methods Behavior Difference" + [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from all [other](agents.md#running-agents) run methods when choosing the final result: + + - **Streaming methods**: The first tool call that **can** produce a final result becomes the final output. This includes both [output tools](output.md#tool-output) and [deferred tools](deferred-tools.md). Since these methods process the model's response as it streams, they eagerly select the first "final" tool and mark it as final result. + + - **Non-streaming methods**: These methods get to see the complete model response before processing. They prioritize output tools first. Only if no output tools are called, all deferred tools become the final result as [`DeferredToolRequests`][pydantic_ai.tools.DeferredToolRequests]. + + ### Streaming All Events Like `agent.run_stream()`, [`agent.run()`][pydantic_ai.agent.AbstractAgent.run_stream] takes an optional `event_stream_handler` diff --git a/docs/output.md b/docs/output.md index 802886a9ad..8e894ef285 100644 --- a/docs/output.md +++ b/docs/output.md @@ -316,7 +316,7 @@ When the model calls other tools in parallel with an output tool, you can contro The `'exhaustive'` strategy is useful when tools have important side effects (like logging, sending notifications, or updating metrics) that should always execute. !!! warning "Streaming Methods Behavior Difference" - [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] select the first tool call that can produce a final result ([output](#tool-output) or [deferred](deferred-tools.md)), while other run methods prioritize [output tools](#tool-output) first. + [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] select the first tool call that can produce a final result ([output](#tool-output) or [deferred](deferred-tools.md)), while all [other](agents.md#running-agents) run methods prioritize [output tools](#tool-output) first. See [Streaming Events and Final Output](agents.md#streaming-events-and-final-output) for a detailed explanation. From fcf5eef3b7e3e04def5b66063d9873219783a338 Mon Sep 17 00:00:00 2001 From: Danipulok Date: Tue, 23 Dec 2025 03:21:55 +0200 Subject: [PATCH 6/9] Checkout to `main` --- docs/agents.md | 18 +++++++----------- docs/output.md | 5 ----- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index ceba246116..640df1e24c 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -124,7 +124,13 @@ It also takes an optional `event_stream_handler` argument that you can use to ga The example below shows how to stream events and text output. You can also [stream structured output](output.md#streaming-structured-output). -If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. +!!! note + The `run_stream()` method will consider the first output that matches the [output type](output.md#structured-output) to be the final output of the agent run, even when the model generates tool calls after this "final" output. + + These "dangling" tool calls will not be executed unless the agent's [`end_strategy`][pydantic_ai.agent.Agent.end_strategy] is set to `'exhaustive'`, and even then their results will not be sent back to the model as the agent run will already be considered completed. + + If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, + use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. ```python {title="run_stream_event_stream_handler.py"} import asyncio @@ -220,16 +226,6 @@ if __name__ == '__main__': """ ``` -_(This example is complete, it can be run "as is")_ - -!!! note "Streaming Methods Behavior Difference" - [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] behave differently from all [other](agents.md#running-agents) run methods when choosing the final result: - - - **Streaming methods**: The first tool call that **can** produce a final result becomes the final output. This includes both [output tools](output.md#tool-output) and [deferred tools](deferred-tools.md). Since these methods process the model's response as it streams, they eagerly select the first "final" tool and mark it as final result. - - - **Non-streaming methods**: These methods get to see the complete model response before processing. They prioritize output tools first. Only if no output tools are called, all deferred tools become the final result as [`DeferredToolRequests`][pydantic_ai.tools.DeferredToolRequests]. - - ### Streaming All Events Like `agent.run_stream()`, [`agent.run()`][pydantic_ai.agent.AbstractAgent.run_stream] takes an optional `event_stream_handler` diff --git a/docs/output.md b/docs/output.md index db3a3a96ef..dbbb84c6e9 100644 --- a/docs/output.md +++ b/docs/output.md @@ -319,11 +319,6 @@ When the model calls other tools in parallel with an output tool, you can contro The `'exhaustive'` strategy is useful when tools have important side effects (like logging, sending notifications, or updating metrics) that should always execute. -!!! warning "Streaming Methods Behavior Difference" - [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] select the first tool call that can produce a final result ([output](#tool-output) or [deferred](deferred-tools.md)), while all [other](agents.md#running-agents) run methods prioritize [output tools](#tool-output) first. - - See [Streaming Events and Final Output](agents.md#streaming-events-and-final-output) for a detailed explanation. - #### Native Output Native Output mode uses a model's native "Structured Outputs" feature (aka "JSON Schema response format"), where the model is forced to only output text matching the provided JSON schema. Note that this is not supported by all models, and sometimes comes with restrictions. For example, Gemini cannot use tools at the same time as structured output, and attempting to do so will result in an error. From 7c87002a56cf64442a28d6003ab66abc6705def6 Mon Sep 17 00:00:00 2001 From: Danipulok Date: Tue, 23 Dec 2025 03:50:21 +0200 Subject: [PATCH 7/9] Docs: stable docs --- docs/agents.md | 6 ++++-- docs/output.md | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index 640df1e24c..2f89a54cb1 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -125,9 +125,9 @@ It also takes an optional `event_stream_handler` argument that you can use to ga The example below shows how to stream events and text output. You can also [stream structured output](output.md#streaming-structured-output). !!! note - The `run_stream()` method will consider the first output that matches the [output type](output.md#structured-output) to be the final output of the agent run, even when the model generates tool calls after this "final" output. + The [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] methods will consider the first tool call that can produce a final result (both [output](output.md#tool-output) and [deferred](deferred-tools.md) tools) to be the final output of the agent run. This is different behavior from all [other](#running-agents) agent run methods, which prioritize **output** tools over deferred tools. - These "dangling" tool calls will not be executed unless the agent's [`end_strategy`][pydantic_ai.agent.Agent.end_strategy] is set to `'exhaustive'`, and even then their results will not be sent back to the model as the agent run will already be considered completed. + "Dangling" tool calls generated after the "final output" will not be executed unless the agent's [`end_strategy`][pydantic_ai.agent.Agent.end_strategy] is set to `'exhaustive'`, and even then their results will not be sent back to the model as the agent run will already be considered completed. If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. @@ -226,6 +226,8 @@ if __name__ == '__main__': """ ``` +_(This example is complete, it can be run "as is")_ + ### Streaming All Events Like `agent.run_stream()`, [`agent.run()`][pydantic_ai.agent.AbstractAgent.run_stream] takes an optional `event_stream_handler` diff --git a/docs/output.md b/docs/output.md index dbbb84c6e9..bde9439d38 100644 --- a/docs/output.md +++ b/docs/output.md @@ -319,6 +319,12 @@ When the model calls other tools in parallel with an output tool, you can contro The `'exhaustive'` strategy is useful when tools have important side effects (like logging, sending notifications, or updating metrics) that should always execute. +!!! warning "Difference in choosing Final Result in Streaming Methods" + [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] methods select the first tool call that can produce a final result (both [output](#tool-output) and [deferred](deferred-tools.md)) as final output, while all [other](agents.md#running-agents) run methods prioritize [output tools](#tool-output) first. + + See [Streaming Events and Final Output](agents.md#streaming-events-and-final-output) for a detailed explanation. + + #### Native Output Native Output mode uses a model's native "Structured Outputs" feature (aka "JSON Schema response format"), where the model is forced to only output text matching the provided JSON schema. Note that this is not supported by all models, and sometimes comes with restrictions. For example, Gemini cannot use tools at the same time as structured output, and attempting to do so will result in an error. From cd406cd76ff0a408fa0005c997ed308cd621e9e6 Mon Sep 17 00:00:00 2001 From: Danipulok Date: Tue, 23 Dec 2025 03:57:13 +0200 Subject: [PATCH 8/9] Docs: update docs --- docs/agents.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index 2f89a54cb1..0c91a0dd24 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -124,13 +124,13 @@ It also takes an optional `event_stream_handler` argument that you can use to ga The example below shows how to stream events and text output. You can also [stream structured output](output.md#streaming-structured-output). -!!! note - The [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] methods will consider the first tool call that can produce a final result (both [output](output.md#tool-output) and [deferred](deferred-tools.md) tools) to be the final output of the agent run. This is different behavior from all [other](#running-agents) agent run methods, which prioritize **output** tools over deferred tools. +!!! note "Streaming Methods Choose Final Result Differently" + [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] will consider the first tool call that can produce a final result (both [output](output.md#tool-output) and [deferred](deferred-tools.md) tools) to be the final output. This is different from all [other](#running-agents) run methods, which prioritize **output** tools over deferred tools. - "Dangling" tool calls generated after the "final output" will not be executed unless the agent's [`end_strategy`][pydantic_ai.agent.Agent.end_strategy] is set to `'exhaustive'`, and even then their results will not be sent back to the model as the agent run will already be considered completed. +!!! note + "Dangling" tool calls generated after the "final output" will not be executed unless the agent's [`end_strategy`][pydantic_ai.agent.Agent.end_strategy] is set to `'exhaustive'`, and even then their results will not be sent back to the model as the agent run will already be considered completed. - If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, - use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. + If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. ```python {title="run_stream_event_stream_handler.py"} import asyncio From 9b0b184ca7ee7aaae4e7ad7085a7debafc057cd9 Mon Sep 17 00:00:00 2001 From: Danipulok Date: Tue, 23 Dec 2025 03:58:46 +0200 Subject: [PATCH 9/9] Docs: update docs --- docs/agents.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/agents.md b/docs/agents.md index 0c91a0dd24..9833ab0c01 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -127,9 +127,9 @@ The example below shows how to stream events and text output. You can also [stre !!! note "Streaming Methods Choose Final Result Differently" [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] and [`run_stream_sync()`][pydantic_ai.agent.AbstractAgent.run_stream_sync] will consider the first tool call that can produce a final result (both [output](output.md#tool-output) and [deferred](deferred-tools.md) tools) to be the final output. This is different from all [other](#running-agents) run methods, which prioritize **output** tools over deferred tools. -!!! note "Dangling" tool calls generated after the "final output" will not be executed unless the agent's [`end_strategy`][pydantic_ai.agent.Agent.end_strategy] is set to `'exhaustive'`, and even then their results will not be sent back to the model as the agent run will already be considered completed. +!!! note If you want to always keep running the agent when it performs tool calls, and stream all events from the model's streaming response and the agent's execution of tools, use [`agent.run_stream_events()`][pydantic_ai.agent.AbstractAgent.run_stream_events] or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead, as described in the following sections. ```python {title="run_stream_event_stream_handler.py"}