Skip to content

Commit 8c32055

Browse files
Merge branch 'main' into update-versins
2 parents 6766617 + 484022f commit 8c32055

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1896
-1600
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ jobs:
204204
enable-cache: true
205205

206206
- run: uv sync --package pydantic-ai-slim --only-dev
207+
- run: rm coverage/.coverage.*-py3.9-* # Exclude 3.9 coverage as it gets the wrong line numbers, causing invalid failures.
207208
- run: uv run coverage combine coverage
208209

209210
- run: uv run coverage html --show-contexts --title "PydanticAI coverage for ${{ github.sha }}"

docs/a2a.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The library is designed to be used with any agentic framework, and is **not excl
3232
Given the nature of the A2A protocol, it's important to understand the design before using it, as a developer
3333
you'll need to provide some components:
3434

35-
- [`Storage`][fasta2a.Storage]: to save and load tasks
35+
- [`Storage`][fasta2a.Storage]: to save and load tasks, as well as store context for conversations
3636
- [`Broker`][fasta2a.Broker]: to schedule tasks
3737
- [`Worker`][fasta2a.Worker]: to execute tasks
3838

@@ -55,6 +55,28 @@ flowchart TB
5555

5656
FastA2A allows you to bring your own [`Storage`][fasta2a.Storage], [`Broker`][fasta2a.Broker] and [`Worker`][fasta2a.Worker].
5757

58+
#### Understanding Tasks and Context
59+
60+
In the A2A protocol:
61+
62+
- **Task**: Represents one complete execution of an agent. When a client sends a message to the agent, a new task is created. The agent runs until completion (or failure), and this entire execution is considered one task. The final output is stored as a task artifact.
63+
64+
- **Context**: Represents a conversation thread that can span multiple tasks. The A2A protocol uses a `context_id` to maintain conversation continuity:
65+
- When a new message is sent without a `context_id`, the server generates a new one
66+
- Subsequent messages can include the same `context_id` to continue the conversation
67+
- All tasks sharing the same `context_id` have access to the complete message history
68+
69+
#### Storage Architecture
70+
71+
The [`Storage`][fasta2a.Storage] component serves two purposes:
72+
73+
1. **Task Storage**: Stores tasks in A2A protocol format, including their status, artifacts, and message history
74+
2. **Context Storage**: Stores conversation context in a format optimized for the specific agent implementation
75+
76+
This design allows for agents to store rich internal state (e.g., tool calls, reasoning traces) as well as store task-specific A2A-formatted messages and artifacts.
77+
78+
For example, a PydanticAI agent might store its complete internal message format (including tool calls and responses) in the context storage, while storing only the A2A-compliant messages in the task history.
79+
5880

5981
### Installation
6082

@@ -94,3 +116,12 @@ uvicorn agent_to_a2a:app --host 0.0.0.0 --port 8000
94116
```
95117

96118
Since the goal of `to_a2a` is to be a convenience method, it accepts the same arguments as the [`FastA2A`][fasta2a.FastA2A] constructor.
119+
120+
When using `to_a2a()`, PydanticAI automatically:
121+
122+
- Stores the complete conversation history (including tool calls and responses) in the context storage
123+
- Ensures that subsequent messages with the same `context_id` have access to the full conversation history
124+
- Persists agent results as A2A artifacts:
125+
- String results become `TextPart` artifacts and also appear in the message history
126+
- Structured data (Pydantic models, dataclasses, tuples, etc.) become `DataPart` artifacts with the data wrapped as `{"result": <your_data>}`
127+
- Artifacts include metadata with type information and JSON schema when available

docs/agents.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -466,26 +466,43 @@ PydanticAI offers a [`settings.ModelSettings`][pydantic_ai.settings.ModelSetting
466466
This structure allows you to configure common parameters that influence the model's behavior, such as `temperature`, `max_tokens`,
467467
`timeout`, and more.
468468

469-
There are two ways to apply these settings:
469+
There are three ways to apply these settings, with a clear precedence order:
470470

471-
1. Passing to `run{_sync,_stream}` functions via the `model_settings` argument. This allows for fine-tuning on a per-request basis.
472-
2. Setting during [`Agent`][pydantic_ai.agent.Agent] initialization via the `model_settings` argument. These settings will be applied by default to all subsequent run calls using said agent. However, `model_settings` provided during a specific run call will override the agent's default settings.
471+
1. **Model-level defaults** - Set when creating a model instance via the `settings` parameter. These serve as the base defaults for that model.
472+
2. **Agent-level defaults** - Set during [`Agent`][pydantic_ai.agent.Agent] initialization via the `model_settings` argument. These are merged with model defaults, with agent settings taking precedence.
473+
3. **Run-time overrides** - Passed to `run{_sync,_stream}` functions via the `model_settings` argument. These have the highest priority and are merged with the combined agent and model defaults.
473474

474475
For example, if you'd like to set the `temperature` setting to `0.0` to ensure less random behavior,
475476
you can do the following:
476477

477478
```py
478479
from pydantic_ai import Agent
480+
from pydantic_ai.models.openai import OpenAIModel
481+
from pydantic_ai.settings import ModelSettings
479482

