
字节笔记本
2026年10月24日 · 约 14 分钟读完
主模型挂了怎么办:模型降级链

本文是《从 0 写 Agent》系列第 23 章。这个系列基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版),用 Go 从零实现一个生产级 Agent 运行时;本章讲「Fallback:模型降级链」。
Retry 解决"临时故障",等一下再试就好。但如果模型彻底挂了(API key 过期、服务下线、区域故障),retry 多少次都没用。
Fallback 解决"彻底不可用",主模型失败,自动切到备用模型。agents-go 的
NewFallbackModel依次尝试一个模型链,第一个成功就返回。这一章拆它,以及和 Retry 的最佳组合。
引子:主模型彻底挂了
场景:
- 你的主模型是 OpenAI GPT-5
- OpenAI 突然区域故障(真发生过)
- Retry 重试 5 次都失败
- Agent 完全不可用
但如果你有备用模型(Anthropic Claude、本地模型),应该自动切过去。 这就是 Fallback,主模型挂了,用备用的顶上,保证服务不中断。

源码对照:FallbackModel
NewFallbackModel(model_fallback.go:27)创建一个降级链:
// agents/model_fallback.go:27
func NewFallbackModel(primary Model, fallbacks ...Model) Model {
models := append([]Model{primary}, fallbacks...)
return &fallbackModel{models: models, shouldFallback: DefaultRetryIf}
}主模型 + 任意多个备用,组成一个链。 返回的还是 Model 接口,装饰器模式。
阻塞版的逻辑
// agents/model_fallback.go:34(骨架)
func (m *fallbackModel) GetResponse(ctx, req) (*ModelResponse, error) {
var errs []error
for _, inner := range m.models {
resp, err := inner.GetResponse(ctx, req)
if err == nil {
return resp, nil // 成功 → 返回
}
errs = append(errs, err)
if !m.shouldFallback(err) {
break // context 取消等错误,不降级
}
// 否则继续试下一个
}
return nil, errors.Join(errs...) // 全失败 → 合并所有错误
}逻辑极其简单,依次试,第一个成功就返回,全失败返回合并错误。 三个细节:
1. errors.Join 合并所有错误
return nil, errors.Join(errs...)不是返回最后一个错误,而是合并所有模型的错误。 为什么?因为调试时,你要知道"主模型为什么失败、备用为什么也失败",合并的错误信息最全。Go 1.20 的 errors.Join 正是为这种场景设计的。
2. context 取消不降级
if !m.shouldFallback(err) {
break // DefaultRetryIf:context 取消不重试/降级
}如果错误是 context 取消(调用方放弃了),立即停止,不试备用。 因为调用方都不要结果了,试备用是浪费。
3. 流式版的"首 event 前"约束
和 Retry 一样,流式 Fallback 也受"首 event 前"约束(model_fallback.go:69):
if producedAny || i == len(m.models)-1 || !m.shouldFallback(streamErr) {
yield(nil, errors.Join(errs...))
return // 吐过 token 了,不降级
}一旦某个模型吐过 token,就"锁定"它,不切备用。 原因相同,已经吐给用户的内容改不回来,切备用会让内容跳变。
设计权衡:Fallback 和 Retry 的最佳组合
注释给了明确建议(model_fallback.go:21):
Wrap each backend in NewRetryModel first so every provider exhausts its own retries before the chain moves on.
翻译,先给每个模型包 Retry,再组 Fallback。 让每个 provider 先耗尽自己的重试,再降级:
// 推荐组合
model := agents.NewFallbackModel(
agents.NewRetryModel(openaiModel, retryPolicy), // OpenAI 先重试 5 次
agents.NewRetryModel(anthropicModel, retryPolicy), // 还失败 → Claude 重试 5 次
agents.NewRetryModel(localModel, retryPolicy), // 还失败 → 本地重试 5 次
)为什么不直接 Fallback 不 Retry?
| 方式 | 行为 | 问题 |
|---|---|---|
| 只 Fallback | OpenAI 失败 1 次 → 立刻切 Claude | OpenAI 只是临时抖动,却切走了 |
| 只 Retry | OpenAI 重试 5 次都失败 → 整个挂 | 没有 Plan B |
| Retry + Fallback | OpenAI 重试 5 次 → 还失败 → 切 Claude 重试 5 次 → ... | 最优 |
Retry 处理"临时故障"(多试几次就好),Fallback 处理"彻底故障"(换一个)。 两者解决不同问题,组合才完整。
Provider 级的 Fallback
除了 NewFallbackModel(模型级),还有 NewFallbackProvider(model_fallback.go:89),provider 级的降级:
// agents/model_fallback.go:89
func NewFallbackProvider(primary ModelProvider, fallbacks ...ModelProvider) ModelProvider区别,Model 级是"一个模型名,多个 Model 实例";Provider 级是"一个 provider 入口,每次 GetModel 自动组成 FallbackModel"。
// Provider 级
provider := agents.NewFallbackProvider(
openai.NewProvider(), // 主
anthropic.NewProvider(), // 备
)
// 之后 provider.GetModel("gpt-5") 自动返回 FallbackModelProvider 级更方便,配置一次,所有模型自动有降级。 适合"整个 provider 可能挂"的场景(OpenAI 全挂 → 全切 Anthropic)。
实战配置示例
一个生产级的模型配置,组合 Router + Retry + Fallback:
// 1. 各 provider
openaiP := openai.NewProvider()
anthropicP := anthropic.NewProvider()
localP := ollama.NewProvider()
// 2. 每个 provider 包 Retry(处理临时故障)
retryPolicy := agents.RetryPolicy{MaxAttempts: 3}
// 3. Provider 级 Fallback(OpenAI 挂 → Anthropic → 本地)
robustProvider := agents.NewFallbackProvider(
wrapWithRetry(openaiP, retryPolicy),
wrapWithRetry(anthropicP, retryPolicy),
wrapWithRetry(localP, retryPolicy), // 本地做最后兜底
)
// 4. 再套 Router(按模型名前缀路由)
router := agents.NewRouterProvider(map[string]agents.ModelProvider{
"openai": robustProvider,
"anthropic": robustProvider, // 都走同一个降级链
}).WithFallback(robustProvider)
opts := agents.RunOptions{ModelProvider: router}三层防护,Router(路由)+ Retry(临时故障)+ Fallback(彻底故障)。 这是生产级 Agent 的标配。第 24 章会详述这个组合。
设计权衡:Fallback 的代价
Fallback 不是免费的:
- 延迟,主模型失败后,要等 Retry 耗尽,才试备用。最坏情况延迟 = 主 Retry 全程 + 备用 Retry 全程。
- 一致性,不同模型的输出风格不同。主用 GPT,备用 Claude,用户可能感觉"Agent 变了个人"。
- 成本,备用模型可能更贵(比如本地不可用切到贵的云 API)。
所以 Fallback 要配置得当,备用模型要"够用"(不一定和主一样强,但要能完成任务),Retry 的 MaxAttempts 不要太大(否则降级太慢)。
小结
读完这一章,你应该理解:
- Fallback 解决什么,主模型彻底挂了,自动切备用
- 降级链逻辑,依次试,第一个成功返回,全失败合并错误
- errors.Join,合并所有错误,便于调试
- context 取消不降级,调用方放弃了,不浪费
- 流式"首 event 前"约束,吐过 token 不切换
- 最佳组合,每个模型先 Retry 再 Fallback,临时故障和彻底故障都覆盖
- Provider 级 vs Model 级,前者配置一次全覆盖,后者更精细
Retry + Fallback 让模型调用"既容忍抖动,又有 Plan B"。 加上第 21 章的 Router,三个装饰器可以任意组合,下一章讲怎么组合。
下一篇
第 24 章,三装饰器可任意组合。Retry、Fallback、Router 都实现 Model/ModelProvider 接口,是装饰器。它们可以任意嵌套,Retry 套 Fallback、Fallback 套 Router、Router 套 Retry……这一章用几个实战组合讲透装饰器模式的威力。
本文是《从 0 写 Agent》系列第 23 章,案例代码基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版)。全系列目录见总览。

