多 LLM 提供商集成——17+ 模型的智能路由艺术

📑 目录

IronClaw 深度剖析(七):多 LLM 提供商集成——17+ 模型的智能路由艺术

在 LLM 百花齐放的 2025-2026 年,一个严肃的 Agent OS 该如何面对"选择困难症"?IronClaw 的答案是:全都要。通过 rig-core v0.30 的统一抽象、五层装饰器链的弹性架构,以及运行时热切换能力,IronClaw 将 17+ 个 LLM 提供商整合为一个无缝的"模型网格"。本文将深入剖析这套多提供商集成架构的每一层设计。


一、Multi-Provider 架构全景:一个工厂,17 个入口

IronClaw 的 LLM 集成并非简单的" if-else 堆叠",而是基于统一的 trait 抽象 + 工厂模式 + 协议注册表的三层架构。所有提供商无论底层协议差异多大,最终都通过同一个 Arc<dyn LlmProvider> 接口对外服务。

graph TB
    subgraph "ProviderProtocol 注册表"
        P1["OpenAiCompletions"]
        P2["Anthropic"]
        P3["Ollama"]
        P4["DeepSeek"]
        P5["Gemini"]
        P6["OpenRouter"]
        P7["GithubCopilot"]
        P8["Bedrock"]
        P9["OpenAiCodex"]
        P10["GeminiOauth"]
        P11["NearAi"]
    end

    subgraph "统一抽象层"
        ADAPTER["RigAdapter<M: CompletionModel>"]
        TRAIT["LlmProvider trait"]
    end

    subgraph "装饰器链"
        D1["RetryProvider"]
        D2["SmartRoutingProvider"]
        D3["FailoverProvider"]
        D4["CircuitBreakerProvider"]
        D5["CachedProvider"]
        D6["SwappableLlmProvider"]
    end

    P1 & P2 & P3 & P4 & P5 & P6 & P7 & P8 & P9 & P10 & P11 --> ADAPTER
    ADAPTER --> TRAIT
    TRAIT --> D1 --> D2 --> D3 --> D4 --> D5 --> D6

    style TRAIT fill:#e3f2fd,stroke:#1565c0
    style D2 fill:#fff3e0,stroke:#ef6c00
    style D6 fill:#e8f5e9,stroke:#2e7d32

1.1 支持的 17+ 提供商一览

#提供商认证方式底层协议特殊能力
1NEAR AIOAuth (浏览器) / API KeyCustom Chat Completions会话令牌自动续期
2AnthropicANTHROPIC_API_KEYAnthropic NativePrompt Caching、Extended Thinking
3OpenAIOPENAI_API_KEYOpenAI Completions函数调用、结构化输出
4Google GeminiOAuth (浏览器)Gemini NativeCloud Code API、SSE 流式
5Gemini API KeyGEMINI_API_KEYGemini Native原生多模态
6GitHub CopilotGITHUB_COPILOT_TOKENToken ExchangeVS Code 身份头
7Ollama无需认证Ollama Native本地推理、隐私优先
8AWS BedrockAWS 凭证AWS Converse API跨区域路由、企业合规
9DeepSeekAPI KeyDeepSeek Nativereasoning_content 保留
10OpenRouterAPI KeyOpenRouter Nativereasoning_details 透传
11io.netIONET_API_KEYOpenAI-compatible分布式推理
12MistralMISTRAL_API_KEYOpenAI-compatible工具调用 ID 规范化
13Yandex AIYANDEX_API_KEYOpenAI-compatible俄语优化
14MiniMaxMINIMAX_API_KEYOpenAI-compatible中文场景优化
15CloudflareCLOUDFLARE_API_KEYOpenAI-compatible边缘推理
16OpenAI CodexOAuth (设备码)Responses API设备码流程
17OpenAI-Compatible多种OpenAI Completions通用适配

这 17 个提供商覆盖了从本地隐私推理(Ollama)到企业级云托管(AWS Bedrock)、从OAuth 浏览器登录(NEAR AI、Gemini)到设备码认证(OpenAI Codex)的全谱系场景。


