
字节笔记本
2026年10月24日 · 约 12 分钟读完
三个装饰器,任意组合出韧性

本文是《从 0 写 Agent》系列第 24 章。这个系列基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版),用 Go 从零实现一个生产级 Agent 运行时;本章讲「三装饰器可任意组合」。
Retry、Fallback、Router,三个模型装饰器,各自解决一个问题。但它们的真正威力在于,可以任意嵌套组合。
这一章用几个实战组合,讲透装饰器模式为什么是 Agent 模型工程的基石。
引子:装饰器模式的本质
先回顾三个装饰器:
| 装饰器 | 解决什么 | 包装什么 |
|---|---|---|
| RetryModel | 临时故障(重试就好) | 包装 Model |
| FallbackModel | 彻底故障(换备用) | 包装 Model |
| RouterProvider | 多 provider 路由 | 包装 ModelProvider |
它们的共同点,都实现被包装对象的接口。 RetryModel 实现 Model,FallbackModel 实现 Model,RouterProvider 实现 ModelProvider。
这意味着,装饰器可以包装装饰器。 一个 RetryModel 可以包一个 FallbackModel,后者又包了 Router 返回的 Model。任意嵌套,像俄罗斯套娃。

为什么能任意组合
装饰器模式的核心契约,装饰器实现和被装饰对象相同的接口。
// RetryModel 实现了 Model 接口
type retryModel struct {
inner Model // 被装饰的,也是 Model
}
func (r *retryModel) GetResponse(...) { /* 失败时调 inner 重试 */ }
// FallbackModel 也实现了 Model
type fallbackModel struct {
models []Model // 一串 Model
}
func (f *fallbackModel) GetResponse(...) { /* 依次试 models */ }因为都是 Model 接口,所以:
// Fallback 里包 Retry
m := NewFallbackModel(
NewRetryModel(openai, policy), // 这个是 Model
NewRetryModel(anthropic, policy), // 这个也是 Model
)
// m 是 Model,可以被再包
m2 := NewRetryModel(m, anotherPolicy) // 给整个降级链再套一层 Retry无限嵌套,类型永远对。 这是接口 + 装饰器的威力,你不用为"组合方式"写特殊代码,接口天然支持。
实战组合一:Retry + Fallback(最常用)
第 23 章讲过的最佳实践,每个模型先 Retry,再 Fallback:
model := agents.NewFallbackModel(
agents.NewRetryModel(openaiModel, retryPolicy),
agents.NewRetryModel(anthropicModel, retryPolicy),
)行为:
OpenAI 重试 3 次 → 全失败 → Anthropic 重试 3 次 → 全失败 → 返回合并错误
最坏延迟 = OpenAI 3 次(含退避)+ Anthropic 3 次。可接受。
实战组合二:Provider 级 Fallback + Router
// 1. Provider 级 Fallback(OpenAI 挂 → Anthropic)
robust := agents.NewFallbackProvider(openaiP, anthropicP)
// 2. Router 路由(按前缀分发)
router := agents.NewRouterProvider(map[string]agents.ModelProvider{
"fast": groqP, // groq/xxx 走 Groq
"smart": robust, // smart/xxx 走降级链
}).WithFallback(robust) // 无前缀走降级链行为:
groq/llama-3.3→ 走 Groq(快、便宜)smart/gpt-5→ 走 OpenAI(重试+降级到 Anthropic)claude-sonnet(无前缀)→ 走 Fallback 链
一个 provider 入口,按模型名分发到不同后端,每个后端有自己的容错策略。
实战组合三:全防护(生产级)
retryPolicy := agents.RetryPolicy{MaxAttempts: 3, BaseDelay: 500 * time.Millisecond}
// 每个 provider 自带 Retry
robustOpenAI := agents.NewRetryProvider(openaiP, retryPolicy)
robustAnthropic := agents.NewRetryProvider(anthropicP, retryPolicy)
robustLocal := agents.NewRetryProvider(ollamaP, retryPolicy)
// Provider 级 Fallback(云挂 → 本地兜底)
cloudWithLocalBackup := agents.NewFallbackProvider(
robustOpenAI,
robustAnthropic,
robustLocal, // 本地做最后保险
)
// Router 路由
router := agents.NewRouterProvider(map[string]agents.ModelProvider{
"openai": cloudWithLocalBackup,
"anthropic": cloudWithLocalBackup,
}).WithFallback(cloudWithLocalBackup)三层防护:
- Router,按模型名路由
- Fallback,云挂了切本地
- Retry,每个 provider 内部重试
这是"即使 OpenAI 和 Anthropic 同时挂,本地模型也能顶上"的极限容错。 适合不能停服的关键业务。
设计权衡:嵌套层次与延迟
装饰器可以无限嵌套,但不应该。每多一层,最坏延迟增加。
3 层 Fallback × 每层 3 次 Retry × 每次 500ms~30s 退避
最坏延迟 = 3 × (3 × 15s 平均) = 135 秒用户等不了 135 秒。 所以实战中要平衡:
- Retry 的 MaxAttempts 别太大(2-3 次够)
- Fallback 的层级别太深(2-3 个备用够)
- 配合 context timeout 兜底(整个调用最多等 N 秒)
ctx, cancel := context.WithTimeout(parentCtx, 30 * time.Second)
defer cancel()
agents.Run(ctx, ...) // 30 秒还没结果就放弃容错不是无限重试,是"在可接受时间内,尽可能成功"。
设计权衡:装饰器 vs 配置
有人会想,为什么不做成"一个配置对象"(比如 YAML 描述重试/降级策略),而要用代码嵌套?
配置适合"参数调整"(改 MaxAttempts、改 BaseDelay),但不适合"结构组合"(Retry 套 Fallback 还是 Fallback 套 Retry,是不同的拓扑)。
代码嵌套的好处:
- 类型安全,编译器检查接口匹配
- 显式拓扑,嵌套结构一目了然,不像配置要脑补解析
- 灵活,想怎么组合怎么组合,不受配置 schema 限制
agents-server(Part VI)的做法,数据库存参数(MaxAttempts、BaseDelay、模型名),代码里组装装饰器拓扑。参数可配置,拓扑在代码里。这是平衡点。
装饰器模式的普适性
装饰器不只用于模型层。它是软件工程的通用模式:
| 场景 | 装饰器 |
|---|---|
| HTTP middleware | 日志 → 认证 → 限流 → handler |
| io.Reader | bufio.Reader(GzipReader(FileReader)) |
| Agent Model | Retry(Fallback(Router(openai))) |
核心都是,实现相同接口,包装增强能力,任意组合。 理解了 agents-go 的模型装饰器,你就理解了装饰器模式本身,它无处不在。
Part IV 模型工程小结
第 21-24 章构成了 Agent 的"模型工程":
| 章节 | 能力 |
|---|---|
| 21 Router | 多 provider 路由 |
| 22 Retry | 容忍临时故障 |
| 23 Fallback | 容忍彻底故障 |
| 24 组合 | 任意嵌套,生产级容错 |
这四章让 Agent 的模型调用"健壮",不怕抖动、不怕故障、多后端混用。 但"健壮"还不够,你还要"知道发生了什么"。这就是 Tracing(第 25-27 章)。
下一篇
第 25 章,Tracing:从 0 造一套可观测系统。Agent 出错时,你需要知道是哪一步炸的。agents-go 自实现了一套 trace/span/processor/exporter 四层架构(不用 OpenTelemetry),对标 Python SDK 保证一致性。这是 Agent 可观测性的核心。
本文是《从 0 写 Agent》系列第 24 章,案例代码基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版)。全系列目录见总览。



