# Provider Development

> LiteLLM-RS Provider 开发指南。用于添加新 provider（Tier 1 catalog 条目或 Tier 2 代码实现）、统一错误处理，或把旧错误枚举迁移到 ProviderError。

- Skill: `majiayu000/provider-development` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add majiayu000/provider-development`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/provider-development/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/majiayu000/provider-development

---


# 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 数量随版本演进，不在此硬编码。枚举方法：

```bash
# 两个计数都会包含各自的 helper 定义，因此分别减 1
grep -c 'def_chat(' src/core/providers/registry/catalog.rs
grep -c 'def_local_chat(' src/core/providers/registry/catalog.rs

# 排除基础设施目录后再人工确认代码型 provider
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 变体

```rust
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 },
}
```

### 工厂方法使用

```rust
// 基础工厂方法
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`。

```rust
// 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 MyProviderConfig {
    fn validate(&self) -> Result<(), String> {
        self.validate_standard("my_provider")
    }

    fn api_key(&self) -> Option<&str> { self.api_key.as_deref() }
    fn api_base(&self) -> Option<&str> { self.api_base.as_deref() }
    fn timeout(&self) -> std::time::Duration { std::time::Duration::from_secs(self.timeout) }
    fn max_retries(&self) -> u32 { self.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`。

```rust
// 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(Self { config, pool_manager, models: get_models() })
    }

    async fn execute_chat_json(&self, request: &ChatRequest) -> Result<Value, ProviderError> {
        let api_key = self.config.api_key()
            .ok_or_else(|| ProviderError::authentication(PROVIDER_NAME, "API key required"))?;
        let api_base = self.config.api_base()
            .ok_or_else(|| ProviderError::configuration(PROVIDER_NAME, "API base required"))?;
        let url = format!("{}/chat/completions", api_base.trim_end_matches('/'));
        let body = serde_json::to_value(request)
            .map_err(|e| ProviderError::serialization(PROVIDER_NAME, e.to_string()))?;

        let headers = vec![
            header("Authorization", format!("Bearer {}", api_key)),
            header("Content-Type", "application/json".to_string()),
        ];

        let response = self.pool_manager
            .execute_request(&url, HttpMethod::POST, headers, Some(body))
            .await?;

        let status = response.status();
        if !status.is_success() {
            let response_body = response.text().await
                .map_err(|e| ProviderError::network(PROVIDER_NAME, e.to_string()))?;
            return Err(self.map_http_error(status.as_u16(), &response_body));
        }

        let response_body = response.bytes().await
            .map_err(|e| ProviderError::network(PROVIDER_NAME, e.to_string()))?;
        serde_json::from_slice(&response_body)
            .map_err(|e| ProviderError::response_parsing(PROVIDER_NAME, e.to_string()))
    }

    fn map_http_error(&self, status: u16, body: &str) -> ProviderError {
        use crate::core::providers::shared::parse_retry_after_from_body;
        match status {
            401 => ProviderError::authentication(PROVIDER_NAME, "Invalid API key"),
            404 => ProviderError::model_not_found(PROVIDER_NAME, body),
            429 => ProviderError::rate_limit(PROVIDER_NAME, parse_retry_after_from_body(body)),
            400 => ProviderError::invalid_request(PROVIDER_NAME, body),
            500..=599 => ProviderError::provider_unavailable(PROVIDER_NAME, body),
            _ => ProviderError::api_error(PROVIDER_NAME, status, body),
        }
    }
}
```

### 模型信息

```rust
// model_info.rs
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

```rust
// src/core/providers/my_provider/mod.rs
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` 的接线方式）：

1. 在 `src/core/providers/mod.rs` 声明模块并给 `Provider` 增加带相同 feature gate 的变体。
2. 把该变体加入 `dispatch_provider!` 的 `sync`、`async_err`、`value`、`async_direct` 四个 `@expand` 臂，并补齐 `Provider::name()` 和 `provider_type()` 分支。
3. 在 `provider_type.rs` 增加 `ProviderType` 变体，并加入 `all_non_custom_provider_types()`；字符串转换由 registry 元数据派生，不要另写一套别名表。
4. 在 `registry/types.rs` 的 `PROVIDER_TYPE_REGISTRY` 增加 canonical name、aliases、`catalog_backed` 和正确的 `ProviderDispatchKind`。
   feature-gated 原生实现应复用或新增 cfg-sensitive dispatch-kind helper，分别表达启用与禁用时的模式；registry entry 本身没有 feature 字段。
5. 在 `factory/builder.rs` 增加配置构造器，在 `factory/registry.rs` 增加工厂 match 分支；module、`Provider`、dispatch 与 factory wiring 使用同步的 cfg gate。
6. 更新 provider-type/registry lifecycle、factory support 与 feature-on/off 测试，确认别名解析、支持状态和构造路径一致。

---

## References

- [reference/migration-and-checklists.md](reference/migration-and-checklists.md) — 迁移现有 provider 到统一错误的步骤、错误映射对照表与迁移检查清单
- [reference/industry-notes-and-faq.md](reference/industry-notes-and-faq.md) — 行业架构选择参考与常见问题解答

