首页
/ get-shit-done `dynamic_routing` 全解析:基于失败分档(Failure-Tier)升级的按需模型路由与成本控制机制

get-shit-done `dynamic_routing` 全解析:基于失败分档(Failure-Tier)升级的按需模型路由与成本控制机制

2026-09-07 16:50:30作者:庞眉杨Will

dynamic_routing 是 get-shit-done(GSD)在 .planning/config.json 中引入的一组动态路由配置(feature [#3024],变更说明见 .changeset/dynamic-routing.md),其设计哲学是"默认用廉价模型起步,仅在编排器检测到软失败(soft failure)时按档位逐级升配"。读完本文你将掌握:dynamic_routing 全部配置字段的含义与默认值、light / standard / heavy 三档 agent 归类、解析器 resolveModelForTier 的底层实现与升级流程,以及它与 model_overridesmodels.<phase_type> 叠加后的五层优先级裁决规则,从而把 GSD 多 agent 协同的模型成本精确控制在"该花的才花"。

一、机制速览:为什么需要"失败分档升级"

get-shit-done 在多阶段工作流中会为 planner、debugger、executor、verifier 等大量子代理(agent)逐一挑选模型。如果所有 agent 一律使用最强模型(如 opus),成本随 agent 数量线性膨胀;如果一律使用最弱模型,复杂阶段的推理质量又无法保证。

dynamic_routing 提供第三条路——把它当作一根"成本杠杆":首次派发(attempt = 0)时解析器返回 tier_models[default_tier],即每个 agent 声明档位对应的默认模型;当编排器(orchestrator)检测到软失败(典型场景:verification 结论不明确、plan-check 返回 FLAG)时,以 attempt + 1 重新派发,解析器将档位向上升一级;升级次数受 max_escalations 硬性封顶。而硬失败(异常/崩溃)不参与升级,直接向用户暴露。

该能力在变更说明中被定性为"完全向后兼容":dynamic_routing 默认关闭(enabled: false 或省略整个块),此时解析行为与此前静态解析完全一致。

二、配置指南:dynamic_routing 完整参数解析

在项目的 .planning/config.json 中加入以下块即可启用:

{
  "dynamic_routing": {
    "enabled": true,
    "tier_models": {
      "light":    "haiku",
      "standard": "sonnet",
      "heavy":    "opus"
    },
    "escalate_on_failure": true,
    "max_escalations": 1
  }
}

官方配置总览文档见 docs/CONFIGURATION.md 的"Dynamic Routing with Failure-Tier Escalation"一节。各字段语义如下:

Key 类型 默认值 说明
dynamic_routing.enabled boolean false 总开关。为 true 时启用动态路由解析器做档位选择;false 或缺失时完全保持原有静态解析,不产生任何行为差异
dynamic_routing.tier_models.light enum (无) light 档模型别名,典型配置为 haiku
dynamic_routing.tier_models.standard enum (无) standard 档模型别名,典型配置为 sonnet
dynamic_routing.tier_models.heavy enum (无) heavy 档模型别名,典型配置为 opus
dynamic_routing.escalate_on_failure boolean true 置为 false 时即使 enabled: true 也禁止升级,所有尝试一律停留在默认档
dynamic_routing.max_escalations integer 1 每次 agent 调用的升级次数硬上限,防止升级失控;超出上限时解析器返回上限档对应模型

配置校验由 isValidConfigKey(实现在 get-shit-done/bin/lib/config-schema.cjs)负责。合法键仅限上述字段:dynamic_routing.enableddynamic_routing.escalate_on_failuredynamic_routing.max_escalations 以及 dynamic_routing.tier_models.light / standard / heavytier_models 中任何其他档位(如 jumbomedium)与未知的 dynamic_routing.* 键都会被拒绝,且裸 dynamic_routing(不带字段)不能作为配置写入目标。

说明tier_models 中填的是档位别名而非全限定模型 ID。全限定 ID(如 openai/gpt-5)属于 model_overrides 的职责范畴,详见下文"五层优先级"。

三、Agent 默认分档:light / standard / heavy

每个 agent 都在模型目录(catalog)中声明自己的默认档位。首次派发时解析器取 tier_models[default_tier] 作为模型。三档划分及对应 agent 如下:

档位 Agent 适用定位
light gsd-codebase-mapper、gsd-doc-classifier、gsd-doc-verifier、gsd-integration-checker、gsd-intel-updater、gsd-nyquist-auditor、gsd-pattern-mapper、gsd-plan-checker、gsd-research-synthesizer、gsd-ui-auditor、gsd-ui-checker 廉价/快速——纯映射器、扫描器、低风险审计
standard gsd-advisor-researcher、gsd-ai-researcher、gsd-code-fixer、gsd-code-reviewer、gsd-doc-synthesizer、gsd-doc-writer、gsd-domain-researcher、gsd-eval-auditor、gsd-executor、gsd-phase-researcher、gsd-project-researcher、gsd-ui-researcher、gsd-verifier 默认主力——研究、写作、一级验证
heavy gsd-assumptions-analyzer、gsd-debug-session-manager、gsd-debugger、gsd-eval-planner、gsd-framework-selector、gsd-planner、gsd-roadmapper、gsd-security-auditor、gsd-user-profiler 深度推理——已在顶端,无法继续升级

从源码层面看,档位表的来源分三层:

  1. 数据层:所有 33 个内置 agent 的逐档配置存放在模型目录 sdk/shared/model-catalog.json,每个 agent 带 routingTierphaseTypegolden/balanced/budget 等元数据;
  2. 常量层VALID_AGENT_TIERScatalog.adaptiveTierMap 的键集合构造,AGENT_DEFAULT_TIERS 则由 catalog 中每个 agent 的 routingTier 映射而来,见 get-shit-done/bin/lib/model-catalog.cjs#L50-L69
  3. 再导出层:上述常量连同 nextTierget-shit-done/bin/lib/model-profiles.cjs 统一再导出,供解析器与测试引用。

nextTier 的档位爬升规则直接写死在 get-shit-done/bin/lib/model-catalog.cjs#L93-L98

function nextTier(currentTier) {
  const order = ['light', 'standard', 'heavy'];
  const idx = order.indexOf(String(currentTier));
  if (idx === -1) return null;
  return order[Math.min(idx + 1, order.length - 1)];
}

light → standard → heavy → heavy:heavy 已到顶,继续调用 nextTier 仍停留 heavy;对非法输入(如 jumbonull)返回 null,驱动解析器走安全回退。

四、解析器契约与升级流程:resolveModelForTier(cwd, agent, attempt)

动态路由的 JS 层基础设施是新增的解析器 resolveModelForTier,导出自 get-shit-done/bin/lib/core.cjs#L1881。函数签名与行为约定(见 core.cjs#L1356-L1379):

resolveModelForTier(cwd, agentType, attempt = 0) → modelAlias | fullId
  • cwd:项目目录,用于 loadConfig 读取 .planning/config.json
  • agentType:agent 名(如 gsd-verifier);
  • attempt:0 表示首次派发,≥1 表示升级重试;内部会被 max_escalations 截断,超出部分返回封顶档模型,便于编排器记录 "max escalations reached" 而不再燃烧预算。

4.1 完整解析流程(源码级)

核心实现位于 core.cjs#L1381-L1442,关键分支如下:

  1. 用户级覆盖优先config.model_overrides?.[agentType] 命中则直接返回(全限定 ID 直接绕过整个档位机制);
  2. 关闭/缺省回退dynamic_routing 缺失、非对象或 enabled !== true 时,等价调用旧解析器 resolveModelInternal(cwd, agentType)——这正是向后兼容性的实现点,编排器可以无条件调用新解析器而无需担心破坏旧行为;
  3. 缺档表回退tier_models 缺失时无法路由,回退 resolveModelInternal
  4. 未映射 agent 回退AGENT_DEFAULT_TIERS[agentType] 不存在或不在 VALID_AGENT_TIERS 内时回退,避免静默选错模型;
  5. 升级封顶max_escalations 仅在"非负整数"时生效,否则默认 1escalate_on_failure !== false 判定升级是否启用(见 4.2);
  6. 沿档位链步进:从 defaultTier 出发循环调用 nextTier effectiveAttempt 次,next 为空或等于当前档(已到顶)即 break;
  7. 末位兜底tierModels[tier] 为空串或非字符串(配置缺失槽位)时回退 resolveModelInternal,绝不返回空模型 ID。

4.2 升级流程与 kill-switch

官方配置文档给出的编排器交互流程为:

1. Orchestrator spawns agent → resolver returns tier_models[default_tier]
2. Soft failure?
   ├─ no → ✓ done (cheap path)
   └─ yes → orchestrator re-spawns at attempt+1
            → resolver returns tier_models[next_tier_up]
            → cap at max_escalations
3. Hard failure (exception/crash) → bypass escalation, surface immediately

其中 escalate_on_failure: false 是升级的 kill-switch。这一点源于一次代码评审中的 Major 级发现(CR Major #3031,见 tests/feat-3024-dynamic-routing.test.cjs#L253-L277):若某个编排器盲目地在每次重试时递增 attempt 计数器,即使用户通过 escalate_on_failure: false 明确退出升级,也会被静默升级。修复后,当开关为 false 时,无论 attempt 计数多高,每一次解析都被拉回默认档(light → haiku)。

4.3 兜底回退一览

综合源码可得到以下安全回退矩阵(均收敛到旧的静态解析 resolveModelInternal):

触发条件 后果
整块缺失或 enabled: false 完全等价旧解析器(向后兼容)
tier_models 缺失 无法路由,回退
agent 未声明默认档 / 档位非法 回退,不静默选错模型
tier_models[resolvedTier] 槽位为空 回退,不发出空模型 ID
attempt 超过 max_escalations 返回封顶档模型(不无休止升级)

五、五层优先级:动态路由与其他模型配置的合成

dynamic_routing 并非孤立存在。它加入后,agent 最终模型的裁决链自上而下为:

  1. model_overrides[<agent>] —— 每 agent 精确覆盖,接受全限定 ID,优先级最高,且"override 永远赢":即使在已升级的 attempt 下,命中覆盖的 agent 依旧返回覆盖模型;
  2. dynamic_routing.tier_models[<tier>] —— 启用动态路由时按档位选择(本次新增,升级感知);
  3. models[<phase_type>] —— 粗粒度的阶段级档位(#3023),六个阶段槽位 planning / discuss / research / execution / verification / completion
  4. model_profile —— 当前 profile(quality / balanced / budget / adaptive / inherit)下每个 agent 的档位列;
  5. 运行时默认 —— 兜底。

官方文档将这五层概括为"自顶向下叠加":model_profile 是基底档位,models.<phase_type> 在阶段层面覆盖,dynamic_routing(启用时)在软失败时逐次升级,model_overrides 在最上层雕刻每 agent 例外,最后才是运行时默认。例如当 models.research: "opus" 而某 research 类 agent(如 gsd-codebase-mapper)默认档为 light 且动态路由启用时,tier_models.light(haiku)胜出——动态路由的档位优先级高于阶段级 models

详细的优先级定义同时写在两处权威位置:实现注释 core.cjs#L1365-L1370,以及配置文档 docs/CONFIGURATION.md#L943-L951

六、验收标准与测试佐证

feature #3024 的行为契约被类型化地固化在 tests/feat-3024-dynamic-routing.test.cjs(对 resolveModelForTier / isValidConfigKey 的返回值做结构断言,而非 grep stdout)。各验收维度包括:

  • schema 完备性AGENT_DEFAULT_TIERS 非空,VALID_AGENT_TIERS 恰好为 {light, standard, heavy}MODEL_PROFILES 中每个 agent 都有合法默认档(L74-L96);
  • nextTierlight→standardstandard→heavyheavy→heavy,非法输入返回 nullL100-L116);
  • 关闭模式是 no-op:无块或 enabled: false 时,无论 attempt=0/1 都等于 resolveModelInternalL120-L149);
  • 启用模式的档位选择:attempt=0 返回 tier_models[default_tier](如 gsd-codebase-mapper 为 light → haiku,gsd-planner 为 heavy → opus)(L158-L176);
  • 升级行为:light agent 在 attempt=1 升到 standard 档模型,standard agent 升到 heavy 档模型(L178-L198);
  • 封顶行为max_escalations: 1 时 attempt=2 与 attempt=5 都停在 attempt=1 的档位;heavy agent 永不越顶;省略 max_escalations 时默认 1L200-L251);
  • kill-switchescalate_on_failure: false 时 attempt=1/5 一律返回默认档模型(L255-L277);
  • 优先级裁决model_overrides 胜过动态路由(attempt=0/1 均返回覆盖 ID);动态路由胜过 models.<phase_type>L298-L332);
  • schema 校验:合法键全部放行,非法档位与未知键被拒,裸 dynamic_routing 不可作为配置写入目标(L336-L365)。

