Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
227e873
Add OpenRouter support and test coverage
ajac-zero Oct 4, 2025
c3c1546
Add OpenRouter reasoning config and refactor response details
ajac-zero Oct 5, 2025
10a1a17
Move OpenRouterModelSettings import into try block
ajac-zero Oct 5, 2025
5e64a62
Update pydantic_ai_slim/pydantic_ai/models/openrouter.py
ajac-zero Oct 7, 2025
5e787da
Merge branch 'main' into main
ajac-zero Oct 7, 2025
e219b8c
Handle OpenRouter errors and extract response metadata
ajac-zero Oct 8, 2025
c5e0600
Merge branch 'pydantic:main' into main
ajac-zero Oct 8, 2025
6f99fb2
Add type ignores to tests
ajac-zero Oct 8, 2025
83d14b1
Merge branch 'pydantic:main' into main
ajac-zero Oct 10, 2025
ef3c6dd
Send back reasoning_details/signature
ajac-zero Oct 10, 2025
0ba3691
Merge branch 'pydantic:main' into main
ajac-zero Oct 13, 2025
ed9e7df
add OpenRouterChatCompletion model
ajac-zero Oct 16, 2025
0689e29
Merge branch 'pydantic:main' into main
ajac-zero Oct 16, 2025
75adbb4
Update pydantic_ai_slim/pydantic_ai/models/openrouter.py
ajac-zero Oct 24, 2025
ab9d690
Update pydantic_ai_slim/pydantic_ai/models/openrouter.py
ajac-zero Oct 24, 2025
db1630d
Merge branch 'main' into main
ajac-zero Oct 24, 2025
5700a19
fix spelling mistake
ajac-zero Oct 16, 2025
ee93121
add openrouter web plugin
ajac-zero Oct 24, 2025
ca45f8a
WIP build reasoning_details from ThinkingParts
ajac-zero Oct 24, 2025
113c1cb
Merge branch 'main' into main
ajac-zero Oct 27, 2025
5db26d0
Merge branch 'main' into main
ajac-zero Oct 27, 2025
91bee62
Merge branch 'main' into main
ajac-zero Oct 28, 2025
b325816
wip reasoning details conversion
ajac-zero Oct 27, 2025
1db529f
finish openrouter thinking part
ajac-zero Oct 27, 2025
3d7f1b4
add preserve reasoning tokens test
ajac-zero Oct 28, 2025
1d7a8a4
fix typing
ajac-zero Oct 28, 2025
e81621b
Merge branch 'main' into main
ajac-zero Oct 28, 2025
c6aca8d
Merge branch 'main' into main
ajac-zero Oct 29, 2025
516e823
remove <thinking> tags from content
ajac-zero Oct 29, 2025
b8406d0
Merge branch 'main' into main
ajac-zero Oct 29, 2025
c16c960
fix typing
ajac-zero Oct 29, 2025
63d1b84
Merge branch 'main' into main
ajac-zero Oct 29, 2025
0835073
Merge branch 'main' into main
ajac-zero Nov 1, 2025
baede41
add _map_model_response method
ajac-zero Nov 1, 2025
89ef9a8
move assert_never import to typing_extensions
ajac-zero Nov 1, 2025
ebc8d08
add tool calling test
ajac-zero Nov 2, 2025
21a78e4
replace process_response with hooks
ajac-zero Nov 2, 2025
0b37792
add stream hooks
ajac-zero Nov 3, 2025
8d090f0
simplify hooks
ajac-zero Nov 3, 2025
e8c3c81
fix coverage/linting
ajac-zero Nov 3, 2025
02b8527
Merge branch 'main' into main
ajac-zero Nov 3, 2025
895ea03
Merge branch 'main' into main
ajac-zero Nov 3, 2025
7c50f07
fix lint
ajac-zero Nov 3, 2025
8e32475
replace OpenRouterThinking with encoding in 'id'
ajac-zero Nov 4, 2025
9d57be0
Merge branch 'main' into main
ajac-zero Nov 4, 2025
0a110d2
replace cast with asserts
ajac-zero Nov 7, 2025
64f75f8
Merge branch 'main' into main
ajac-zero Nov 7, 2025
a1f5385
fix merge changes
ajac-zero Nov 8, 2025
5f7c039
Merge branch 'main' into main
ajac-zero Nov 10, 2025
a65a795
Merge branch 'main' into main
DouweM Nov 10, 2025
ba40e05
Update pydantic_ai_slim/pydantic_ai/models/openai.py
ajac-zero Nov 11, 2025
18c5ac1
Merge branch 'main' into main
ajac-zero Nov 13, 2025
d50e24f
add usage
ajac-zero Nov 13, 2025
d47dba8
Merge branch 'main' into main
ajac-zero Nov 14, 2025
8bcb352
add explicit thinking part mapping
ajac-zero Nov 14, 2025
b13afb6
Merge branch 'main' into main
ajac-zero Nov 18, 2025
acb55dd
Refactor provider details mapping into top-level private function
ajac-zero Nov 18, 2025
5120c2f
Merge branch 'main' into main
ajac-zero Nov 18, 2025
0b38096
Merge branch 'main' into main
ajac-zero Nov 19, 2025
d41d0b6
add docs
ajac-zero Nov 19, 2025
a4c429e
add docs
ajac-zero Nov 19, 2025
79fbc20
add docs
ajac-zero Nov 19, 2025
1079522
refacotr _map_model_response with context
ajac-zero Nov 19, 2025
33c9e89
fix linting
ajac-zero Nov 19, 2025
faec3ed
Merge branch 'main' into main
ajac-zero Nov 19, 2025
776bcb4
refactor reasoning detail conversion
ajac-zero Nov 20, 2025
bb5fabf
Merge branch 'main' into main
ajac-zero Nov 20, 2025
490eff0
Merge branch 'main' into main
ajac-zero Nov 20, 2025
2568fda
Update docs/models/openrouter.md
ajac-zero Nov 20, 2025
e3da005
make _into_reasoning_detail more explicit
ajac-zero Nov 21, 2025
9271305
Merge branch 'main' into main
ajac-zero Nov 21, 2025
86df47b
Merge branch 'main' into main
DouweM Nov 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 1 addition & 29 deletions docs/models/openai.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ agent = Agent(model)
```

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.
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 `OpenAIChatModel` explicitly. Similarly, you can pass the provider name as a string to the `provider` argument on `OpenAIChatModel` instead of building instantiating the provider class explicitly.
When a provider has its own provider class, you can use the `Agent("<provider>:<model>")` shorthand, e.g. `Agent("deepseek:deepseek-chat")` or `Agent("moonshotai:kimi-k2-0711-preview")`, instead of building the `OpenAIChatModel` explicitly. Similarly, you can pass the provider name as a string to the `provider` argument on `OpenAIChatModel` instead of building instantiating the provider class explicitly.

#### Model Profile

Expand Down Expand Up @@ -385,34 +385,6 @@ agent = Agent(model)
...
```