480-
agent = Agent('openai:gpt-4o')
483+
# 1. Model-level defaults
484+
model = OpenAIModel(
485+
'gpt-4o',
486+
settings=ModelSettings(temperature=0.8, max_tokens=500) # Base defaults
487+
)
488+
489+
# 2. Agent-level defaults (overrides model defaults by merging)
490+
agent = Agent(model, model_settings=ModelSettings(temperature=0.5))
481491

492+
# 3. Run-time overrides (highest priority)
482493
result_sync = agent.run_sync(
483-
'What is the capital of Italy?', model_settings={'temperature': 0.0}
494+
'What is the capital of Italy?',
495+
model_settings=ModelSettings(temperature=0.0) # Final temperature: 0.0
484496
)
485497
print(result_sync.output)
486498
#> Rome
487499
```
488500

501+
The final request uses `temperature=0.0` (run-time), `max_tokens=500` (from model), demonstrating how settings merge with run-time taking precedence.
502+
503+
!!! note "Model Settings Support"
504+
Model-level settings are supported by all concrete model implementations (OpenAI, Anthropic, Google, etc.). Wrapper models like `FallbackModel`, `WrapperModel`, and `InstrumentedModel` don't have their own settings - they use the settings of their underlying models.
505+
489506
### Model specific settings
490507

491508
If you wish to further customize model behavior, you can use a subclass of [`ModelSettings`][pydantic_ai.settings.ModelSettings], like [`GeminiModelSettings`][pydantic_ai.models.gemini.GeminiModelSettings], associated with your model of choice.

docs/api/output.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
- NativeOutput
1010
- PromptedOutput
1111
- TextOutput
12+
- StructuredDict

docs/examples/chat-app.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Python code that runs the chat app:
3333

3434
Simple HTML page to render the app:
3535

36-
```snippet {path="/examples/pydantic_ai_examples/chat_app.py"}```
36+
```snippet {path="/examples/pydantic_ai_examples/chat_app.html"}```
3737

3838
TypeScript to handle rendering the messages, to keep this simple (and at the risk of offending frontend developers) the typescript code is passed to the browser as plain text and transpiled in the browser.
3939

docs/graph.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ stateDiagram-v2
352352
Feedback --> [*]
353353
```
354354

355-
```python {title="genai_email_feedback.py" py="3.10"}
355+
```python {title="genai_email_feedback.py" py="3.10" test="ci_only"}
356356
from __future__ import annotations as _annotations
357357

358358
from dataclasses import dataclass, field