七、实战选型与成本控制建议

官方文档把三档模型与四类需求做了对应:

你的诉求 使用
全 agent 统一档位策略 model_profile
粗粒度阶段级调优("planning 用 opus,其余用 sonnet") models.<phase_type>
每 agent 精确控制(全限定 ID) model_overrides
默认廉价、失败才升级 dynamic_routing

综合前文可以提炼出几条可落地的组合策略:

  1. 纯成本杠杆场景:仅配置 dynamic_routing 块,保持 model_overridesmodels 为空。此时 33 个 agent 按档位归队,只有真正卡住的重试任务才会短暂升配,且 max_escalations: 1(默认)确保单次任务至多升一档,成本可预期;
  2. 叠加例外场景:对少数必须恒定使用特定模型的 agent(例如要求全限定 ID 的非 Claude 运行时),用 model_overrides 打补丁——它永远优先于档位机制,不受升级影响;
  3. 验证回退场景:把 dynamic_routing 整块删除或置 enabled: false,即可在任意时刻回退到 feature #3024 引入之前的静态解析行为,验证差异、做 A/B 成本对比时尤其有用;
  4. 彻底关停升级:保留 enabled: true 但设 escalate_on_failure: false,等于"仅按默认档路由、永不动用备用预算",适合预算极敏感的无人值守批次。

需要留意的是:所有档位别名最终仍会通过 runtime-aware 解析(runtime 已设置时)映射为对应运行时的原生模型 ID,因此在 Codex、OpenCode、Gemini CLI 等运行时下,动态路由的档位语义(opus/sonnet/haiku 别名按 runtime 映射)保持一致,无需为每个运行时重写 tier_models。变更说明中亦强调该块"与 model_overrides(更高优先级)和 models.<phase_type>(更低优先级)组合使用,可获得完整的成本控制灵活性"。


参考资源:功能变更说明 .changeset/dynamic-routing.md;配置总览 docs/CONFIGURATION.md;解析器实现 get-shit-done/bin/lib/core.cjs#L1356-L1442;档位常量与 nextTier 定义 get-shit-done/bin/lib/model-catalog.cjs#L50-L98;再导出层 get-shit-done/bin/lib/model-profiles.cjs;feature #3024 行为测试 tests/feat-3024-dynamic-routing.test.cjs

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391