Skip to content

Commit 831aead

Browse files
committed
Merge branch 'main' into fallback-output-mode
2 parents 3363ba9 + 6a153c8 commit 831aead

File tree

15 files changed

+277
-13
lines changed

15 files changed

+277
-13
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.

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
@@ -237,7 +237,7 @@ def __init__(
237237
output_type: The type of the output data, used to validate the data returned by the model,
238238
defaults to `str`.
239239
instructions: Instructions to use for this agent, you can also register instructions via a function with
240-
[`instructions`][pydantic_ai.Agent.instructions].
240+
[`instructions`][pydantic_ai.Agent.instructions] or pass additional, temporary, instructions when executing a run.
241241
system_prompt: Static system prompts to use for this agent, you can also register system
242242
prompts via a function with [`system_prompt`][pydantic_ai.Agent.system_prompt].
243243
deps_type: The type used for dependency injection, this parameter exists solely to allow you to fully
@@ -413,6 +413,7 @@ def iter(
413413
message_history: Sequence[_messages.ModelMessage] | None = None,
414414
deferred_tool_results: DeferredToolResults | None = None,
415415
model: models.Model | models.KnownModelName | str | None = None,
416+
instructions: Instructions[AgentDepsT] = None,
416417
deps: AgentDepsT = None,
417418
model_settings: ModelSettings | None = None,
418419
usage_limits: _usage.UsageLimits | None = None,
@@ -431,6 +432,7 @@ def iter(
431432
message_history: Sequence[_messages.ModelMessage] | None = None,
432433
deferred_tool_results: DeferredToolResults | None = None,
433434
model: models.Model | models.KnownModelName | str | None = None,
435+
instructions: Instructions[AgentDepsT] = None,
434436
deps: AgentDepsT = None,
435437
model_settings: ModelSettings | None = None,
436438
usage_limits: _usage.UsageLimits | None = None,
@@ -449,6 +451,7 @@ async def iter(
449451
message_history: Sequence[_messages.ModelMessage] | None = None,
450452
deferred_tool_results: DeferredToolResults | None = None,
451453
model: models.Model | models.KnownModelName | str | None = None,
454+
instructions: Instructions[AgentDepsT] = None,
452455
deps: AgentDepsT = None,
453456
model_settings: ModelSettings | None = None,
454457
usage_limits: _usage.UsageLimits | None = None,
@@ -522,6 +525,7 @@ async def main():
522525
message_history: History of the conversation so far.
523526
deferred_tool_results: Optional results for deferred tool calls in the message history.
524527
model: Optional model to use for this run, required if `model` was not set when creating the agent.
528+
instructions: Optional additional instructions to use for this run.
525529
deps: Optional dependencies to use for this run.
526530
model_settings: Optional settings to use for this model's request.
527531
usage_limits: Optional limits on model request count or token usage.
@@ -575,7 +579,7 @@ async def main():
575579
model_settings = merge_model_settings(merged_settings, model_settings)
576580
usage_limits = usage_limits or _usage.UsageLimits()
577581

578-
instructions_literal, instructions_functions = self._get_instructions()
582+
instructions_literal, instructions_functions = self._get_instructions(additional_instructions=instructions)
579583

580584
async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
581585
parts = [
@@ -1320,9 +1324,15 @@ def _normalize_instructions(
13201324

13211325
def _get_instructions(
13221326
self,
1327+
additional_instructions: Instructions[AgentDepsT] = None,
13231328
) -> tuple[str | None, list[_system_prompt.SystemPromptRunner[AgentDepsT]]]:
13241329
override_instructions = self._override_instructions.get()
1325-
instructions = override_instructions.value if override_instructions else self._instructions
1330+
if override_instructions:
1331+
instructions = override_instructions.value
1332+
else:
1333+
instructions = self._instructions.copy()
1334+
if additional_instructions is not None:
1335+
instructions.extend(self._normalize_instructions(additional_instructions))
13261336

13271337
literal_parts: list[str] = []
13281338
functions: list[_system_prompt.SystemPromptRunner[AgentDepsT]] = []

0 commit comments

Comments
 (0)