Skip to content

Commit 6eea048

Browse files
authored
Merge branch 'main' into patch-3323
2 parents 39d47b5 + b8d2904 commit 6eea048

28 files changed

+515
-196
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ jobs:
8080
- run: make docs
8181

8282
- run: make docs-insiders
83-
if: github.event.pull_request.head.repo.full_name == github.repository || github.ref == 'refs/heads/main'
83+
if: (github.event.pull_request.head.repo.full_name == github.repository || github.ref == 'refs/heads/main') && github.repository == 'pydantic/pydantic-ai'
8484
env:
8585
PPPR_TOKEN: ${{ secrets.PPPR_TOKEN }}
8686

@@ -103,7 +103,7 @@ jobs:
103103
test-live:
104104
runs-on: ubuntu-latest
105105
timeout-minutes: 5
106-
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push'
106+
if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push') && github.repository == 'pydantic/pydantic-ai'
107107
steps:
108108
- uses: actions/checkout@v4
109109

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ node_modules/
2121
/test_tmp/
2222
.mcp.json
2323
.claude/
24+
/.cursor/
25+
/.devcontainer/

Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ typecheck-both: typecheck-pyright typecheck-mypy
5353
.PHONY: test
5454
test: ## Run tests and collect coverage data
5555
@# To test using a specific version of python, run 'make install-all-python' then set environment variable PYTEST_PYTHON=3.10 or similar
56-
$(if $(PYTEST_PYTHON),UV_PROJECT_ENVIRONMENT=.venv$(subst .,,$(PYTEST_PYTHON))) uv run $(if $(PYTEST_PYTHON),--python $(PYTEST_PYTHON)) coverage run -m pytest -n auto --dist=loadgroup --durations=20
56+
COLUMNS=150 $(if $(PYTEST_PYTHON),UV_PROJECT_ENVIRONMENT=.venv$(subst .,,$(PYTEST_PYTHON))) uv run $(if $(PYTEST_PYTHON),--python $(PYTEST_PYTHON)) coverage run -m pytest -n auto --dist=loadgroup --durations=20
5757
@uv run coverage combine
5858
@uv run coverage report
5959

6060
.PHONY: test-all-python
6161
test-all-python: ## Run tests on Python 3.10 to 3.13
62-
UV_PROJECT_ENVIRONMENT=.venv310 uv run --python 3.10 --all-extras --all-packages coverage run -p -m pytest
63-
UV_PROJECT_ENVIRONMENT=.venv311 uv run --python 3.11 --all-extras --all-packages coverage run -p -m pytest
64-
UV_PROJECT_ENVIRONMENT=.venv312 uv run --python 3.12 --all-extras --all-packages coverage run -p -m pytest
65-
UV_PROJECT_ENVIRONMENT=.venv313 uv run --python 3.13 --all-extras --all-packages coverage run -p -m pytest
62+
COLUMNS=150 UV_PROJECT_ENVIRONMENT=.venv310 uv run --python 3.10 --all-extras --all-packages coverage run -p -m pytest
63+
COLUMNS=150 UV_PROJECT_ENVIRONMENT=.venv311 uv run --python 3.11 --all-extras --all-packages coverage run -p -m pytest
64+
COLUMNS=150 UV_PROJECT_ENVIRONMENT=.venv312 uv run --python 3.12 --all-extras --all-packages coverage run -p -m pytest
65+
COLUMNS=150 UV_PROJECT_ENVIRONMENT=.venv313 uv run --python 3.13 --all-extras --all-packages coverage run -p -m pytest
6666
@uv run coverage combine
6767
@uv run coverage report
6868

docs/examples/ag-ui.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Agent User Interaction (AG-UI)
22

