Skip to content

Commit 3a5735f

Browse files
authored
Merge branch 'main' into johan/fix_annotations_duckduckgo
2 parents b573683 + 6a153c8 commit 3a5735f

File tree

24 files changed

+523
-56
lines changed

24 files changed

+523
-56
lines changed

docs/agents.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -904,14 +904,15 @@ You should use:
904904

905905
In general, we recommend using `instructions` instead of `system_prompt` unless you have a specific reason to use `system_prompt`.
906906

907-
Instructions, like system prompts, fall into two categories:
907+
Instructions, like system prompts, can be specified at different times:
908908

909909
1. **Static instructions**: These are known when writing the code and can be defined via the `instructions` parameter of the [`Agent` constructor][pydantic_ai.Agent.__init__].
910910
2. **Dynamic instructions**: These rely on context that is only available at runtime and should be defined using functions decorated with [`@agent.instructions`][pydantic_ai.Agent.instructions]. Unlike dynamic system prompts, which may be reused when `message_history` is present, dynamic instructions are always reevaluated.
911+
3. **Runtime instructions*: These are additional instructions for a specific run that can be passed to one of the [run methods](#running-agents) using the `instructions` argument.
911912

912-
Both static and dynamic instructions can be added to a single agent, and they are appended in the order they are defined at runtime.
913+
All three types of instructions can be added to a single agent, and they are appended in the order they are defined at runtime.
913914

914-
Here's an example using both types of instructions:
915+
Here's an example using a static instruction as well as dynamic instructions:
915916

916917
```python {title="instructions.py"}
917918
from datetime import date

docs/durable_execution/temporal.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ As workflows and activities run in separate processes, any values passed between
172172

173173
To account for these limitations, tool functions and the [event stream handler](#streaming) running inside activities receive a limited version of the agent's [`RunContext`][pydantic_ai.tools.RunContext], and it's your responsibility to make sure that the [dependencies](../dependencies.md) object provided to [`TemporalAgent.run()`][pydantic_ai.durable_exec.temporal.TemporalAgent.run] can be serialized using Pydantic.
174174

175-
Specifically, only the `deps`, `retries`, `tool_call_id`, `tool_name`, `tool_call_approved`, `retry`, `max_retries` and `run_step` fields are available by default, and trying to access `model`, `usage`, `prompt`, `messages`, or `tracer` will raise an error.
175+
Specifically, only the `deps`, `retries`, `tool_call_id`, `tool_name`, `tool_call_approved`, `retry`, `max_retries`, `run_step` and `partial_output` fields are available by default, and trying to access `model`, `usage`, `prompt`, `messages`, or `tracer` will raise an error.
176176
If you need one or more of these attributes to be available inside activities, you can create a [`TemporalRunContext`][pydantic_ai.durable_exec.temporal.TemporalRunContext] subclass with custom `serialize_run_context` and `deserialize_run_context` class methods and pass it to [`TemporalAgent`][pydantic_ai.durable_exec.temporal.TemporalAgent] as `run_context_type`.
177177

178178
### Streaming

docs/output.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,40 @@ print(result.output)
470470

471471
_(This example is complete, it can be run "as is")_
472472

473+
#### Handling partial output in output validators {#partial-output}
474+
475+
You can use the `partial_output` field on `RunContext` to handle validation differently for partial outputs during streaming (e.g. skip validation altogether).
476+
477+
```python {title="partial_validation_streaming.py" line_length="120"}
478+
from pydantic_ai import Agent, ModelRetry, RunContext
479+
480+
agent = Agent('openai:gpt-5')
481+
482+
@agent.output_validator
483+
def validate_output(ctx: RunContext, output: str) -> str:
484+
if ctx.partial_output:
485+
return output
486+
else:
487+
if len(output) < 50:
488+
raise ModelRetry('Output is too short.')
489+
return output
490+
491+
492+
async def main():
493+
async with agent.run_stream('Write a long story about a cat') as result:
494+
async for message in result.stream_text():
495+
print(message)
496+
#> Once upon a
497+
#> Once upon a time, there was
498+
#> Once upon a time, there was a curious cat
499+
#> Once upon a time, there was a curious cat named Whiskers who
500+
#> Once upon a time, there was a curious cat named Whiskers who loved to explore
501+
#> Once upon a time, there was a curious cat named Whiskers who loved to explore the world around
502+
#> Once upon a time, there was a curious cat named Whiskers who loved to explore the world around him...
503+
```
504+
505+
_(This example is complete, it can be run "as is" — you'll need to add `asyncio.run(main())` to run `main`)_
506+
473507
## Image output
474508

475509
Some models can generate images as part of their response, for example those that support the [Image Generation built-in tool](builtin-tools.md#image-generation-tool) and OpenAI models using the [Code Execution built-in tool](builtin-tools.md#code-execution-tool) when told to generate a chart.

docs/thinking.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,64 @@ agent = Agent(model, model_settings=settings)
6666

6767
## Bedrock
6868

69+
Although Bedrock Converse doesn't provide a unified API to enable thinking, you can still use [`BedrockModelSettings.bedrock_additional_model_requests_fields`][pydantic_ai.models.bedrock.BedrockModelSettings.bedrock_additional_model_requests_fields] [model setting](agents.md#model-run-settings) to pass provider-specific configuration:
70+
71+
=== "Claude"
72+
73+
```python {title="bedrock_claude_thinking_part.py"}
74+
from pydantic_ai import Agent
75+
from pydantic_ai.models.bedrock import BedrockConverseModel, BedrockModelSettings
76+
77+
model = BedrockConverseModel('us.anthropic.claude-sonnet-4-5-20250929-v1:0')
78+
model_settings = BedrockModelSettings(
79+
bedrock_additional_model_requests_fields={
80+
'thinking': {'type': 'enabled', 'budget_tokens': 1024}
81+
}
82+
)
83+
agent = Agent(model=model, model_settings=model_settings)
84+
85+
```
86+
=== "OpenAI"
87+
88+
89+
```python {title="bedrock_openai_thinking_part.py"}
90+
from pydantic_ai import Agent
91+
from pydantic_ai.models.bedrock import BedrockConverseModel, BedrockModelSettings
92+
93+
model = BedrockConverseModel('openai.gpt-oss-120b-1:0')
94+
model_settings = BedrockModelSettings(
95+
bedrock_additional_model_requests_fields={'reasoning_effort': 'low'}
96+
)
97+
agent = Agent(model=model, model_settings=model_settings)
98+
99+
```
100+
=== "Qwen"
101+
102+
103+
```python {title="bedrock_qwen_thinking_part.py"}
104+
from pydantic_ai import Agent
105+
from pydantic_ai.models.bedrock import BedrockConverseModel, BedrockModelSettings
106+
107+
model = BedrockConverseModel('qwen.qwen3-32b-v1:0')
108+
model_settings = BedrockModelSettings(
109+
bedrock_additional_model_requests_fields={'reasoning_config': 'high'}
110+
)
111+
agent = Agent(model=model, model_settings=model_settings)
112+
113+
```
114+
115+
=== "Deepseek"
116+
Reasoning is [always enabled](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-reasoning.html) for Deepseek model
117+
118+
```python {title="bedrock_deepseek_thinking_part.py"}
119+
from pydantic_ai import Agent
120+
from pydantic_ai.models.bedrock import BedrockConverseModel
121+
122+
model = BedrockConverseModel('us.deepseek.r1-v1:0')
123+
agent = Agent(model=model)
124+
125+
```
126+
69127
## Groq
70128

71129
Groq supports different formats to receive thinking parts:

pydantic_ai_slim/pydantic_ai/_run_context.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class RunContext(Generic[RunContextAgentDepsT]):
5858
"""The current step in the run."""
5959
tool_call_approved: bool = False
6060
"""Whether a tool call that required approval has now been approved."""
61+
partial_output: bool = False
62+
"""Whether the output passed to an output validator is partial."""
6163

6264
@property
6365
def last_attempt(self) -> bool:

pydantic_ai_slim/pydantic_ai/_tool_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ async def _call_tool(
147147
tool_call_id=call.tool_call_id,
148148
retry=self.ctx.retries.get(name, 0),
149149
max_retries=tool.max_retries,
150+
partial_output=allow_partial,
150151
)
151152

152153
pyd_allow_partial = 'trailing-strings' if allow_partial else 'off'

pydantic_ai_slim/pydantic_ai/agent/__init__.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def __init__(
238238
output_type: The type of the output data, used to validate the data returned by the model,
239239
defaults to `str`.
240240
instructions: Instructions to use for this agent, you can also register instructions via a function with
241-
[`instructions`][pydantic_ai.Agent.instructions].
241+
[`instructions`][pydantic_ai.Agent.instructions] or pass additional, temporary, instructions when executing a run.
242242
system_prompt: Static system prompts to use for this agent, you can also register system
243243
prompts via a function with [`system_prompt`][pydantic_ai.Agent.system_prompt].
244244
deps_type: The type used for dependency injection, this parameter exists solely to allow you to fully
@@ -418,6 +418,7 @@ def iter(
418418
message_history: Sequence[_messages.ModelMessage] | None = None,
419419
deferred_tool_results: DeferredToolResults | None = None,
420420
model: models.Model | models.KnownModelName | str | None = None,
421+
instructions: Instructions[AgentDepsT] = None,
421422
deps: AgentDepsT = None,
422423
model_settings: ModelSettings | None = None,
423424
usage_limits: _usage.UsageLimits | None = None,
@@ -436,6 +437,7 @@ def iter(
436437
message_history: Sequence[_messages.ModelMessage] | None = None,
437438
deferred_tool_results: DeferredToolResults | None = None,
438439
model: models.Model | models.KnownModelName | str | None = None,
440+
instructions: Instructions[AgentDepsT] = None,
439441
deps: AgentDepsT = None,
440442
model_settings: ModelSettings | None = None,
441443
usage_limits: _usage.UsageLimits | None = None,
@@ -454,6 +456,7 @@ async def iter(
454456
message_history: Sequence[_messages.ModelMessage] | None = None,
455457
deferred_tool_results: DeferredToolResults | None = None,
456458
model: models.Model | models.KnownModelName | str | None = None,
459+
instructions: Instructions[AgentDepsT] = None,
457460
deps: AgentDepsT = None,
458461
model_settings: ModelSettings | None = None,
459462
usage_limits: _usage.UsageLimits | None = None,
@@ -527,6 +530,7 @@ async def main():
527530
message_history: History of the conversation so far.
528531
deferred_tool_results: Optional results for deferred tool calls in the message history.
529532
model: Optional model to use for this run, required if `model` was not set when creating the agent.
533+
instructions: Optional additional instructions to use for this run.
530534
deps: Optional dependencies to use for this run.
531535
model_settings: Optional settings to use for this model's request.
532536
usage_limits: Optional limits on model request count or token usage.
@@ -580,7 +584,7 @@ async def main():
580584
model_settings = merge_model_settings(merged_settings, model_settings)
581585
usage_limits = usage_limits or _usage.UsageLimits()
582586

583-
instructions_literal, instructions_functions = self._get_instructions()
587+
instructions_literal, instructions_functions = self._get_instructions(additional_instructions=instructions)
584588

585589
async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
586590
parts = [
@@ -1330,9 +1334,15 @@ def _normalize_instructions(
13301334

13311335
def _get_instructions(
13321336
self,
1337+
additional_instructions: Instructions[AgentDepsT] = None,
13331338
) -> tuple[str | None, list[_system_prompt.SystemPromptRunner[AgentDepsT]]]:
13341339
override_instructions = self._override_instructions.get()
1335-
instructions = override_instructions.value if override_instructions else self._instructions
1340+
if override_instructions:
1341+
instructions = override_instructions.value
1342+
else:
1343+
instructions = self._instructions.copy()
1344+
if additional_instructions is not None:
1345+
instructions.extend(self._normalize_instructions(additional_instructions))
13361346

13371347
literal_parts: list[str] = []
13381348
functions: list[_system_prompt.SystemPromptRunner[AgentDepsT]] = []

0 commit comments

Comments
 (0)