### OpenRouter

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

You can set the `OPENROUTER_API_KEY` environment variable and use [`OpenRouterProvider`][pydantic_ai.providers.openrouter.OpenRouterProvider] by name:

```python
from pydantic_ai import Agent

agent = Agent('openrouter:anthropic/claude-3.5-sonnet')
...
```

Or initialise the model and provider directly:

```python
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openrouter import OpenRouterProvider

model = OpenAIChatModel(
'anthropic/claude-3.5-sonnet',
provider=OpenRouterProvider(api_key='your-openrouter-api-key'),
)
agent = Agent(model)
...
```

### Vercel AI Gateway

To use [Vercel's AI Gateway](https://vercel.com/docs/ai-gateway), first follow the [documentation](https://vercel.com/docs/ai-gateway) instructions on obtaining an API key or OIDC token.
Expand Down
54 changes: 54 additions & 0 deletions docs/models/openrouter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# OpenRouter

## Install

To use `OpenRouterModel`, you need to either install `pydantic-ai`, or install `pydantic-ai-slim` with the `openrouter` optional group:

```bash
pip/uv-add "pydantic-ai-slim[openrouter]"
```

## Configuration

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

You can set the `OPENROUTER_API_KEY` environment variable and use [`OpenRouterProvider`][pydantic_ai.providers.openrouter.OpenRouterProvider] by name:

```python
from pydantic_ai import Agent

agent = Agent('openrouter:anthropic/claude-3.5-sonnet')
...
```

Or initialise the model and provider directly:

```python
from pydantic_ai import Agent
from pydantic_ai.models.openrouter import OpenRouterModel
from pydantic_ai.providers.openrouter import OpenRouterProvider

model = OpenRouterModel(
'anthropic/claude-3.5-sonnet',
provider=OpenRouterProvider(api_key='your-openrouter-api-key'),
)
agent = Agent(model)
...
```

## App Attribution

OpenRouter has an [app attribution](https://openrouter.ai/docs/app-attribution) feature to track your application in their public ranking and analytics.

You can pass in an `app_url` and `app_title` when initializing the provider to enable app attribution.

```python
from pydantic_ai.providers.openrouter import OpenRouterProvider

provider=OpenRouterProvider(
api_key='your-openrouter-api-key',
app_url='https://your-app.com',
app_title='Your App',
),
...
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ nav:
- models/google.md
- models/bedrock.md
- models/cohere.md
- models/openrouter.md
- models/groq.md
- models/mistral.md
- models/huggingface.md
Expand Down
6 changes: 6 additions & 0 deletions pydantic_ai_slim/pydantic_ai/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,12 @@ class ThinkingPart:
part_kind: Literal['thinking'] = 'thinking'
"""Part type identifier, this is available on all parts as a discriminator."""

provider_details: dict[str, Any] | None = None
"""Additional provider-specific details in a serializable format.

This allows storing selected vendor-specific data that isn't mapped to standard ThinkingPart fields.
"""

def has_content(self) -> bool:
"""Return `True` if the thinking content is non-empty."""
return bool(self.content)
Expand Down
5 changes: 4 additions & 1 deletion pydantic_ai_slim/pydantic_ai/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,6 @@ def infer_model( # noqa: C901
'heroku',
'moonshotai',
'ollama',
'openrouter',
'together',
'vercel',
'litellm',
Expand Down Expand Up @@ -838,6 +837,10 @@ def infer_model( # noqa: C901
from .cohere import CohereModel

return CohereModel(model_name, provider=provider)
elif model_kind == 'openrouter':
from .openrouter import OpenRouterModel

return OpenRouterModel(model_name, provider=provider)
elif model_kind == 'mistral':
from .mistral import MistralModel

Expand Down
Loading