AI 学习笔记(七):多 Provider 路由与降级服务层,做一套可扩展的模型调度
上一篇我们把 OpenAI、Claude、DeepSeek 的接口差异梳理清楚了。
但项目真要往线上走,问题很快就会从“能不能调通”变成:
- 默认先走哪个 provider
- 失败后要不要切下一个 provider
- 同一个会话能不能中途换家
- 429、503、529 这类错误该冷却多久
- 每次路由决策怎样记录,排障时才能回放
所以这篇不再讲 Adapter,而是讲 多 Provider 路由与降级服务层。
它的目标不是“随机换一家模型”,而是把 能力选择、会话状态、失败处理和观测 收口成一层可配置策略。
1. 路由层真正要解决的,不只是 fallback
如果你的系统里已经有统一的 generateText、generateObject、callTools 这类接口,路由层至少要再解决 4 件事:
- 能力匹配
不同任务走不同 provider 顺序,而不是所有请求都套同一个列表。 - 会话亲和性
多轮对话、工具调用、长上下文任务,不能简单把每一轮都当成独立请求。 - 失败分级
auth_error、validation_error这种要立即失败;rate_limit、overloaded、server_error才适合降级或冷却后重试。 - 可观测
你需要知道“这次为什么走了 Claude 而不是 OpenAI”,“为什么 DeepSeek 被摘掉 30 秒”,“最终到底尝试了哪几家”。
一句话总结:
Adapter 解决“怎么接”,Router 解决“什么时候选谁、什么时候停”。
2. 路由前先想清楚:会话不能随便跳 Provider
这是多 provider 系统里最容易被忽略的一点。
OpenAI 的 Responses API 可以通过 previous_response_id 继续上一轮响应链路,这意味着一部分会话状态可以由平台帮你维护。
而 DeepSeek 官方文档明确说明它的多轮对话接口本质上是无状态的,需要你自己把历史消息继续带上。
Claude 这边同样需要你显式传递消息,另外它的 Prompt Caching 是否生效,也依赖你是否保持稳定的提示词前缀。
这会直接影响路由策略:
| 场景 | 更稳的做法 |
|---|---|
| 单次文本生成 | 按任务类型配置 provider 顺序,顺序降级即可 |
| 多轮对话 | 开启 session affinity,优先固定在同一 provider |
| 重复长前缀任务 | 尽量保持统一前缀,别频繁跨 provider,避免缓存收益丢失 |
| 工具调用且有副作用 | 把“模型规划”与“真实执行”拆开,重试边界必须清楚 |
如果你的业务侧只存了 OpenAI 的 previous_response_id,然后下一轮突然切去 Claude 或 DeepSeek,会话上下文其实已经断了。
所以更稳的方式通常是:
- 路由层只负责“选 provider”
- 会话层维护一份规范化历史记录
- OpenAI 可以额外利用
previous_response_id - Claude / DeepSeek 继续走“显式带历史”的模式
这样你在需要跨 provider 时,至少还有能力把历史物化后重新发出去,而不是完全绑死在某一家平台的状态模型里。
3. 先做一份策略配置,不要把路由写死在业务里
我更推荐把策略拆成两块:
providerRegistry
描述每家 provider 支持什么、默认冷却多久、会话模式是什么。routerPolicy
描述每类任务优先顺序、是否需要会话亲和性、最多尝试几家。
最小示例如下:
1 | export const providerRegistry = { |
这里的 provider 顺序只是示例,不是标准答案。
真正该怎么排,取决于你项目更看重哪一项:
- 稳定优先
- 成本优先
- 结构化输出优先
- 工具调用优先
- 国内可用性优先
重点不是“排序本身”,而是**让排序变成配置,而不是散落在业务代码里的 if/else**。
4. 先统一失败语义,再做降级
很多人做 fallback 时只写一句“失败了就换下一家”,这通常不够。
更合理的做法是先把 provider 错误收敛成统一语义,例如:
auth_errorvalidation_errorunsupported_capabilityrate_limittimeoutnetwork_errorserver_erroroverloaded
然后再定义动作:
| 错误类型 | 建议动作 |
|---|---|
auth_error / validation_error / unsupported_capability |
立即失败,不要继续切换 |
rate_limit |
进入冷却,切下一个 provider |
timeout / network_error / server_error |
可切下一个 provider,必要时带短冷却 |
overloaded |
明确冷却后跳过一段时间 |
为什么要做得这么细?
因为三家的“暂时失败”信号并不完全一样:
- OpenAI 文档里有
x-request-id,也有一组x-ratelimit-*头,适合做请求追踪和重置时间判断。 - Anthropic 文档里有
request-id、retry-after和anthropic-ratelimit-*头,另外529表示平台过载。 - DeepSeek 的错误码文档列出了
429和503,并明确建议请求过快或服务繁忙时放慢节奏,必要时临时切换其他 provider。
所以更稳的 Adapter 不只是返回文本,还应该把失败统一包装成类似下面这种结构:
1 | { |
这样 Router 才能根据 error.type 和 error.meta.headers 决定是:
- 立即失败
- 冷却后跳过当前 provider
- 切下一个 provider
- 还是把错误原样抛给上层
5. 一个可直接复用的最小 Router 示例
下面这个版本重点展示 4 件事:
- 按任务类型决定候选 provider
- 支持 session affinity
- 根据错误类型决定是否降级
- 根据头信息设置 provider 冷却时间
1 | function parseCooldownMs(headers = {}, fallbackMs = 15_000) { |
这段代码刻意只做“够用的最小版本”,但已经具备几个很重要的工程特征:
- Router 不需要知道三家 SDK 的细节,只吃 Adapter 归一化后的结果。
- 会话亲和性是按任务类型开启的,不会无脑全局粘连。
- 429 / 503 / 529 不再只是报错,而会进入冷却窗口。
- 每次成功或失败都会带上
traceId、provider、requestId、tried,后面接日志和指标会轻很多。
6. 真上项目时,别漏掉这 4 个细节
6.1 会话亲和性和“历史物化”
如果一个对话线程已经绑定到 OpenAI,并且你还利用了 previous_response_id,那就不要在下一轮无脑切去别家。
更稳的做法是:
- 平时保存规范化历史
- 当前 provider 正常时走它自己的最优路径
- 必须切家时,把历史物化后重新发给新的 provider
否则你以为自己做了 fallback,实际上只是悄悄丢了上下文。
6.2 工具调用的重试边界
工具调用和纯文本生成最大的区别在于:它可能带副作用。
比如模型已经规划出“下单”“发邮件”“写数据库”这类动作时,Router 不能简单把失败重放成第二次业务执行。
更稳的做法通常是:
- 模型规划阶段可重试
- 真实执行阶段要带
operationId/ 幂等键 - 执行结果再回填给模型
这样降级的是“生成过程”,不是把业务动作重复做一遍。
6.3 观测维度别只记 provider
路由排障至少要有下面这些字段:
traceIdtaskTypeprovidermodelrouteReasontriedrequestIddurationMserrorType
OpenAI 和 Anthropic 都提供了请求 ID;DeepSeek 这边即使没有完全一致的返回头,你也应该自己生成统一的 traceId。
否则问题一上量,你只会知道“失败了”,不知道“为什么这次失败前已经绕了两家”。
6.4 熔断先做简单版就够了
很多系统一上来就想做权重路由、实时评分、成本优化。
但大多数团队更先需要的是:
- 顺序降级
- provider 冷却
- 会话亲和性
- 统一日志
先把这 4 件事做好,系统稳定性通常已经能上一个台阶。
真正跑出流量后,再考虑:
- 动态权重
- 区域路由
- 成本感知调度
- A/B 与灰度
7. 一条更稳的演进路径
如果你准备从“多 provider 接入”继续升级,推荐按这个顺序推进:
- 先做顺序 fallback
- 再做错误分类和冷却窗口
- 再做 session affinity
- 再做能力感知路由
- 最后再做权重、成本和实验流量
这个顺序的好处是:每一步都能独立带来稳定性收益,而且不会过早把系统做复杂。
总结
从“我能同时接 OpenAI、Claude、DeepSeek”走到“我能稳定上线”,真正关键的一步就是补上一层路由与降级服务层。
这层最重要的不是算法有多花哨,而是你有没有把下面几件事做对:
- 任务类型和 provider 能力匹配
- 会话不要随便跳 provider
- 错误先归一化,再决定是否降级
- 冷却和观测要进系统,不要靠人工猜
最值得记住的一句话是:
业务接口保持稳定,provider 可以替换;路由策略必须可配置,失败路径必须可观测。
下一篇进入 Phase 3:MCP 与 AI Agent 工作流的最小实践,把“单次模型调用”继续往“可组合工具链”推进。
参考资料
- OpenAI API Overview
- OpenAI Conversation State
- Anthropic API Overview
- Anthropic Errors
- Anthropic Rate Limits
- Anthropic Tool Use Overview
- Anthropic Prompt Caching
- DeepSeek First API Call
- DeepSeek Multi-round Conversation
- DeepSeek Context Caching
- DeepSeek Function Calling
- DeepSeek Error Codes
本文永久链接: https://www.mulianju.com/learning-notes/ai-learning-notes-provider-router-fallback-service-layer/