Skip to content

Commit ee93121

Browse files
committed
add openrouter web plugin
1 parent 5700a19 commit ee93121

File tree

2 files changed

+40
-17
lines changed

2 files changed

+40
-17
lines changed

pydantic_ai_slim/pydantic_ai/models/openai.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -567,10 +567,6 @@ def _process_response(self, response: chat.ChatCompletion | str) -> ModelRespons
567567
if reasoning := getattr(choice.message, 'reasoning', None):
568568
items.append(ThinkingPart(id='reasoning', content=reasoning, provider_name=self.system))
569569

570-
# NOTE: We don't currently handle OpenRouter `reasoning_details`:
571-
# - https://openrouter.ai/docs/use-cases/reasoning-tokens#preserving-reasoning-blocks
572-
# If you need this, please file an issue.
573-
574570
if choice.message.content:
575571
items.extend(
576572
(replace(part, id='content', provider_name=self.system) if isinstance(part, ThinkingPart) else part)

pydantic_ai_slim/pydantic_ai/models/openrouter.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class OpenRouterMaxPrice(TypedDict, total=False):
114114
class OpenRouterProviderConfig(TypedDict, total=False):
115115
"""Represents the 'Provider' object from the OpenRouter API."""
116116

117-
order: list[OpenRouterSlug]
117+
order: list[OpenRouterProvider]
118118
"""List of provider slugs to try in order (e.g. ["anthropic", "openai"]). [See details](https://openrouter.ai/docs/features/provider-routing#ordering-specific-providers)"""
119119

120120
allow_fallbacks: bool
@@ -129,7 +129,7 @@ class OpenRouterProviderConfig(TypedDict, total=False):
129129
zdr: bool
130130
"""Restrict routing to only ZDR (Zero Data Retention) endpoints. [See details](https://openrouter.ai/docs/features/provider-routing#zero-data-retention-enforcement)"""
131131

132-
only: list[OpenRouterSlug]
132+
only: list[OpenRouterProvider]
133133
"""List of provider slugs to allow for this request. [See details](https://openrouter.ai/docs/features/provider-routing#allowing-only-specific-providers)"""
134134

135135
ignore: list[str]
@@ -166,6 +166,36 @@ class OpenRouterReasoning(TypedDict, total=False):
166166
"""Whether to enable reasoning with default parameters. Default is inferred from effort or max_tokens."""
167167

168168

169+
class WebPlugin(TypedDict, total=False):
170+
"""You can incorporate relevant web search results for any model on OpenRouter by activating and customizing the web plugin.
171+
172+
The web search plugin is powered by native search for Anthropic and OpenAI natively and by Exa for other models. For Exa, it uses their "auto" method (a combination of keyword search and embeddings-based web search) to find the most relevant results and augment/ground your prompt.
173+
"""
174+
175+
id: Literal['web']
176+
177+
engine: Literal['native', 'exa', 'undefined']
178+
"""The web search plugin supports the following options for the engine parameter:
179+
180+
`native`: Always uses the model provider's built-in web search capabilities
181+
`exa`: Uses Exa's search API for web results
182+
`undefined` (not specified): Uses native search if available for the provider, otherwise falls back to Exa
183+
184+
Native search is used by default for OpenAI and Anthropic models that support it
185+
Exa search is used for all other models or when native search is not supported.
186+
187+
When you explicitly specify "engine": "native", it will always attempt to use the provider's native search, even if the model doesn't support it (which may result in an error)."""
188+
189+
max_results: int
190+
"""The maximum results allowed by the web plugin."""
191+
192+
search_prompt: str
193+
"""The prompt used to attach results to your message."""
194+
195+
196+
OpenRouterPlugin = WebPlugin
197+
198+
169199
class OpenRouterModelSettings(ModelSettings, total=False):
170200
"""Settings used for an OpenRouter model request."""
171201

@@ -199,6 +229,8 @@ class OpenRouterModelSettings(ModelSettings, total=False):
199229
The reasoning config object consolidates settings for controlling reasoning strength across different models. [See more](https://openrouter.ai/docs/use-cases/reasoning-tokens)
200230
"""
201231

232+
openrouter_plugins: list[OpenRouterPlugin]
233+
202234

203235
class OpenRouterError(BaseModel):
204236
"""Utility class to validate error messages from OpenRouter."""
@@ -288,23 +320,18 @@ def _openrouter_settings_to_openai_settings(model_settings: OpenRouterModelSetti
288320
Returns:
289321
An 'OpenAIChatModelSettings' object with equivalent settings.
290322
"""
291-
extra_body: dict[str, Any] = {}
323+
extra_body = model_settings['extra_body']
292324

293-
if models := model_settings.get('openrouter_models'):
325+
if models := model_settings.pop('openrouter_models', None):
294326
extra_body['models'] = models
295-
if provider := model_settings.get('openrouter_provider'):
327+
if provider := model_settings.pop('openrouter_provider', None):
296328
extra_body['provider'] = provider
297-
if preset := model_settings.get('openrouter_preset'):
329+
if preset := model_settings.pop('openrouter_preset', None):
298330
extra_body['preset'] = preset
299-
if transforms := model_settings.get('openrouter_transforms'):
331+
if transforms := model_settings.pop('openrouter_transforms', None):
300332
extra_body['transforms'] = transforms
301333

302-
base_keys = ModelSettings.__annotations__.keys()
303-
base_data: dict[str, Any] = {k: model_settings[k] for k in base_keys if k in model_settings}
304-
305-
new_settings = OpenAIChatModelSettings(**base_data, extra_body=extra_body)
306-
307-
return new_settings
334+
return OpenAIChatModelSettings(**model_settings, extra_body=extra_body)
308335

309336

310337
class OpenRouterModel(OpenAIChatModel):

0 commit comments

Comments
 (0)