应用部分 9. 模型路由与令牌预算
状态:建议。 将廉价模型用于日常事务、昂贵模型用于关键审查——这是成熟的实践。具体的阈值、故障切换(fallback)公式以及作为独立服务的预算管理员——属于前沿领域:Qwen Code 本身不管理预算,实现取决于基础设施。
对于教学演练,只需模拟 local-coder 在 examples/budget-keeper/ 中的故障,并验证并非整个队列都进入昂贵层级。独立的预算管理员和与供应商的集成属于完整的生产路径。
在教学 AgentClinic 中,我们在第一卷第 4 部分选择了一个模型,并保持了流程独立于该模型(第 15 部分)。在生产环境中,单一模型是不够的。昂贵模型不应自发吞噬所有事件队列。廉价模型也不应在有争议的案例上默默降级。这里增加了一个教学项目中没有的维度:按流水线阶段管理模型混合。路由可以方便地嵌入用户命令或钩子中——这些技巧来自第 14 部分. 通过 Qwen Code 技能构建自己的工作流。
阅读前
- 第一卷的依赖:第 15 部分要求智能体可替换性,第 14 部分展示项目技能和钩子。
- 本地教学案例:
autoscale_200pct,因为廉价层级的故障提供了可观察的预算模拟。 - 留给
capstone/的线索:一个针对high_memory_usage的风险——当local-coder故障时会发生什么,允许多少任务进入frontier-reviewer,哪个token_health阈值会阻止切换。
- 第一遍阅读的主要术语:层级(tier)和
token_health。预算管理员(budget keeper)、failover_to_frontier、manual_queue_after_120s——供参考。 - 可以推迟的内容:与供应商的集成、独立的预算管理员服务以及定期演练。
目标
本章的目标是将每日令牌预算(示例中为 1000 万)从静态限制转变为 SDD 流水线的可管理路由表。这就是层级预算(tier-budgeting):按工作阶段在不同模型层级之间分配令牌。廉价模型(local-coder)处理日常事务。昂贵模型(frontier-reviewer)仅在关键审查和有争议的决策时启用。
1000 万这个数字的选择是为了在平均阶段成本约 5 万令牌的情况下,覆盖每天约 200 个事件的流量。对于更大的流量,按比例扩展预算;对于更小的流量,按比例缩减,同时保持阶段之间的比例。9M / 1M 的层级分配反映了观察结果:在正常模式下,有争议的审查约占预算的 10%。如果您的项目经常遇到复杂任务,请将上层级的比例提高到 15–20%。
完成本节后,您将能够:构建事件管理各阶段的令牌分配、为每个层级设定 SLA 阈值、验证廉价模型故障时系统的行为,并证明节省成本不会破坏 MTTR(平均恢复时间)、升级质量和事后分析的稳定性。local-coder 和 frontier-reviewer 是基础设施中的角色,而非模型名称:在一个项目中,它们可能是同一供应商的不同模型;在另一个项目中,可能是本地和云端模型。
最小教学场景
教学案例
来自 book/part-12-mvp.md 的 appointments-api MVP 阶段的生产事件 autoscale_200pct。上午本地层级不可用 45 分钟(11:00–11:45),20 个事件进入队列,手动超时为 120 秒。教学演练的目标是验证:故障切换仅让高风险任务进入上层级,而非整个队列,并保持 token_health_min 高于安全阈值。
准备
book2/examples/budget-keeper/specs/budget_network.yaml— 1000 万令牌的计划描述。
book2/examples/budget-keeper/specs/budget_network_5m.yaml— 现成的 500 万令牌校准版本,比例相同。book2/examples/budget-keeper/scenarios/fail_local_45m.json和fail_local_15m.json— 两个故障场景。book2/examples/budget-keeper/outputs/budget_plan.example.json、outputs/fail_result.example.json— 用于比较的基准。book2/examples/budget-keeper/scripts/compile.py、simulate.py、inspect.py。
步骤
cd book2/examples/budget-keeper。 预期:您已进入示例目录,无需额外依赖。python3 scripts/compile.py --budget-spec specs/budget_network.yaml --out out/budget_plan.json。 *预期:out/budget_plan.json中字段daily_budget_tokens: 10000000,local 层级总和为 9,000,000,frontier 为 1,000,000(90/10)。*- 通过
diff比较out/budget_plan.json与outputs/budget_plan.example.json。 预期:无差异,或仅注释有偏差。 python3 scripts/simulate.py --plan out/budget_plan.json --scenario scenarios/fail_local_45m.json --out out/fail_result.json。 *预期:failover_to_frontier == 5,degraded_queue == 15,token_health_min >= 0.5。*
python3 scripts/inspect.py --result out/fail_result.json --query "failover_to_frontier==5 && degraded_queue==15 && manual_queue_after_120s==15 && token_health_min>=0.5"。 预期:返回代码 0,四个条件同时满足。
不好的做法: 一次只检查一个指标——frontier 去了 5 个任务,其余"看起来还行",忘了 token_health。 好的做法: 用 && 连接四个条件的单次 inspect 运行——任一指标失败都会中断运行。
- 短时故障。
python3 scripts/simulate.py --plan out/budget_plan.json --scenario scenarios/fail_local_15m.json --out out/fail_15m_result.json && python3 scripts/inspect.py --result out/fail_15m_result.json --query "token_health_min>=0.7"。 *预期:返回代码 0,token_health_min >= 0.7(短时故障对 frontier 的消耗较不激进)。* - 将运行结果记录为简短预算结论:
local-coder不可用,上层级仅接收 5 个任务,其余进入 degraded/manual,token_health_min保持高于阈值。 *预期:下次token_health回归时,比较对象不是"绿色 vs 旧基线",而是两个模拟结果。*
如果您已安装 Qwen Code 并需要审查解释,执行一个额外的可选步骤:
qwen -p "阅读 @out/fail_result.json 和 @out/fail_15m_result.json。解释为什么 45 分钟故障比 15 分钟故障更显著地降低 token_health。不要修改文件。" --approval-mode plan
此类输出作为评论有用,但不能替代 inspect.py,也不被视为可运行的事实。
控制事实
步骤 5 的四个条件同时满足。token_health_min 在 45 分钟故障时不低于 0.5,在 15 分钟故障时不低于 0.7。缺少任一模拟,场景即被视为不完整:单点无法展示预算对故障持续时间的敏感性。
如何进入 capstone/
转移到 capstone/budget-note.md 的不是整个预算表,而是一个风险和一个限制器:local-coder 故障时会发生什么,多少任务进入 frontier-reviewer,哪个 token_health 阈值阻止进一步切换。如果主要评分案例是 high_memory_usage,将此运行记录为同一范围的预算风险:不是整个 autoscale_200pct,而是"廉价层级故障时昂贵层级不接受整个队列"的原则。完整的 budget_plan.json 仅完整路径需要。
最小片段:
risk: "local-coder 不可用 45 分钟"
effect: "5 个任务进入 frontier-reviewer,15 个保持 degraded/manual"
simulated_floor: "token_health_min == 0.5(45 分钟时的低谷)"
alert_threshold: "token_health_min < 0.60(来自 anti-Goodhart 表的守卫)"
decision: "不将整个队列转移到昂贵层级"
两个不同的阈值不应混淆。0.5 是模拟观察到的谷底;0.60 是生产环境中守卫阻止自动切换的底线。教学场景显示,45 分钟故障突破了守卫,因此需要人工决策。
可审查的线索
out/ 目录是本地模拟结果,不应进入仓库。对于教学演练,capstone/budget-note.md 中的一行包含风险、效果、守卫阈值和决策即足够。
在您的生产仓库中,可以额外存储简短的演练运行报告:45 分钟和 15 分钟场景的链接、token_health_min 不变量和不将整个队列转移到昂贵层级的决策。此类报告仅在有人审查或 CI 读取时才有用;单独的提交不是 SDD 事实。
关键思想
模型路由始于将事件划分为阶段:triage(初步分类)、分类、诊断、计划、修复、事后分析。为每个阶段固定三个参数:哪个模型服务它、预期令牌成本、以及什么风险下升级到上层级。
Triage 和分类是密集、模板化的流量,对延迟敏感。因此 local-coder 作为主要日常消费者承担:快速规范化告警、分组相似症状、提取服务、严重级别、最近事件和初步影响半径(blast radius,可能的损害范围)。
frontier-reviewer 占据网络顶层,处理有争议的诊断、冲突计划、关键修复和事后分析。这些是错误成本可能超过整个模型调用的案例。
层级之间的界限不按模型声望划分,而按决策的可恢复性划分。如果操作容易回滚且可通过本地验证器检查,则留在廉价范围。如果回滚昂贵或后果影响多个服务,则需要昂贵范围。
flowchart TD IN[事件流] S[SDD 阶段 S 信号收集与规范化] D1[SDD 阶段 D1 异常检测] D2[SDD 阶段 D2 诊断与评估] Q[处理队列长度] R[风险等级] B[令牌预算作为能量] P[流量分配器] A[local-coder 基础层级] G[frontier-reviewer 顶层] O[事件解决与反馈] IN --> S --> D1 --> D2 --> O D1 --> Q D2 --> R Q --> P R --> P B --> P P -->|稳定模式| A P -->|队列与风险增长| G A --> O G --> O A -->|复杂案例升级| G O -->|限制与队列校正| B
上图仅显示 SDD 周期的输入和决策阶段(信号收集、检测、诊断);完整的事件周期继续 plan、remediation、postmortem 阶段,它们有独立的 SLA 和配额——出现在下面的 YAML 中。即图表中的三个抽象阶段(S、D1、D2)展开为六个具体配额(triage、classification、diagnosis、plan、remediation、postmortem)加上 control_reserve 作为缓冲。
按负载形态而非仅按期望节省构建令牌配额。对于每日 1000 万令牌,基础分配可将 900 万固定给 local-coder,100 万给 frontier-reviewer。廉价层级覆盖 triage、分类、初步诊断和初步计划。昂贵层级获得验证、有争议的修复操作和事后分析的储备。
为每个阶段单独设定 SLA 阈值。例如:triage 必须在数十秒内完成,诊断可以消耗更多上下文,事后分析允许更长的运行以换取证据链的完整性。
不要将储备变成"剩余随便用"。储备是仅在风险、队列或不确定性增长时激活的保险层。
项目文件模板:.specify/memory/budget_network.yaml。
daily_budget_tokens: 10000000
phases:
triage:
local-coder: 3000000
frontier-reviewer: 120000
sla_p95: "30s"
classification:
local-coder: 2000000
frontier-reviewer: 140000
sla_p95: "45s"
diagnosis:
local-coder: 1500000
frontier-reviewer: 180000
sla_p95: "90s"
plan:
local-coder: 800000
frontier-reviewer: 120000
sla_p95: "120s"
remediation:
local-coder: 700000
frontier-reviewer: 200000
sla_p95: "180s"
postmortem:
local-coder: 300000
frontier-reviewer: 240000
sla_p95: "10m"
control_reserve:
local-coder: 700000
frontier-reviewer: 0
在您的项目中,这些步骤实现为 tools/budget_keeper.py compile|assert|simulate|inspect,基于与供应商和 CI 的集成。在教程内部,运行可运行的等效版本:
> [可运行] — 预算管理器的可运行示例位于 [examples/budget-keeper/](examples/budget-keeper/)(参见 [examples/budget-keeper/README.md](examples/budget-keeper/README.md)):包含示例 budget_network.yaml、脚本 compile.py、simulate.py、inspect.py 和故障切换场景。
cd book2/examples/budget-keeper
python3 scripts/compile.py \
--budget-spec specs/budget_network.yaml \
--out out/budget_plan.json
将级联故障建模为层级故障时的排序切换,而非简单的一换一。这里的故障切换(failover)是层级故障时的负载切换计划。看看方法的区别。
不好的做法: > local-coder 故障时,所有流量进入 frontier-reviewer。
问题:昂贵层级会在几分钟内耗尽每日配额,当真正的 P0/P1 到来时无法服务。
好的做法: > local-coder 故障时,仅 severity in [P0, P1] 且 age > 90s 的任务进入 frontier-reviewer,其余进入降级队列(degraded queue)。
如果 local-coder 故障,不要让整个入站流量自动进入 frontier-reviewer。否则昂贵层级会快速耗尽配额,失去服务真正关键案例的能力。
相反,预算管理员(budget-keeper,令牌预算控制服务)每分钟计算几个参数:spent[p] 和 queue[p](阶段 p 的已消耗和队列长度)、quota[p](剩余配额)、事件年龄、影响半径(blast radius)和模型置信度差距(confidence-gap)。基于此,它仅选择延迟比消耗更危险的任务。这种排序故障切换改变了升级时间:部分事件立即进入 frontier-reviewer,部分留在降级模式(degraded mode),部分在给定超时后转入人工通道。
紧急模式,或"红色按钮"(red button),是切换到保护模式的开关。形象名称可接受,但工件中需固定紧急模式的确切条件。它需要作为独立管理模式,因为自动故障切换本身可能成为故障源。启动条件是形式化的:两个连续窗口出现 token_health(令牌预算健康综合指标)风险增长、队列超限、关键严重级别 SLA 超限、或 local-coder 服务端点故障。
触发后,系统限制新队列、禁止大规模自动修复、为 P0/P1 保留 frontier-reviewer、将其余决策转入人工或准人工模式。人工模式不是退回混乱。让它继承相同的文件协议、证据链和 PostToolUse 检查,以便稳定后可以恢复每个决策的原因。
validation.md 中的反古德哈特定律(anti-Goodhart)逻辑关闭了预算优化的主要风险:以隐藏的实际事件管理恶化为代价改善报告指标。反古德哈特定律规则禁止:如果一个指标的增长以其他指标恶化为代价,则视为发布成功。
如果仅控制 MTTR,系统可能将复杂事件更快标记为非关键、压低升级比例、或将不便的 P0 挤入人工通道而不做完整事后分析。因此 MTTR 需与四个守卫指标和一个检查激活条件一起验证。它们的角色适合放在一张表中。
| 指标 | 测量内容 | 阻止什么 |
|---|---|---|
escalation_share | 升级到总流量的比例 | 检查激活条件——当 MTTR 快速下降时同时低于历史区间 |
silent_p0 | 无升级关闭的 P0 比例 | 超过 2% 的增长 |
unresolved_manual_ratio | 未解决的人工任务比例 | 超过 5% 的增长 |
postmortem_gap | 事后分析遗漏 | 超过 10% 的遗漏 |
token_health_min | 预算健康最低水平 | 低于 0.6 |
如果任一守卫指标越界,MTTR 的改善视为无效。配对检查正是为此:漂亮的报告指标不应掩盖稳定性退化、静默 P0 失败或证据链断裂。
validation.md 中预算网关规则的片段。
checks:
- id: anti_goodhart_budget
if:
all:
- mttr_p95 < "5m"
- escalation_ratio < 0.08
then:
fail_if:
- silent_p0 > 0.02
- unresolved_manual_ratio > 0.05
- postmortem_gap > 0.10
- token_health_min < 0.60
- id: ecology_warn
if:
any:
- token_health_trend_5m < -0.12
- queue_pressure > 0.80
- degraded_mode_duration > "120s"
then:
require:
- red_button_review == true
- manual_channel_open == true
- frontier_reserved_for_p0_p1 == true
在您的项目中,此检查实现为 python3 tools/validation_runner.py run --spec validation.md --out .specify/artifacts/validation_health.json,随后用 jq 检查 anti_goodhart_budget 和 ecology_warn。反古德哈特定律检查本身的可运行近似版本是 examples/goodhart-validator/scripts/run_validation.py(参见第 10 章)。
完整路径:阈值校准
预算规模、local/frontier 比例和 manual_timeout_sec 的"低/默认/高"表、500 万压缩版本的练习以及重审信号——见附录 D,D.3 节。第一遍阅读时,两次故障模拟和 budget-note.md 中的 token_health 行即足够。
示例与应用
场景 B 的实用模拟验证:local-coder 故障不会将 frontier-reviewer 变成整个队列的紧急储备。11:00 廉价模型的本地端点不可用 45 分钟。队列包含 20 个事件。手动超时为 120 秒。
策略选择三个方向:5 个影响半径最大且最老的任务进入 frontier-reviewer,15 个任务留在降级队列,两分钟后打开人工通道。检查的成功不在于所有任务自动处理。成功在于:系统保留了昂贵层级、限制了队列、且未让 token_health_min 低于安全阈值。
在您的项目中,此场景运行为 tools/budget_keeper.py simulate ... --failure "11:00,local-coder,down,45m" --queue 20 --manual-timeout-sec 120,随后 inspect 条件 failover_to_frontier==5 && degraded_queue==15 && manual_queue_after_120s==15 && token_health_min>=0.5。可运行的等效版本相同:
> [可运行] — 场景 examples/budget-keeper/scenarios/fail_local_45m.json。
cd book2/examples/budget-keeper
python3 scripts/simulate.py \
--plan out/budget_plan.json \
--scenario scenarios/fail_local_45m.json \
--out out/fail_result.json
python3 scripts/inspect.py \
--result out/fail_result.json \
--query "failover_to_frontier==5 && degraded_queue==15 && manual_queue_after_120s==15 && token_health_min>=0.5"
稳定后的回退应分阶段:否则廉价层级恢复会造成第二次级联。首先仅将 local-coder 的 30% 配额用于 triage/classification(这些阶段更容易按形式特征验证,且更快卸载入站流量);再 30% 用于 diagnosis/plan,在三个稳定窗口的 token_health、silent_p0_ratio 无增长且队列正常化之后;仅在 PostToolUse 审计后允许完全恢复。原因:过早解除人工模式可能掩盖降级期间积累的错误。
在运维中,此模型适合作为每日预算演练(budget-drill)验证。团队取昨日告警流量,通过当前 budget_network.yaml 回放,并人为断开 local-coder 15、30 和 45 分钟。然后比较四个指标:MTTR、升级比例、人工队列规模和最小 token_health。
分析信号:
- 如果短时故障时
frontier-reviewer开始服务非关键任务——故障切换过宽; - 如果人工通道在适度队列时已打开——SLA 阈值过于敏感。
演练目标是找到降级可预测的配置,而非在配额耗尽前不可见的配置。
总结
令牌预算只有在五个要素连接为单一管理回路时才成为可控资源:SDD 阶段、模型层级化(model tiering)、SLA 阈值、故障切换和验证。在此回路中,local-coder 为大规模日常事务提供吞吐能力;frontier-reviewer 保护有争议和高风险决策;紧急模式在风险增长时限制自动化;validation.md 禁止以隐藏 P0 和破坏事后分析为代价改善 MTTR。此方案不仅显示当前消耗,还显示退化顺序:哪些阶段将首先匮乏、哪些任务必须转入昂贵层级、以及何时人工模式比继续自动化更安全。接下来,此回路将转向古德哈特指标和配对守卫指标。
工件与就绪标准
| 工件 | 就绪条件 |
|---|---|
本地运行 book2/examples/budget-keeper | 配额总和匹配 1000 万令牌及给定的 local/frontier 分配 |
| out/budget_plan.json、out/fail_result.json、out/fail_15m_result.json | 45 分钟场景给出 failover_to_frontier==5、degraded_queue==15、manual_queue_after_120s==15、token_health_min>=0.5;15 分钟场景保持 token_health_min>=0.7;out/ 不提交 | | precedents.md 或 capstone/budget-note.md 中的记录 | 解释 local-coder 故障时发生什么、哪些任务进入 frontier-reviewer、哪个 token_health_min 阈值保护预算 |
完整路径添加 .specify/memory/budget_network.yaml 含阶段和 SLA、compile 后的 budget_plan.json、fail_scenario_B.json、含预算网关 anti-Goodhart 的 validation.md 和 validation_health.json。视为就绪的条件:紧急模式为 P0/P1 保留 frontier 并打开人工通道、anti-Goodhart 网关阻止以 silent_p0 或审计断裂为代价的节省、预算模拟纳入定期演练或 CI。
实践
cd book2/examples/budget-keeper && python3 scripts/compile.py --budget-spec specs/budget_network.yaml --out out/budget_plan.json— *预期:daily_budget_tokens == 10_000_000,local 层级总和 9M,frontier 1M(90/10)。*
python3 scripts/simulate.py --plan out/budget_plan.json --scenario scenarios/fail_local_45m.json --out out/fail_result.json && python3 scripts/inspect.py --result out/fail_result.json --query "failover_to_frontier==5 && degraded_queue==15 && manual_queue_after_120s==15 && token_health_min>=0.5"— 预期:代码 0,四个条件同时满足。- 将五行转移到
capstone/budget-note.md:risk、effect、simulated_floor、alert_threshold、decision。 *预期:格式与"如何进入capstone/"部分的示例一致;完整的budget_plan.json不进入capstone/。*
检查问题
- 为什么故障切换不应让整个队列进入昂贵层级?
- 哪些指标暴露预算路由的退化?
- 何时人工模式比继续自动化更安全?
- 本地模型在高峰时段故障 45 分钟。您有 60% 的日预算,但 MTTR 在上升。您会切换什么——模型、路由策略还是 triage 模式?