Skip to content

Commit 1d47e1e

Browse files
committed
merge
2 parents 97ff651 + 7a7ca1e commit 1d47e1e

Some content is hidden

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

66 files changed

+2336
-457
lines changed

docs/api/profiles.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# `pydantic_ai.profiles`
2+
3+
::: pydantic_ai.profiles.ModelProfile
4+
5+
::: pydantic_ai.profiles.openai
6+
7+
::: pydantic_ai.profiles.anthropic
8+
9+
::: pydantic_ai.profiles.google
10+
11+
::: pydantic_ai.profiles.meta
12+
13+
::: pydantic_ai.profiles.amazon
14+
15+
::: pydantic_ai.profiles.deepseek
16+
17+
::: pydantic_ai.profiles.grok
18+
19+
::: pydantic_ai.profiles.mistral
20+
21+
::: pydantic_ai.profiles.qwen

docs/api/providers.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@
1919
::: pydantic_ai.providers.cohere
2020

2121
::: pydantic_ai.providers.mistral
22+
23+
::: pydantic_ai.providers.fireworks
24+
25+
::: pydantic_ai.providers.grok
26+
27+
::: pydantic_ai.providers.together

docs/evals.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,16 +333,16 @@ dataset = Dataset(
333333

334334

335335
async def double_number(input_value: int) -> int:
336-
"""Function that simulates work by sleeping for a second before returning double the input."""
336+
"""Function that simulates work by sleeping for a tenth of a second before returning double the input."""
337337
await asyncio.sleep(0.1) # Simulate work
338338
return input_value * 2
339339

340340

341341
# Run evaluation with unlimited concurrency
342342
t0 = time.time()
343343
report_default = dataset.evaluate_sync(double_number)
344-
print(f'Evaluation took less than 0.3s: {time.time() - t0 < 0.3}')
345-
#> Evaluation took less than 0.3s: True
344+
print(f'Evaluation took less than 0.5s: {time.time() - t0 < 0.5}')
345+
#> Evaluation took less than 0.5s: True
346346

347347
report_default.print(include_input=True, include_output=True, include_durations=False) # (1)!
348348
"""

docs/models/google.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ from pydantic_ai import Agent
8080
from pydantic_ai.models.google import GoogleModel
8181
from pydantic_ai.providers.google import GoogleProvider
8282

83-
credentials = service_account.Credentials.from_service_account_file('path/to/service-account.json')
83+
credentials = service_account.Credentials.from_service_account_file(
84+
'path/to/service-account.json',
85+
scopes=['https://www.googleapis.com/auth/cloud-platform'],
86+
)
8487
provider = GoogleProvider(credentials=credentials)
8588
model = GoogleModel('gemini-1.5-flash', provider=provider)
8689
agent = Agent(model)

docs/models/index.md

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@
33
PydanticAI is model-agnostic and has built-in support for multiple model providers:
44

55
* [OpenAI](openai.md)
6-
* [DeepSeek](openai.md#openai-compatible-models)
76
* [Anthropic](anthropic.md)
87
* [Gemini](gemini.md) (via two different APIs: Generative Language API and VertexAI API)
9-
* [Ollama](openai.md#ollama)
108
* [Groq](groq.md)
119
* [Mistral](mistral.md)
1210
* [Cohere](cohere.md)
1311
* [Bedrock](bedrock.md)
1412

1513
## OpenAI-compatible Providers
1614

17-
Many models are compatible with the OpenAI API, and can be used with `OpenAIModel` in PydanticAI:
15+
In addition, many providers are compatible with the OpenAI API, and can be used with `OpenAIModel` in PydanticAI:
1816

19-
* [OpenRouter](openai.md#openrouter)
17+
* [DeepSeek](openai.md#deepseek)
2018
* [Grok (xAI)](openai.md#grok-xai)
19+
* [Ollama](openai.md#ollama)
20+
* [OpenRouter](openai.md#openrouter)
2121
* [Perplexity](openai.md#perplexity)
2222
* [Fireworks AI](openai.md#fireworks-ai)
2323
* [Together AI](openai.md#together-ai)
@@ -40,27 +40,33 @@ PydanticAI uses a few key terms to describe how it interacts with different LLMs
4040
roughly in the format `<VendorSdk>Model`, for example, we have `OpenAIModel`, `AnthropicModel`, `GeminiModel`,
4141
etc. When using a Model class, you specify the actual LLM model name (e.g., `gpt-4o`,
4242
`claude-3-5-sonnet-latest`, `gemini-1.5-flash`) as a parameter.
43-
* **Provider**: This refers to Model-specific classes which handle the authentication and connections
43+
* **Provider**: This refers to provider-specific classes which handle the authentication and connections
4444
to an LLM vendor. Passing a non-default _Provider_ as a parameter to a Model is how you can ensure
4545
that your agent will make requests to a specific endpoint, or make use of a specific approach to
4646
authentication (e.g., you can use Vertex-specific auth with the `GeminiModel` by way of the `VertexProvider`).
4747
In particular, this is how you can make use of an AI gateway, or an LLM vendor that offers API compatibility
4848
with the vendor SDK used by an existing Model (such as `OpenAIModel`).
49+
* **Profile**: This refers to a description of how requests to a specific model or family of models need to be
50+
constructed to get the best results, independent of the model and provider classes used.
51+
For example, different models have different restrictions on the JSON schemas that can be used for tools,
52+
and the same schema transformer needs to be used for Gemini models whether you're using `GoogleModel`
53+
with model name `gemini-2.5-pro-preview`, or `OpenAIModel` with `OpenRouterProvider` and model name `google/gemini-2.5-pro-preview`.
4954

50-
In short, you select a specific model name (like `gpt-4o`), PydanticAI uses the appropriate Model class (like `OpenAIModel`), and the provider handles the connection and authentication to the underlying service.
55+
When you instantiate an [`Agent`][pydantic_ai.Agent] with just a name formatted as `<provider>:<model>`, e.g. `openai:gpt-4o` or `openrouter:google/gemini-2.5-pro-preview`,
56+
PydanticAI will automatically select the appropriate model class, provider, and profile.
57+
If you want to use a different provider or profile, you can instantiate a model class directly and pass in `provider` and/or `profile` arguments.
5158

5259
## Custom Models
5360

54-
To implement support for models not already supported, you will need to subclass the [`Model`][pydantic_ai.models.Model] abstract base class.
55-
56-
For streaming, you'll also need to implement the following abstract base class:
57-
58-
* [`StreamedResponse`][pydantic_ai.models.StreamedResponse]
61+
To implement support for a model API that's not already supported, you will need to subclass the [`Model`][pydantic_ai.models.Model] abstract base class.
62+
For streaming, you'll also need to implement the [`StreamedResponse`][pydantic_ai.models.StreamedResponse] abstract base class.
5963

6064
The best place to start is to review the source code for existing implementations, e.g. [`OpenAIModel`](https://github.com/pydantic/pydantic-ai/blob/main/pydantic_ai_slim/pydantic_ai/models/openai.py).
6165

6266
For details on when we'll accept contributions adding new models to PydanticAI, see the [contributing guidelines](../contributing.md#new-model-rules).
6367

68+
If a model API is compatible with the OpenAI API, you do not need a custom model class and can provide your own [custom provider](openai.md#openai-compatible-models) instead.
69+
6470
<!-- TODO(Marcelo): We need to create a section in the docs about reliability. -->
6571
## Fallback Model
6672

docs/models/openai.md

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
## Install
44

5-
To use OpenAI models, you need to either install `pydantic-ai`, or install `pydantic-ai-slim` with the `openai` optional group:
5+
To use OpenAI models or OpenAI-compatible APIs, you need to either install `pydantic-ai`, or install `pydantic-ai-slim` with the `openai` optional group:
66

77
```bash
88
pip/uv-add "pydantic-ai-slim[openai]"
99
```
1010

1111
## Configuration
1212

13-
To use `OpenAIModel` through their main API, go to [platform.openai.com](https://platform.openai.com/) and follow your nose until you find the place to generate an API key.
13+
To use `OpenAIModel` with the OpenAI API, go to [platform.openai.com](https://platform.openai.com/) and follow your nose until you find the place to generate an API key.
1414

1515
## Environment variable
1616

@@ -130,7 +130,7 @@ You can learn more about the differences between the Responses API and Chat Comp
130130

131131
## OpenAI-compatible Models
132132

133-
Many models are compatible with the OpenAI API, and can be used with `OpenAIModel` in PydanticAI.
133+
Many providers and models are compatible with the OpenAI API, and can be used with `OpenAIModel` in PydanticAI.
134134
Before getting started, check the [installation and configuration](#install) instructions above.
135135

136136
To use another OpenAI-compatible API, you can make use of the `base_url` and `api_key` arguments from `OpenAIProvider`:
@@ -150,7 +150,40 @@ agent = Agent(model)
150150
...
151151
```
152152

153-
You can also use the `provider` argument with a custom provider class like the `DeepSeekProvider`:
153+
Various providers also have their own provider classes so that you don't need to specify the base URL yourself and you can use the standard `<PROVIDER>_API_KEY` environment variable to set the API key.
154+
When a provider has its own provider class, you can use the `Agent("<provider>:<model>")` shorthand, e.g. `Agent("deepseek:deepseek-chat")` or `Agent("openrouter:google/gemini-2.5-pro-preview")`, instead of building the `OpenAIModel` explicitly. Similarly, you can pass the provider name as a string to the `provider` argument on `OpenAIModel` instead of building instantiating the provider class explicitly.
155+
156+
#### Model Profile
157+
158+
Sometimes, the provider or model you're using will have slightly different requirements than OpenAI's API or models, like having different restrictions on JSON schemas for tool definitions, or not supporting tool definitions to be marked as strict.
159+
160+
When using an alternative provider class provided by PydanticAI, an appropriate model profile is typically selected automatically based on the model name.
161+
If the model you're using is not working correctly out of the box, you can tweak various aspects of how model requests are constructed by providing your own [`ModelProfile`][pydantic_ai.profiles.ModelProfile] (for behaviors shared among all model classes) or [`OpenAIModelProfile`][pydantic_ai.profiles.openai.OpenAIModelProfile] (for behaviors specific to `OpenAIModel`):
162+
163+
```py
164+
from pydantic_ai import Agent
165+
from pydantic_ai.models.openai import OpenAIModel
166+
from pydantic_ai.profiles._json_schema import InlineDefsJsonSchemaTransformer
167+
from pydantic_ai.profiles.openai import OpenAIModelProfile
168+
from pydantic_ai.providers.openai import OpenAIProvider
169+
170+
model = OpenAIModel(
171+
'model_name',
172+
provider=OpenAIProvider(
173+
base_url='https://<openai-compatible-api-endpoint>.com', api_key='your-api-key'
174+
),
175+
profile=OpenAIModelProfile(
176+
json_schema_transformer=InlineDefsJsonSchemaTransformer, # Supported by any model class on a plain ModelProfile
177+
openai_supports_strict_tool_definition=False # Supported by OpenAIModel only, requires OpenAIModelProfile
178+
)
179+
)
180+
agent = Agent(model)
181+
```
182+
183+
### DeepSeek
184+
185+
To use the [DeepSeek](https://deepseek.com) provider, first create an API key by following the [Quick Start guide](https://api-docs.deepseek.com/).
186+
Once you have the API key, you can use it with the `DeepSeekProvider`:
154187

155188
```python
156189
from pydantic_ai import Agent
@@ -285,7 +318,7 @@ agent = Agent(model)
285318

286319
To use [OpenRouter](https://openrouter.ai), first create an API key at [openrouter.ai/keys](https://openrouter.ai/keys).
287320

288-
Once you have the API key, you can use it with the `OpenAIProvider`:
321+
Once you have the API key, you can use it with the `OpenRouterProvider`:
289322

290323
```python
291324
from pydantic_ai import Agent
@@ -303,16 +336,16 @@ agent = Agent(model)
303336
### Grok (xAI)
304337

305338
Go to [xAI API Console](https://console.x.ai/) and create an API key.
306-
Once you have the API key, you can use it with the `OpenAIProvider`:
339+
Once you have the API key, you can use it with the `GrokProvider`:
307340

308341
```python
309342
from pydantic_ai import Agent
310343
from pydantic_ai.models.openai import OpenAIModel
311-
from pydantic_ai.providers.openai import OpenAIProvider
344+
from pydantic_ai.providers.grok import GrokProvider
312345

313346
model = OpenAIModel(
314347
'grok-2-1212',
315-
provider=OpenAIProvider(base_url='https://api.x.ai/v1', api_key='your-xai-api-key'),
348+
provider=GrokProvider(api_key='your-xai-api-key'),
316349
)
317350
agent = Agent(model)
318351
...
@@ -342,19 +375,16 @@ agent = Agent(model)
342375
### Fireworks AI
343376

344377
Go to [Fireworks.AI](https://fireworks.ai/) and create an API key in your account settings.
345-
Once you have the API key, you can use it with the `OpenAIProvider`:
378+
Once you have the API key, you can use it with the `FireworksProvider`:
346379

347380
```python
348381
from pydantic_ai import Agent
349382
from pydantic_ai.models.openai import OpenAIModel
350-
from pydantic_ai.providers.openai import OpenAIProvider
383+
from pydantic_ai.providers.fireworks import FireworksProvider
351384

352385
model = OpenAIModel(
353386
'accounts/fireworks/models/qwq-32b', # model library available at https://fireworks.ai/models
354-
provider=OpenAIProvider(
355-
base_url='https://api.fireworks.ai/inference/v1',
356-
api_key='your-fireworks-api-key',
357-
),
387+
provider=FireworksProvider(api_key='your-fireworks-api-key'),
358388
)
359389
agent = Agent(model)
360390
...
@@ -363,19 +393,16 @@ agent = Agent(model)
363393
### Together AI
364394

365395
Go to [Together.ai](https://www.together.ai/) and create an API key in your account settings.
366-
Once you have the API key, you can use it with the `OpenAIProvider`:
396+
Once you have the API key, you can use it with the `TogetherProvider`:
367397

368398
```python
369399
from pydantic_ai import Agent
370400
from pydantic_ai.models.openai import OpenAIModel
371-
from pydantic_ai.providers.openai import OpenAIProvider
401+
from pydantic_ai.providers.together import TogetherProvider
372402

373403
model = OpenAIModel(
374404
'meta-llama/Llama-3.3-70B-Instruct-Turbo-Free', # model library available at https://www.together.ai/models
375-
provider=OpenAIProvider(
376-
base_url='https://api.together.xyz/v1',
377-
api_key='your-together-api-key',
378-
),
405+
provider=TogetherProvider(api_key='your-together-api-key'),
379406
)
380407
agent = Agent(model)
381408
...

examples/pydantic_ai_examples/weather_agent_gradio.py

100644100755
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,12 @@ async def stream_from_agent(prompt: str, chatbot: list[dict], past_messages: lis
3333
for message in result.new_messages():
3434
for call in message.parts:
3535
if isinstance(call, ToolCallPart):
36-
call_args = (
37-
call.args.args_json
38-
if hasattr(call.args, 'args_json')
39-
else json.dumps(call.args.args_dict)
40-
)
36+
call_args = call.args_as_json_str()
4137
metadata = {
4238
'title': f'🛠️ Using {TOOL_TO_DISPLAY_NAME[call.tool_name]}',
4339
}
4440
if call.tool_call_id is not None:
45-
metadata['id'] = {call.tool_call_id}
41+
metadata['id'] = call.tool_call_id
4642

4743
gr_message = {
4844
'role': 'assistant',

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ nav:
8585
- api/models/function.md
8686
- api/models/fallback.md
8787
- api/models/wrapper.md
88+
- api/profiles.md
8889
- api/providers.md
8990
- api/pydantic_graph/graph.md
9091
- api/pydantic_graph/nodes.md

pydantic_ai_slim/pydantic_ai/_output.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ async def process(
407407
if wrap_validation_errors:
408408
m = _messages.RetryPromptPart(
409409
tool_name=tool_call.tool_name,
410-
content=e.errors(include_url=False),
410+
content=e.errors(include_url=False, include_context=False),
411411
tool_call_id=tool_call.tool_call_id,
412412
)
413413
raise ToolRetryError(m) from e

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ def model_response_object(self) -> dict[str, Any]:
375375
"""Return a dictionary representation of the content, wrapping non-dict types appropriately."""
376376
# gemini supports JSON dict return values, but no other JSON types, hence we wrap anything else in a dict
377377
if isinstance(self.content, dict):
378-
return tool_return_ta.dump_python(self.content, mode='json') # pyright: ignore[reportUnknownMemberType] # pragma: no cover
378+
return tool_return_ta.dump_python(self.content, mode='json') # pyright: ignore[reportUnknownMemberType]
379379
else:
380380
return {'return_value': tool_return_ta.dump_python(self.content, mode='json')}
381381

0 commit comments

Comments
 (0)