language_models: Add provider options for OpenRouter models (#37979)
Supersedes: #34500 Also this will allow to fix this: #35386 without the UX changes but providers can now be control through settings as well within zed. Just rebased the latest main and docs added. Added @AurelienTollard as co-author as it was started by him everything else remains the same from original PR. Release Notes: - Added ability to control Provider Routing for OpenRouter models from settings. Co-authored-by: Aurelien Tollard <tollard.aurelien1999@gmail.com>
This commit is contained in:
co-authored by
Aurelien Tollard
parent
8201f3d72f
commit
1142408675
Generated
+1
@@ -11244,6 +11244,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"strum 0.27.1",
|
||||
"thiserror 2.0.12",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ use language_model::{
|
||||
LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage,
|
||||
};
|
||||
use open_router::{
|
||||
Model, ModelMode as OpenRouterModelMode, ResponseStreamEvent, list_models, stream_completion,
|
||||
Model, ModelMode as OpenRouterModelMode, Provider, ResponseStreamEvent, list_models,
|
||||
stream_completion,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -48,6 +49,7 @@ pub struct AvailableModel {
|
||||
pub supports_tools: Option<bool>,
|
||||
pub supports_images: Option<bool>,
|
||||
pub mode: Option<ModelMode>,
|
||||
pub provider: Option<Provider>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -296,6 +298,7 @@ impl LanguageModelProvider for OpenRouterLanguageModelProvider {
|
||||
supports_tools: model.supports_tools,
|
||||
supports_images: model.supports_images,
|
||||
mode: model.mode.clone().unwrap_or_default().into(),
|
||||
provider: model.provider.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -584,6 +587,7 @@ pub fn into_open_router(
|
||||
LanguageModelToolChoice::Any => open_router::ToolChoice::Required,
|
||||
LanguageModelToolChoice::None => open_router::ToolChoice::None,
|
||||
}),
|
||||
provider: model.provider.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,4 +24,5 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
strum.workspace = true
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
@@ -6,6 +6,7 @@ use serde_json::Value;
|
||||
use std::{convert::TryFrom, io, time::Duration};
|
||||
use strum::EnumString;
|
||||
use thiserror::Error;
|
||||
use util::serde::default_true;
|
||||
|
||||
pub const OPEN_ROUTER_API_URL: &str = "https://openrouter.ai/api/v1";
|
||||
|
||||
@@ -64,6 +65,41 @@ impl From<Role> for String {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DataCollection {
|
||||
Allow,
|
||||
Disallow,
|
||||
}
|
||||
|
||||
impl Default for DataCollection {
|
||||
fn default() -> Self {
|
||||
Self::Allow
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Provider {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
order: Option<Vec<String>>,
|
||||
#[serde(default = "default_true")]
|
||||
allow_fallbacks: bool,
|
||||
#[serde(default)]
|
||||
require_parameters: bool,
|
||||
#[serde(default)]
|
||||
data_collection: DataCollection,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
only: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ignore: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
quantizations: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
sort: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Model {
|
||||
@@ -74,6 +110,7 @@ pub struct Model {
|
||||
pub supports_images: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub mode: ModelMode,
|
||||
pub provider: Option<Provider>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
@@ -95,6 +132,7 @@ impl Model {
|
||||
Some(true),
|
||||
Some(false),
|
||||
Some(ModelMode::Default),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -109,6 +147,7 @@ impl Model {
|
||||
supports_tools: Option<bool>,
|
||||
supports_images: Option<bool>,
|
||||
mode: Option<ModelMode>,
|
||||
provider: Option<Provider>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.to_owned(),
|
||||
@@ -117,6 +156,7 @@ impl Model {
|
||||
supports_tools,
|
||||
supports_images,
|
||||
mode: mode.unwrap_or(ModelMode::Default),
|
||||
provider,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +204,7 @@ pub struct Request {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub reasoning: Option<Reasoning>,
|
||||
pub usage: RequestUsage,
|
||||
pub provider: Option<Provider>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
@@ -597,6 +638,7 @@ pub async fn list_models(
|
||||
} else {
|
||||
ModelMode::Default
|
||||
},
|
||||
provider: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -524,6 +524,53 @@ You can find available models and their specifications on the [OpenRouter models
|
||||
|
||||
Custom models will be listed in the model dropdown in the Agent Panel.
|
||||
|
||||
#### Provider Routing
|
||||
|
||||
You can optionally control how OpenRouter routes a given custom model request among underlying upstream providers via the `provider` object on each model entry.
|
||||
|
||||
Supported fields (all optional):
|
||||
|
||||
- `order`: Array of provider slugs to try first, in order (e.g. `["anthropic", "openai"]`)
|
||||
- `allow_fallbacks` (default: `true`): Whether fallback providers may be used if preferred ones are unavailable
|
||||
- `require_parameters` (default: `false`): Only use providers that support every parameter you supplied
|
||||
- `data_collection` (default: `allow`): `"allow"` or `"disallow"` (controls use of providers that may store data)
|
||||
- `only`: Whitelist of provider slugs allowed for this request
|
||||
- `ignore`: Provider slugs to skip
|
||||
- `quantizations`: Restrict to specific quantization variants (e.g. `["int4","int8"]`)
|
||||
- `sort`: Sort strategy for candidate providers (e.g. `"price"` or `"throughput"`)
|
||||
|
||||
Example adding routing preferences to a model:
|
||||
|
||||
```json
|
||||
{
|
||||
"language_models": {
|
||||
"open_router": {
|
||||
"api_url": "https://openrouter.ai/api/v1",
|
||||
"available_models": [
|
||||
{
|
||||
"name": "openrouter/auto",
|
||||
"display_name": "Auto Router (Tools Preferred)",
|
||||
"max_tokens": 2000000,
|
||||
"supports_tools": true,
|
||||
"provider": {
|
||||
"order": ["anthropic", "openai"],
|
||||
"allow_fallbacks": true,
|
||||
"require_parameters": true,
|
||||
"only": ["anthropic", "openai", "google"],
|
||||
"ignore": ["cohere"],
|
||||
"quantizations": ["int8"],
|
||||
"sort": "price",
|
||||
"data_collection": "allow"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These routing controls let you fine‑tune cost, capability, and reliability trade‑offs without changing the model name you select in the UI.
|
||||
|
||||
### Vercel v0 {#vercel-v0}
|
||||
|
||||
[Vercel v0](https://vercel.com/docs/v0/api) is an expert model for generating full-stack apps, with framework-aware completions optimized for modern stacks like Next.js and Vercel.
|
||||
|
||||
Reference in New Issue
Block a user