二、rig-core 抽象层:RigAdapter 桥接设计

IronClaw 选择 rig-core v0.30 作为底层 LLM 抽象库,但并非直接使用——而是在其上构建了一层双向类型转换桥接,这就是 RigAdapter

2.1 为什么需要 RigAdapter?

git-core 提供了统一的 CompletionModel trait,但 IronClaw 需要额外的能力:

  1. 成本追踪 —— 每次请求需要记录输入/输出 token 成本
  2. 缓存控制 —— Anthropic 的 Prompt Caching 需要注入 cache_control 参数
  3. 工具调用 ID 规范化 —— Mistral 要求 9 字符字母数字 ID
  4. 推理内容回传 —— DeepSeek 的 reasoning_content 必须原样回传,否则 API 返回 400
  5. 热切换支持 —— 运行时更换模型无需重启

2.2 RigAdapter 核心结构

/// 桥接 rig-core CompletionModel 与 IronClaw LlmProvider trait 的适配器
pub struct RigAdapter<M: CompletionModel> {
    model: M,                                    // rig-core 模型实例
    model_name: String,                          // 模型标识(如 "claude-sonnet-4-20250514")
    input_cost: Decimal,                         // 每 1k 输入 token 成本
    output_cost: Decimal,                        // 每 1k 输出 token 成本
    cache_retention: CacheRetention,             // Anthropic 提示缓存策略
    unsupported_params: HashSet<String>,         // 需要剥离的不支持参数
    default_additional_params: Option<Value>,    // 提供商特有默认参数
}

RigAdapter 的核心职责是类型双向转换

IronClaw ChatMessage ──convert_messages()──> rig-core Message
IronClaw ToolDefinition ──convert_tools()──> rig-core ToolDefinition
IronClaw ToolChoice ──convert_tool_choice()──> rig-core ToolChoice

rig-core AssistantContent ──extract_response()──> IronClaw CompletionResponse / ToolCall

2.3 Reasoning Content 回传:一个关键设计挑战

DeepSeek、Gemini 2.5+ 和 OpenRouter 都要求推理内容必须在多轮对话中原样回传,否则 API 会直接拒绝。RigAdapterextract_response() 中特别处理了这一点:

for content in choice.iter() {
    match content {
        AssistantContent::ToolCall(tc) => {
            tool_calls.push(IronToolCall {
                id: tc.id.clone(),
                name: tc.function.name.clone(),
                arguments: tc.function.arguments.clone(),
                signature: tc.signature.clone(),  // #3225: 保留 thought_signature
                reasoning: None,
            });
        }
        AssistantContent::Reasoning(r) => {
            reasoning_parts.push(r.reasoning.join("\n"));
        }
        // ...
    }
}

这里的 signature 字段(Gemini 的 thought_signature)和 reasoning 字段(DeepSeek 的 reasoning_content)是 IronClaw 在标准 rig-core 类型之上扩展的字段,确保推理链的完整性跨越多轮对话。


三、LlmProvider Trait:统一的提供者契约

所有 LLM 提供商——无论是通过 RigAdapter 桥接的还是原生实现的——都必须实现 LlmProvider trait:

#[async_trait]
pub trait LlmProvider: Send + Sync {
    /// 获取当前模型名称
    fn model_name(&self) -> &str;

    /// 获取每 token 成本(输入成本, 输出成本)
    fn cost_per_token(&self) -> (Decimal, Decimal);

    /// 普通对话补全
    async fn complete(&self, request: CompletionRequest)
        -> Result<CompletionResponse, LlmError>;

    /// 带工具调用的对话补全
    async fn complete_with_tools(&self, request: ToolCompletionRequest)
        -> Result<ToolCompletionResponse, LlmError>;

    /// 列出可用模型(默认返回空列表)
    async fn list_models(&self) -> Result<Vec<String>, LlmError>;

    /// 运行时切换模型(默认不支持)
    fn set_model(&self, _model: &str) -> Result<(), LlmError>;