3-
Example of using Pydantic AI agents with the [AG-UI Dojo](https://github.com/ag-ui-protocol/ag-ui/tree/main/typescript-sdk/apps/dojo) example app.
3+
Example of using Pydantic AI agents with the [AG-UI Dojo](https://github.com/ag-ui-protocol/ag-ui/tree/main/apps/dojo) example app.
44

55
See the [AG-UI docs](../ui/ag-ui.md) for more information about the AG-UI integration.
66

@@ -48,7 +48,7 @@ Next run the AG-UI Dojo example frontend.
4848
cd ag-ui/sdks/typescript
4949
```
5050

51-
3. Run the Dojo app following the [official instructions](https://github.com/ag-ui-protocol/ag-ui/tree/main/typescript-sdk/apps/dojo#development-setup)
51+
3. Run the Dojo app following the [official instructions](https://github.com/ag-ui-protocol/ag-ui/tree/main/apps/dojo#development-setup)
5252
4. Visit <http://localhost:3000/pydantic-ai>
5353
5. Select View `Pydantic AI` from the sidebar
5454

docs/models/bedrock.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,47 @@ model = BedrockConverseModel(
114114
agent = Agent(model)
115115
...
116116
```
117+
118+
## Configuring Retries
119+
120+
Bedrock uses boto3's built-in retry mechanisms. You can configure retry behavior by passing a custom boto3 client with retry settings:
121+
122+
```python
123+
import boto3
124+
from botocore.config import Config
125+
126+
from pydantic_ai import Agent
127+
from pydantic_ai.models.bedrock import BedrockConverseModel
128+
from pydantic_ai.providers.bedrock import BedrockProvider
129+
130+
# Configure retry settings
131+
config = Config(
132+
retries={
133+
'max_attempts': 5,
134+
'mode': 'adaptive' # Recommended for rate limiting
135+
}
136+
)
137+
138+
bedrock_client = boto3.client(
139+
'bedrock-runtime',
140+
region_name='us-east-1',
141+
config=config
142+
)
143+
144+
model = BedrockConverseModel(
145+
'us.amazon.nova-micro-v1:0',
146+
provider=BedrockProvider(bedrock_client=bedrock_client),
147+
)
148+
agent = Agent(model)
149+
```
150+
151+
### Retry Modes
152+
153+
- `'legacy'` (default): 5 attempts, basic retry behavior
154+
- `'standard'`: 3 attempts, more comprehensive error coverage
155+
- `'adaptive'`: 3 attempts with client-side rate limiting (recommended for handling `ThrottlingException`)
156+
157+
For more details on boto3 retry configuration, see the [AWS boto3 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html).
158+
159+
!!! note
160+
Unlike other providers that use httpx for HTTP requests, Bedrock uses boto3's native retry mechanisms. The retry strategies described in [HTTP Request Retries](../retries.md) do not apply to Bedrock.

docs/retries.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,17 @@ agent = Agent(model)
339339
- Use async transports for better concurrency when handling multiple requests
340340

341341
For more advanced retry configurations, refer to the [tenacity documentation](https://tenacity.readthedocs.io/).
342+
343+
## Provider-Specific Retry Behavior
344+
345+
### AWS Bedrock
346+
347+
The AWS Bedrock provider uses boto3's built-in retry mechanisms instead of httpx. To configure retries for Bedrock, use boto3's `Config`:
348+
349+
```python
350+
from botocore.config import Config
351+
352+
config = Config(retries={'max_attempts': 5, 'mode': 'adaptive'})
353+
```
354+
355+
See [Bedrock: Configuring Retries](models/bedrock.md#configuring-retries) for complete examples.

pydantic_ai_slim/pydantic_ai/exceptions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ def __init__(self, message: str):
4444
def __eq__(self, other: Any) -> bool:
4545
return isinstance(other, self.__class__) and other.message == self.message
4646

47+
def __hash__(self) -> int:
48+
return hash((self.__class__, self.message))
49+
4750
@classmethod
4851
def __get_pydantic_core_schema__(cls, _: Any, __: Any) -> core_schema.CoreSchema:
4952
"""Pydantic core schema to allow `ModelRetry` to be (de)serialized."""

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -776,10 +776,11 @@ def model_response_str(self) -> str:
776776
def model_response_object(self) -> dict[str, Any]:
777777
"""Return a dictionary representation of the content, wrapping non-dict types appropriately."""
778778
# gemini supports JSON dict return values, but no other JSON types, hence we wrap anything else in a dict
779-
if isinstance(self.content, dict):
780-
return tool_return_ta.dump_python(self.content, mode='json') # pyright: ignore[reportUnknownMemberType]
779+
json_content = tool_return_ta.dump_python(self.content, mode='json')
780+
if isinstance(json_content, dict):
781+
return json_content # type: ignore[reportUnknownReturn]
781782
else:
782-
return {'return_value': tool_return_ta.dump_python(self.content, mode='json')}
783+
return {'return_value': json_content}
783784

784785
def otel_event(self, settings: InstrumentationSettings) -> Event:
785786
return Event(

pydantic_ai_slim/pydantic_ai/models/__init__.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import base64
1010
import warnings
1111
from abc import ABC, abstractmethod
12-
from collections.abc import AsyncIterator, Iterator
12+
from collections.abc import AsyncIterator, Callable, Iterator
1313
from contextlib import asynccontextmanager, contextmanager
1414
from dataclasses import dataclass, field, replace
1515
from datetime import datetime
@@ -47,7 +47,7 @@
4747
)
4848
from ..output import OutputMode
4949
from ..profiles import DEFAULT_PROFILE, ModelProfile, ModelProfileSpec
50-
from ..providers import infer_provider
50+
from ..providers import Provider, infer_provider
5151
from ..settings import ModelSettings, merge_model_settings
5252
from ..tools import ToolDefinition
5353
from ..usage import RequestUsage
@@ -126,18 +126,16 @@
126126
'cerebras:gpt-oss-120b',
127127
'cerebras:llama3.1-8b',
128128
'cerebras:llama-3.3-70b',
129-
'cerebras:llama-4-scout-17b-16e-instruct',
130-
'cerebras:llama-4-maverick-17b-128e-instruct',
131129
'cerebras:qwen-3-235b-a22b-instruct-2507',
132130
'cerebras:qwen-3-32b',
133-
'cerebras:qwen-3-coder-480b',
134131
'cerebras:qwen-3-235b-a22b-thinking-2507',
135132
'cohere:c4ai-aya-expanse-32b',
136133
'cohere:c4ai-aya-expanse-8b',
137134
'cohere:command-nightly',
138135
'cohere:command-r-08-2024',
139136
'cohere:command-r-plus-08-2024',
140137
'cohere:command-r7b-12-2024',
138+
'cerebras:zai-glm-4.6',
141139
'deepseek:deepseek-chat',
142140
'deepseek:deepseek-reasoner',
143141
'google-gla:gemini-2.0-flash',
@@ -189,11 +187,15 @@
189187
'groq:llama-3.2-3b-preview',
190188
'groq:llama-3.2-11b-vision-preview',
191189
'groq:llama-3.2-90b-vision-preview',
190+
'heroku:amazon-rerank-1-0',
192191
'heroku:claude-3-5-haiku',
193192
'heroku:claude-3-5-sonnet-latest',
194193
'heroku:claude-3-7-sonnet',
195-
'heroku:claude-4-sonnet',
196194
'heroku:claude-3-haiku',
195+
'heroku:claude-4-5-haiku',
196+
'heroku:claude-4-5-sonnet',
197+
'heroku:claude-4-sonnet',
198+
'heroku:cohere-rerank-3-5',
197199
'heroku:gpt-oss-120b',
198200
'heroku:nova-lite',
199201
'heroku:nova-pro',
@@ -722,8 +724,17 @@ def override_allow_model_requests(allow_model_requests: bool) -> Iterator[None]:
722724
ALLOW_MODEL_REQUESTS = old_value # pyright: ignore[reportConstantRedefinition]
723725

724726

725-
def infer_model(model: Model | KnownModelName | str) -> Model: # noqa: C901
726-
"""Infer the model from the name."""
727+
def infer_model( # noqa: C901
728+
model: Model | KnownModelName | str, provider_factory: Callable[[str], Provider[Any]] = infer_provider
729+
) -> Model:
730+
"""Infer the model from the name.
731+
732+
Args:
733+
model:
734+
Model name to instantiate, in the format of `provider:model`. Use the string "test" to instantiate TestModel.
735+
provider_factory:
736+
Function that instantiates a provider object. The provider name is passed into the function parameter. Defaults to `provider.infer_provider`.
737+
"""
727738
if isinstance(model, Model):
728739
return model
729740
elif model == 'test':
@@ -758,11 +769,13 @@ def infer_model(model: Model | KnownModelName | str) -> Model: # noqa: C901
758769
)
759770
provider_name = 'google-vertex'
760771

761-
provider = infer_provider(provider_name)
772+
provider: Provider[Any] = provider_factory(provider_name)
762773

763774
model_kind = provider_name
764775
if model_kind.startswith('gateway/'):
765-
model_kind = provider_name.removeprefix('gateway/')
776+
from ..providers.gateway import infer_gateway_model
777+
778+
return infer_gateway_model(model_kind.removeprefix('gateway/'), model_name=model_name)
766779
if model_kind in (
767780
'openai',
768781
'azure',

pydantic_ai_slim/pydantic_ai/models/bedrock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def __init__(
226226
self._model_name = model_name
227227

228228
if isinstance(provider, str):
229-
provider = infer_provider('gateway/bedrock' if provider == 'gateway' else provider)
229+
provider = infer_provider('gateway/converse' if provider == 'gateway' else provider)
230230
self._provider = provider
231231
self.client = cast('BedrockRuntimeClient', provider.client)
232232

0 commit comments

Comments
 (0)