docs/models/google.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ To use Vertex AI, you may need to set up [application default credentials](https
5858

5959
If you have the [`gcloud` CLI](https://cloud.google.com/sdk/gcloud) installed and configured, you can use:
6060

61-
```python
61+
```python {test="ci_only"}
6262
from pydantic_ai import Agent
6363
from pydantic_ai.models.google import GoogleModel
6464
from pydantic_ai.providers.google import GoogleProvider

docs/models/index.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,39 @@ The `ModelResponse` message above indicates in the `model_name` field that the o
124124
!!! note
125125
Each model's options should be configured individually. For example, `base_url`, `api_key`, and custom clients should be set on each model itself, not on the `FallbackModel`.
126126

127+
### Per-Model Settings
128+
129+
You can configure different [`ModelSettings`][pydantic_ai.settings.ModelSettings] for each model in a fallback chain by passing the `settings` parameter when creating each model. This is particularly useful when different providers have different optimal configurations:
130+
131+
```python {title="fallback_model_per_settings.py"}
132+
from pydantic_ai import Agent
133+
from pydantic_ai.models.anthropic import AnthropicModel
134+
from pydantic_ai.models.fallback import FallbackModel
135+
from pydantic_ai.models.openai import OpenAIModel
136+
from pydantic_ai.settings import ModelSettings
137+
138+
# Configure each model with provider-specific optimal settings
139+
openai_model = OpenAIModel(
140+
'gpt-4o',
141+
settings=ModelSettings(temperature=0.7, max_tokens=1000) # Higher creativity for OpenAI
142+
)
143+
anthropic_model = AnthropicModel(
144+
'claude-3-5-sonnet-latest',
145+
settings=ModelSettings(temperature=0.2, max_tokens=1000) # Lower temperature for consistency
146+
)
147+
148+
fallback_model = FallbackModel(openai_model, anthropic_model)
149+
agent = Agent(fallback_model)
150+
151+
result = agent.run_sync('Write a creative story about space exploration')
152+
print(result.output)
153+
"""
154+
In the year 2157, Captain Maya Chen piloted her spacecraft through the vast expanse of the Andromeda Galaxy. As she discovered a planet with crystalline mountains that sang in harmony with the cosmic winds, she realized that space exploration was not just about finding new worlds, but about finding new ways to understand the universe and our place within it.
155+
"""
156+
```
157+
158+
In this example, if the OpenAI model fails, the agent will automatically fall back to the Anthropic model with its own configured settings. The `FallbackModel` itself doesn't have settings - it uses the individual settings of whichever model successfully handles the request.
159+
127160
In this next example, we demonstrate the exception-handling capabilities of `FallbackModel`.
128161
If all models fail, a [`FallbackExceptionGroup`][pydantic_ai.exceptions.FallbackExceptionGroup] is raised, which
129162
contains all the exceptions encountered during the `run` execution.

docs/output.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ _(This example is complete, it can be run "as is")_
3131

3232
## Output data {#structured-output}
3333

34-
The [`Agent`][pydantic_ai.Agent] class constructor takes an `output_type` argument that takes one or more types or [output functions](#output-functions). It supports simple scalar types, list and dict types, dataclasses and Pydantic models, as well as type unions -- generally everything supported as type hints in a Pydantic model. You can also pass a list of multiple choices.
34+
The [`Agent`][pydantic_ai.Agent] class constructor takes an `output_type` argument that takes one or more types or [output functions](#output-functions). It supports simple scalar types, list and dict types (including `TypedDict`s and [`StructuredDict`s](#structured-dict)), dataclasses and Pydantic models, as well as type unions -- generally everything supported as type hints in a Pydantic model. You can also pass a list of multiple choices.
3535

3636
By default, Pydantic AI leverages the model's tool calling capability to make it return structured data. When multiple output types are specified (in a union or list), each member is registered with the model as a separate output tool in order to reduce the complexity of the schema and maximise the chances a model will respond correctly. This has been shown to work well across a wide range of models. If you'd like to change the names of the output tools, use a model's native structured output feature, or pass the output schema to the model in its [instructions](agents.md#instructions), you can use an [output mode](#output-modes) marker class.
3737

@@ -117,7 +117,6 @@ print(result.output)
117117

118118
_(This example is complete, it can be run "as is")_
119119

120-
121120
### Output functions
122121

123122
Instead of plain text or structured data, you may want the output of your agent run to be the result of a function called with arguments provided by the model, for example to further process or validate the data provided through the arguments (with the option to tell the model to try again), or to hand off to another agent.
@@ -387,6 +386,37 @@ print(repr(result.output))
387386

388387
_(This example is complete, it can be run "as is")_
389388

389+
### Custom JSON schema {#structured-dict}
390+
391+
If it's not feasible to define your desired structured output object using a Pydantic `BaseModel`, dataclass, or `TypedDict`, for example when you get a JSON schema from an external source or generate it dynamically, you can use the [`StructuredDict()`][pydantic_ai.output.StructuredDict] helper function to generate a `dict[str, Any]` subclass with a JSON schema attached that Pydantic AI will pass to the model.
392+
393+
Note that Pydantic AI will not perform any validation of the received JSON object and it's up to the model to correctly interpret the schema and any constraints expressed in it, like required fields or integer value ranges.
394+
395+
The output type will be a `dict[str, Any]` and it's up to your code to defensively read from it in case the model made a mistake. You can use an [output validator](#output-validator-functions) to reflect validation errors back to the model and get it to try again.
396+
397+
Along with the JSON schema, you can optionally pass `name` and `description` arguments to provide additional context to the model:
398+
399+
```python
400+
from pydantic_ai import Agent, StructuredDict
401+
402+
HumanDict = StructuredDict(
403+
{
404+
"type": "object",
405+
"properties": {
406+
"name": {"type": "string"},
407+
"age": {"type": "integer"}
408+
},
409+
"required": ["name", "age"]
410+
},
411+
name="Human",
412+
description="A human with a name and age",
413+
)
414+
415+
agent = Agent('openai:gpt-4o', output_type=HumanDict)
416+
result = agent.run_sync("Create a person")
417+
#> {'name': 'John Doe', 'age': 30}
418+
```
419+
390420
### Output validators {#output-validator-functions}
391421

392422
Some validation is inconvenient or impossible to do in Pydantic validators, in particular when the validation requires IO and is asynchronous. PydanticAI provides a way to add validation functions via the [`agent.output_validator`][pydantic_ai.Agent.output_validator] decorator.

fasta2a/LICENSE

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)