    /// 成本计算辅助方法
    fn calculate_cost(&self, input_tokens: u32, output_tokens: u32) -> Decimal;
    
    /// Anthropic 缓存写入倍率:Short=1.25x, Long=2.0x
    fn cache_write_multiplier(&self) -> Decimal;
    
    /// Anthropic 缓存读取折扣:1/10 输入成本(90% 折扣)
    fn cache_read_discount(&self) -> Decimal;
}

这个 trait 设计的精妙之处在于分层职责

  • 基础能力complete / complete_with_tools 是必需的核心方法
  • 成本透明cost_per_token / calculate_cost 让每一次 LLM 调用都可计费、可追踪
  • 运维友好set_model 支持运行时热切换,list_models 支持动态发现
  • 缓存感知cache_write_multiplier / cache_read_discount 让上层可以精确计算缓存经济学

四、五层装饰器链:核心创新

IronClaw 的 LLM 集成最亮眼的设计是五层装饰器链。每一个装饰器都是一个独立的 LlmProvider 实现,通过 Arc<dyn LlmProvider> 包裹内层提供者,形成洋葱式的责任链。

graph LR
    subgraph "装饰器链(从外到内)"
        direction TB
        OUT["外部调用者"]
        R["RetryProvider
指数退避重试"] S["SmartRoutingProvider
轻量任务路由到廉价模型"] F["FailoverProvider
主模型失败自动降级"] C["CircuitBreakerProvider
后端降级快速失败"] CA["CachedProvider
响应缓存"] SW["SwappableLlmProvider
运行时热切换"] REC["RecordingLlm
请求录制回放"] RAW["Raw Provider
原始提供商"] end OUT --> R --> S --> F --> C --> CA --> SW --> REC --> RAW style S fill:#fff3e0,stroke:#ef6c00 style F fill:#fce4ec,stroke:#c62828 style C fill:#ffebee,stroke:#b71c1c style SW fill:#e8f5e9,stroke:#2e7d32

4.1 RetryProvider:弹性重试

pub struct RetryProvider {
    inner: Arc<dyn LlmProvider>,
    config: RetryConfig { max_retries: u32 },
}

// 指数退避 + 抖动:
// 第 0 次: ~1s (0.75s - 1.25s)
// 第 1 次: ~2s (1.5s - 2.5s)
// 第 2 次: ~4s (3.0s - 5.0s)
pub(crate) fn retry_backoff_delay(attempt: u32) -> Duration {
    let base_ms = 1000u64 * 2u64.pow(attempt);
    let jitter = rand::thread_rng()
        .gen_range(0..=jitter_range * 2) as i64 
        - jitter_range as i64;
    Duration::from_millis((base_ms as i64 + jitter).max(100) as u64)
}

RetryProvider 的智能之处在于错误分类:只有"可重试错误"才会触发退避,认证失败、上下文超长等错误会直接短路:

pub fn is_retryable(err: &LlmError) -> bool {
    matches!(err,
        LlmError::RequestFailed { .. }      // 瞬时网络问题
        | LlmError::RateLimited { .. }      // 限流(带 Retry-After)
        | LlmError::BadGateway { .. }       // 5xx 服务端错误
        | LlmError::InvalidResponse { .. }  // 解析失败
        | LlmError::SessionRenewalFailed { .. }
        | LlmError::Http(_) | LlmError::Io(_)
    )
}

4.2 SmartRoutingProvider:智能路由

心跳检测、简单评估等轻量任务自动路由到廉价模型(如 Claude Haiku),复杂任务路由到主力模型(如 Claude Opus),在不影响质量的前提下显著降低成本。

pub struct SmartRoutingProvider {
    primary: Arc<dyn LlmProvider>,   // 主力模型(昂贵但能力强)
    cheap: Arc<dyn LlmProvider>,     // 廉价模型(轻量任务)
    config: SmartRoutingConfig,
}

pub enum TaskComplexity {
    Simple,     // 路由到廉价模型
    Complex,    // 路由到主力模型
}

4.3 FailoverProvider:故障转移

当主力模型连续失败达到一定阈值时,自动切换到备用模型,并进入冷却期避免对已经过载的后端继续施压:

pub struct FailoverProvider {
    providers: Vec<Arc<dyn LlmProvider>>,
    cooldown: CooldownConfig,
}

pub struct CooldownConfig {
    pub cooldown_duration: Duration,   // 例如 300 秒
    pub failure_threshold: u32,        // 例如连续 3 次失败才触发冷却
}

4.4 CircuitBreakerProvider:熔断保护

当后端服务健康度恶化时,快速失败而不是让每个请求都等到超时。三态状态机实现:

  • Closed(闭合):正常服务,请求直通
  • Open(断开):失败次数超过阈值,请求立即失败
  • HalfOpen(半开):冷却期满后,允许探测请求通过验证后端恢复
pub struct CircuitBreakerProvider {
    inner: Arc<dyn LlmProvider>,
    config: CircuitBreakerConfig,
    state: AtomicCircuitState,  // Closed → Open → HalfOpen
}

4.5 CachedProvider & SwappableLlmProvider:缓存与热切换

CachedProvider 提供带 TTL 的内存响应缓存,适合重复性查询场景;SwappableLlmProvider 则是整个链条的"门面",通过 RwLock<Arc<dyn LlmProvider>> 实现原子级运行时替换,调用方持有的 Arc 引用无需更新。

4.6 装饰器链组装代码

pub async fn build_provider_chain(
    config: &LlmConfig,
    session: Arc<SessionManager>,
) -> Result<(Arc<dyn LlmProvider>, Option<Arc<dyn LlmProvider>>, 
             Option<Arc<RecordingLlm>>, Arc<LlmReloadHandle>), LlmError> 
{
    // 1. 构建原始提供商组件
    let components = build_provider_chain_components(config, session).await?;

    // 2. 包裹为可热切换的提供者
    let primary_swappable = Arc::new(SwappableLlmProvider::new(components.primary));
    let cheap_swappable = components.cheap
        .map(|c| Arc::new(SwappableLlmProvider::new(c)));

    // 3. 构建重载句柄(用于运行时重新加载配置)
    let reload_handle = Arc::new(LlmReloadHandle::new(
        Arc::clone(&primary_swappable),
        cheap_swappable.clone(),
    ));

    // 4. 可选:包裹录制层(用于测试回放)
    let recording_handle = RecordingLlm::from_env(primary.clone());
    let primary = if let Some(ref recorder) = recording_handle {
        Arc::clone(recorder) as Arc<dyn LlmProvider>
    } else { primary };

    Ok((primary, cheap, recording_handle, reload_handle))
}

五、流式响应处理:SSE 实时推送

对于交互式场景,IronClaw 通过 eventsource-stream 库实现 Server-Sent Events (SSE) 流式解析,将字节流转换为结构化事件流。

sequenceDiagram
    participant U as 用户/TUI
    participant IC as IronClaw Client
    participant ES as EventSource
    participant NA as NEAR AI Server

    U->>IC: 发送对话请求
    IC->>NA: POST /chat/completions (stream: true)
    NA-->>IC: HTTP 200 + SSE 流
    
    loop SSE 事件循环
        NA-->>ES: data: {"choices":[{"delta":{"content":"Hello"}}]}
        ES-->>IC: Event { data, event_type }
        IC-->>U: 实时渲染 "Hello"
        
        NA-->>ES: data: {"choices":[{"delta":{"content":" world"}}]}
        ES-->>IC: Event { data, event_type }
        IC-->>U: 实时渲染 " world"
    end

    NA-->>ES: data: [DONE]
    ES-->>IC: Stream 结束
    IC-->>U: 最终响应 + Token 统计

5.1 SSE 错误处理与重试

流式传输中的错误被精细分类为"可重试"和"不可重试"两类。对于限流响应,IronClaw 实现了完整的 Retry-After 头部解析(支持 RFC 7231 的 delay-seconds 和 HTTP-date 两种格式):

/// 解析 Retry-After 头部,支持两种格式:
/// - 延迟秒数:"120"(RFC 7231 delay-seconds)
/// - HTTP 日期:"Wed, 21 Oct 2026 07:28:00 GMT"
pub fn parse_retry_after(header: Option<&HeaderValue>) -> Duration {
    header
        .map(parse_retry_after_value)
        .unwrap_or(Duration::from_secs(60))  // 默认 60 秒
}

pub fn parse_retry_after_value(header: &HeaderValue) -> Duration {
    header.to_str().ok().and_then(|v| {
        // 格式 1:纯数字秒数
        if let Ok(secs) = v.trim().parse::<u64>() {
            return Some(cap_retry_after(Duration::from_secs(secs)));
        }
        // 格式 2:RFC 2822 日期格式
        if let Ok(dt) = chrono::DateTime::parse_from_rfc2822(v.trim()) {
            let delta = dt.signed_duration_since(chrono::Utc::now());
            return Some(cap_retry_after(
                Duration::from_secs(delta.num_seconds().max(0) as u64)
            ));
        }
        None
    }).unwrap_or(Duration::from_secs(60))
}

六、Embedding 与向量生成

IronClaw 使用 pgvector 扩展将向量嵌入存储在 PostgreSQL 中,通过 ironclaw_memory crate 管理语义搜索。这种设计选择体现了"数据主权"哲学——向量数据同样不离开用户的 PostgreSQL 实例

# PostgreSQL 向量支持(特性开关控制)
pgvector = { version = "0.4", features = ["postgres"], optional = true }

嵌入生成通过 LLM 提供商的 embedding API 完成,向量存储和相似性搜索则由 ironclaw_memory crate 统一管理,实现了从文本到向量的端到端管道。


七、Token 管理与计费:每一分钱都透明

7.1 成本模型

IronClaw 在 costs.rs 中维护了每模型的输入/输出成本表(每 1k tokens):

graph LR
    subgraph "Anthropic 提示缓存经济学"
        A["正常输入
$3.0 / 1M tokens"] B["缓存写入 Short
1.25x = $3.75"] C["缓存写入 Long
2.0x = $6.0"] D["缓存读取
0.1x = $0.3
90% 折扣!"] end A --> B A --> C A --> D style D fill:#e8f5e9,stroke:#2e7d32 style B fill:#fff3e0,stroke:#ef6c00 style C fill:#fce4ec,stroke:#c62828

7.2 Anthropic 提示缓存策略

pub enum CacheRetention {
    None,   // 不缓存,无额外费用
    Short,  // 5 分钟 TTL,写入费用 1.25x
    Long,   // 1 小时 TTL,写入费用 2.0x
}

缓存的精妙之处在于读取的经济性:缓存写入虽然需要支付 1.25x-2.0x 的溢价,但缓存命中的读取成本仅为正常输入的 1/10(90% 折扣)。对于多轮对话场景——系统提示被反复读取——这是一笔极其划算的买卖。

7.3 主流模型成本对比

提供商模型输入 ($/1M)输出 ($/1M)缓存读取折扣特色
Anthropicclaude-opus-415.0075.0090%最强推理
Anthropicclaude-sonnet-43.0015.0090%均衡之选
Anthropicclaude-haiku0.251.2590%轻量快速
OpenAIgpt-4.12.008.00-函数调用强
OpenAIgpt-4.1-mini0.401.60-性价比高
DeepSeekdeepseek-chat0.271.10-推理链保留
Googlegemini-2.5-pro1.2510.00-多模态原生

7.4 Session 令牌管理

NEAR AI 的会话令牌由 SessionManager 统一管理,实现了四级持久化策略(按安全性从高到低):

  1. 加密密钥库attach_secrets())—— 最安全
  2. 数据库设置表attach_store()
  3. 磁盘文件~/.ironclaw/session.json,权限 0o600
  4. 环境变量NEARAI_SESSION_TOKEN
async fn ensure_authenticated(&self) -> Result<(), LlmError> {
    if !self.has_token().await {
        return self.run_renewer().await;  // 触发交互式 OAuth
    }
    match self.validate_token().await {
        Ok(()) => Ok(()),
        Err(e) => {
            tracing::info!("Session expired: {}", e);
            self.run_renewer().await  // 401 时自动续期
        }
    }
}

八、OAuth 认证集成:一个端口,多种流程

ironclaw_oauth crate 提供了共享的 OAuth 回调基础设施,所有需要浏览器认证的提供商共享同一个本地回调服务器(端口 9876)。

sequenceDiagram
    participant U as 用户
    participant IC as IronClaw
    participant OL as OAuth Listener:9876
    participant NA as NEAR AI / Gemini

    U->>IC: 发起登录请求
    IC->>IC: 生成 PKCE code_verifier + state (CSRF)
    IC->>U: 打开浏览器授权 URL
    U->>NA: 登录并授权
    NA-->>OL: 重定向到 http://127.0.0.1:9876/callback?code=...&state=...
    OL->>OL: 验证 state 防止 CSRF
    OL->>NA: POST 交换 code → access_token
    NA-->>OL: 返回令牌
    OL-->>IC: 令牌存入 SessionManager
    IC-->>U: 显示成功页面

8.1 安全特性

pub const OAUTH_CALLBACK_PORT: u16 = 9876;

pub async fn bind_callback_listener() -> Result<TcpListener, OAuthCallbackError> {
    let host = callback_host();  // 默认 127.0.0.1

    // 安全防御 1:拒绝通配符地址
    if is_wildcard_host(&host) {
        return Err(OAuthCallbackError::Io(
            "Wildcard address would expose session token".into()
        ));
    }

    // 安全防御 2:回环地址优先 IPv4,回退 IPv6
    if is_loopback_host(&host) {
        match TcpListener::bind(format!("127.0.0.1:{}", OAUTH_CALLBACK_PORT)).await {
            Ok(listener) => return Ok(listener),
            Err(_) => {} // IPv4 不可用,尝试 IPv6
        }
        TcpListener::bind(format!("[::1]:{}", OAUTH_CALLBACK_PORT)).await
    } else {
        // 远程模式:绑定到指定地址
        TcpListener::bind(format!("{}:{}", host, OAUTH_CALLBACK_PORT)).await
    }
}

OAuth 回调服务器的安全设计包含多层防护:

安全层实现目的
CSRF 防护state 参数验证防止跨站请求伪造攻击
地址绑定拒绝 0.0.0.0 / :: 通配符防止令牌暴露给网络
回环限制默认 127.0.0.1仅本地进程可访问
超时保护5 分钟超时防止僵尸等待
品牌页面成功/失败 HTML 页面用户体验 + 视觉确认

8.2 三种 OAuth 流程支持

  • NEAR AI 浏览器 OAuth:标准 PKCE 流程,会话令牌持久化到 ~/.ironclaw/session.json
  • Gemini PKCE OAuth:Google OAuth + S256,凭证保存到 ~/.gemini/oauth_creds.json
  • GitHub Copilot 设备码:设备码流程 + VS Code 身份头模拟

九、总结:设计哲学的统一体现

IronClaw 的多 LLM 提供商集成架构是其核心设计哲学的完美体现:

设计原则架构体现
隐私优先Ollama 本地推理 + 密钥通过 secrecy::SecretString 管理
透明可审计每请求成本追踪 + Token 使用明细
自扩展能力通过 ProviderProtocol 枚举和注册表模式,新增提供商只需实现 LlmProvider trait
纵深防御OAuth CSRF 防护 + 熔断保护 + 指数退避 + 工具消息消毒

五层装饰器链是整个设计的点睛之笔——它不是过度工程,而是运维经验的代码化。 anyone who has operated LLM applications in production knows: 模型会宕机、会限流、会变慢,而 IronClaw 的装饰器链让这一切都有章可循、有码可防。

下一篇预告:《IronClaw 深度剖析(八):Agent 记忆系统——从短期对话到长期知识沉淀》,我们将深入 ironclaw_memory crate,探索 IronClaw 如何在本地 PostgreSQL 中构建语义记忆网络。