| name | provider-development |
| description | LiteLLM-RS Provider 开发指南。用于添加新 provider(Tier 1 catalog 条目或 Tier 2 代码实现)、统一错误处理,或把旧错误枚举迁移到 ProviderError。 |
LiteLLM-RS Provider 开发指南
架构概述
本项目采用统一错误 + 闭集 Provider 枚举派发。LLMProvider
统一各实现的方法签名,但路由部署存放的是具体 Provider 枚举,而不是
dyn LLMProvider trait object。
当前架构层次
┌────────────────────────────────────────────────────────┐
│ 网关层 (Gateway) │
│ LiteLLMError = GatewayError(18 个变体) │
│ - 别名定义: core/types/errors/litellm.rs │
│ - 枚举定义: utils/error/gateway_error/types.rs │
│ - 处理路由、配置、认证等网关级错误 │
└────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────┐
│ Provider 层 │
│ ProviderError(经 unified_provider 模块导出, │
│ 定义于 unified_provider_error.rs) │
│ - 统一 provider 错误,24 个变体 │
│ - 每个变体包含 provider: &'static str 字段 │
│ - 丰富的工厂方法和上下文信息 │
└────────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────────┐
│ 各 Provider 实现(两层结构) │
│ - Tier 1: registry/catalog.rs 目录条目(def_chat 等),│
│ 经 OpenAILikeProvider 路由,无专属代码 │
│ - Tier 2: 代码型 provider 目录, │
│ 实现 LLMProvider 并注册到闭集 Provider 枚举 │
└────────────────────────────────────────────────────────┘
Provider 数量随版本演进,不在此硬编码。枚举方法:
grep -c 'def_chat(' src/core/providers/registry/catalog.rs
grep -c 'def_local_chat(' src/core/providers/registry/catalog.rs
ls -d src/core/providers/*/ | grep -vE '/(base|factory|macros|registry)/'
当前派发契约
Provider 定义在 src/core/providers/mod.rs,由本地 dispatch_provider! 宏把
方法转发给具体实现。宏分别维护 sync、async_err、value 和
async_direct 四类展开臂;Router 的 deployment 持有这个枚举,因此 Tier 2 provider
仅实现 LLMProvider 还不够;还必须添加枚举变体、dispatch/factory 分支及模块
注册。Tier 1 catalog provider 复用现有的 Provider::OpenAILike 变体,所以无需
为每个兼容端点增加枚举成员。
LLMProvider 使用原生 async fn 且没有关联错误类型;所有可失败的方法直接
返回 ProviderError。当前 trait 不是路由层的动态插件边界。真实的 trait object
仅出现在局部边界,例如 Box<dyn ErrorMapper<ProviderError>> 和 boxed streaming
Stream。仓库没有可支持具体纳秒、二进制大小或编译耗时对比的基准,因此本文
不提供这些数字。
统一错误类型详解
ProviderError 变体
pub enum ProviderError {
Authentication { provider, message },
RateLimit { provider, message, retry_after, rpm_limit, tpm_limit, current_usage },
QuotaExceeded { provider, message },
ModelNotFound { provider, model },
InvalidRequest { provider, message },
Network { provider, message },
Timeout { provider, message },
ProviderUnavailable { provider, message },
NotSupported { provider, feature },
NotImplemented { provider, feature },
FeatureDisabled { provider, feature },
ContextLengthExceeded { provider, max, actual },
TokenLimitExceeded { provider, message },
ContentFiltered { provider, reason, policy_violations, potentially_retryable },
Configuration { provider, message },
Serialization { provider, message },
ApiError { provider, status, message },
DeploymentError { provider, deployment, message },
ResponseParsing { provider, message },
RoutingError { provider, attempted_providers, message },
TransformationError { provider, from_format, to_format, message },
Streaming { provider, stream_type, position, last_chunk, message },
Cancelled { provider, operation_type, cancellation_reason },
Other { provider, message },
}
工厂方法使用
ProviderError::authentication("openai", "Invalid API key")
ProviderError::rate_limit("anthropic", Some(60))
ProviderError::model_not_found("groq", "llama-invalid")
ProviderError::network("azure", "Connection timeout")
ProviderError::rate_limit_with_limits("openai", Some(60), Some(100), Some(40000), None)
ProviderError::context_length_exceeded("claude", 100000, 150000)
ProviderError::content_filtered("openai", "Violence detected", Some(vec!["violence"]), Some(false))
ProviderError::streaming_error("fireworks", "chat", Some(42), None, "Connection reset")
添加新 Provider
先判断 Tier
- Tier 1(OpenAI 兼容、无需定制逻辑):只需在
src/core/providers/registry/catalog.rs 加一条 def_chat("name", "Display Name", "https://api.example.com/v1", "NAME_API_KEY"),工厂自动经 OpenAILikeProvider 路由,无需新建目录(本地部署类用 def_local_chat)。
- Tier 2(自定义请求转换、认证签名、非 SSE 流式协议、专属模型元数据等):按下文创建代码目录。
目录结构
src/core/providers/my_provider/
├── mod.rs # 模块导出
├── config.rs # ProviderConfig 实现
├── provider.rs # LLMProvider 实现
├── model_info.rs # 模型定义和能力
└── streaming.rs # SSE 流解析(可选)
配置实现
实现 crate::core::traits::provider::ProviderConfig(定义于 src/core/traits/provider/config.rs,必需方法:validate / api_key / api_base / timeout / max_retries)。参考真实实现:src/core/providers/cloudflare/config.rs。
use crate::core::traits::provider::ProviderConfig;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MyProviderConfig {
pub api_key: Option<String>,
pub api_base: Option<String>,
#[serde(default = "default_timeout")]
pub timeout: u64,
#[serde(default = "default_max_retries")]
pub max_retries: u32,
}
fn default_timeout() -> u64 { 60 }
fn default_max_retries() -> u32 { 3 }
impl Default for MyProviderConfig {
fn default() -> Self {
Self {
api_key: std::env::var("MY_PROVIDER_API_KEY").ok(),
api_base: None,
timeout: default_timeout(),
max_retries: default_max_retries(),
}
}
}
impl ProviderConfig for {
(&) <(), > {
.()
}
(&) <&> { .api_key.() }
(&) <&> { .api_base.() }
(&) std::time::Duration { std::time::Duration::(.timeout) }
(&) { .max_retries }
}
Provider 传输实现(使用统一错误)
LLMProvider trait(src/core/traits/provider/llm_provider/trait_definition.rs)没有关联类型:方法签名直接使用 ProviderError,错误映射通过 get_error_mapper() 提供;trait 方法是原生 async fn,实现时无需 #[async_trait] 宏。下面是完整的传输 helper;chat_completion 还需要把成功 JSON 转成 ChatResponse,可参考 cloudflare/provider.rs。
use super::{get_models, MyProviderConfig};
use crate::core::providers::base::{header, BaseConfig, GlobalPoolManager, HttpMethod};
use crate::core::providers::unified_provider::ProviderError;
use crate::core::traits::provider::ProviderConfig;
use crate::core::types::{chat::ChatRequest, model::ModelInfo};
use serde_json::Value;
use std::sync::Arc;
const PROVIDER_NAME: &str = "my_provider";
#[derive(Debug, Clone)]
pub struct MyProvider {
config: MyProviderConfig,
pool_manager: Arc<GlobalPoolManager>,
models: Vec<ModelInfo>,
}
impl MyProvider {
pub async fn new(config: MyProviderConfig) -> Result<Self, ProviderError> {
config.validate()
.map_err(|e| ProviderError::configuration(PROVIDER_NAME, e))?;
let http_config = BaseConfig {
api_key: config.api_key.clone(),
api_base: config.api_base.clone(),
timeout: config.timeout,
max_retries: config.max_retries,
..BaseConfig::default()
};
let pool_manager = Arc::new(GlobalPoolManager::new_for_provider(
PROVIDER_NAME,
http_config,
)?);
Ok( { config, pool_manager, models: () })
}
(&, request: &ChatRequest) <Value, ProviderError> {
= .config.()
.(|| ProviderError::(PROVIDER_NAME, ))?;
= .config.()
.(|| ProviderError::(PROVIDER_NAME, ))?;
= (, api_base.());
= serde_json::(request)
.(|e| ProviderError::(PROVIDER_NAME, e.()))?;
= [
(, (, api_key)),
(, .()),
];
= .pool_manager
.(&url, HttpMethod::POST, headers, (body))
.?;
= response.();
!status.() {
= response.().
.(|e| ProviderError::(PROVIDER_NAME, e.()))?;
(.(status.(), &response_body));
}
= response.().
.(|e| ProviderError::(PROVIDER_NAME, e.()))?;
serde_json::(&response_body)
.(|e| ProviderError::(PROVIDER_NAME, e.()))
}
(&, status: , body: &) ProviderError {
crate::core::providers::shared::parse_retry_after_from_body;
status {
=> ProviderError::(PROVIDER_NAME, ),
=> ProviderError::(PROVIDER_NAME, body),
=> ProviderError::(PROVIDER_NAME, (body)),
=> ProviderError::(PROVIDER_NAME, body),
..= => ProviderError::(PROVIDER_NAME, body),
_ => ProviderError::(PROVIDER_NAME, status, body),
}
}
}
模型信息
use crate::core::types::model::{ModelInfo, ProviderCapability};
pub fn get_models() -> Vec<ModelInfo> {
vec![
ModelInfo {
id: "my-model-large".to_string(),
name: "My Model Large".to_string(),
provider: "my_provider".to_string(),
max_context_length: 128000,
max_output_length: Some(4096),
supports_streaming: true,
supports_tools: true,
supports_multimodal: false,
input_cost_per_1k_tokens: Some(0.01),
output_cost_per_1k_tokens: Some(0.03),
currency: "USD".to_string(),
capabilities: vec![
ProviderCapability::ChatCompletion,
ProviderCapability::ChatCompletionStream,
ProviderCapability::ToolCalling,
],
..Default::default()
},
]
}
注册 Provider
mod config;
mod model_info;
mod provider;
pub use config::MyProviderConfig;
pub use provider::MyProvider;
pub use model_info::get_models;
Tier 2 provider 接入闭合枚举(无法运行时注册,需以下 crate 内改动,参考 cloudflare 的接线方式):
- 在
src/core/providers/mod.rs 声明模块并给 Provider 增加带相同 feature gate 的变体。
- 把该变体加入
dispatch_provider! 的 sync、async_err、value、async_direct 四个 @expand 臂,并补齐 Provider::name() 和 provider_type() 分支。
- 在
provider_type.rs 增加 ProviderType 变体,并加入 all_non_custom_provider_types();字符串转换由 registry 元数据派生,不要另写一套别名表。
- 在
registry/types.rs 的 PROVIDER_TYPE_REGISTRY 增加 canonical name、aliases、catalog_backed 和正确的 ProviderDispatchKind。
feature-gated 原生实现应复用或新增 cfg-sensitive dispatch-kind helper,分别表达启用与禁用时的模式;registry entry 本身没有 feature 字段。
- 在
factory/builder.rs 增加配置构造器,在 factory/registry.rs 增加工厂 match 分支;module、Provider、dispatch 与 factory wiring 使用同步的 cfg gate。
- 更新 provider-type/registry lifecycle、factory support 与 feature-on/off 测试,确认别名解析、支持状态和构造路径一致。
References