基于模型能力打造 Harness:一套诊断与迭代框架
Agent = Model + Harness。Harness 是控制 Agent 行为最直接的杠杆,但大多数团队设计 Harness 的方式仍然是”看到别人用什么我们就用什么”。本文提出一套诊断框架,帮你找到特定模型的真实弱点,再决定该加什么 Harness 组件。
一、一个根本矛盾
看看目前主流的三个 Coding Agent 产品:
Claude Code(Anthropic)
- 极度精简的 Harness,依赖 Claude 本身的能力
- XML output contract + 有限的 Hook 系统(AGENTS.md)
- 几乎没有上下文压缩——假设 Claude 的 recall 足够好
- 设计假设:模型足够强,Harness 只做最小约束
OpenAI Codex CLI
- Rust 实现,多 Agent 架构(orchestrator + workers)
- 结构化轨迹日志,审批门控
- 更强的 Tool registry,明确的执行策略
- 设计假设:模型需要结构性引导,但不需要过度保护
Gemini CLI(Google)
- 深度绑定 Gemini 模型特性
- 不同的上下文窗口管理策略
- 与 Google 生态的集成
- 设计假设:利用模型特有的能力边界来设计交互方式
三个产品都在做”给 LLM 套 Harness”,但设计分歧反映了底层模型的差异。
这引出一个本质的问题:
如何系统性地判断一个模型需要什么强度的 Harness?
不是在 README 里写”支持多模型”,而是说出:“对于这个模型,你应该开 JSON repair,关 compaction,把 tool 执行改成 sequential。“
二、核心观点:模型对自己的行为没有特权访问
先做一个实验:
问一个 LLM:“你喜欢 read 还是 edit 工具?”
它大概率回答:“edit,因为它更精准。” 但如果你去统计它 100 次实际 tool call——如果 write 出现在它的训练数据里更频繁,如果 edit 的 patch 一旦冲突它不知道怎么办——它可能实际更倾向于 write。
self-report 和 behavior 之间的 gap 来自两条不同的路径:
self-report → 陈述性知识:"好的 agent 应该先用 read 看文件,再用 edit 精确修改"
behavior → 程序性知识:当前上下文、之前 tool result 的状态、哪条路径 token cost 最低
两条路径可能在训练数据里就脱节了。就像一个程序员面试时说”我写代码前一定先写测试”,实际干活时直接 git push。
推论:验证模型适合什么 Harness,必须是行为测量,不是访谈。
三、诊断框架:四组维度测试
把模型放到一个最简 Agent loop 里——只保留 LLM call → tool result → 循环,不加任何约束。然后分别压测四个维度。
维度一:指令遵循稳定性
模型能否精确遵守 system prompt 里的多条规则?
测试设计:system prompt 里写 5 条明确规则(如 "永远先 read 再 edit"、"每次 tool call 后必须总结")
同一 prompt 重复跑 50 轮
统计指标:
- 规则违反率(每轮违反几条规则)
- 规则遗忘曲线(第几轮后开始忘记最初的规则)
- 按规则拆分的违反率(哪些规则比其他更容易被忽略)
结果解读:
违反率 > 20% → 需要结构化 output contract + repair loop
违反率 5-20% → 可能需要更精简的 system prompt(太长会稀释注意力)
违反率 < 5% → 这个维度不需要额外 Harness
维度二:结构化输出可靠性
模型能否稳定输出可解析的 JSON/XML?
测试设计:要求模型输出 100 次 JSON tool call
不 repair,直接记录每次解析结果
统计指标:
- 原生解析成功率
- 错误模式分布(缺少括号?字段名拼错?多余空格?)
- repair 后覆盖率(修复后有多少能救回来)
结果解读:
原生成功率 > 95% → 不需要额外处理
原生成功率 80-95% → 加一个轻量 JSON repair(正则修复已知模式)
原生成功率 < 80% → 需要完整的 structured output contract + retry loop
维度三:工具调用准确度
模型能否在正确的时候调用正确的工具,参数不出错?
测试设计:让模型完成 5 种不同类型的任务,每类 20 次
任务 mix:文件编辑、代码搜索、Shell 命令、网络请求、git 操作
统计指标:
- 不该调用时调用了(hallucinated tool call)
- 参数格式错误率
- 重复错误(给 error 后是否换方法)
- 工具选择错误(应该用 edit 却用了 write)
结果解读:
参数错误 > 10% → 需要 tool call pre-validation layer
重复错误 > 30% → 需要 structured error feedback
不该调用的比例高 → 降低 tool 的描述优先级,或减少可见工具数
维度四:上下文定位能力
模型在长对话中能否准确找到关键信息?
测试设计:运行 20 轮对话后问一个第一轮提到的信息
改变轮数(10/20/30 轮)和上下文大小(4k/8k/16k tokens)
统计指标:
- 召回正确率(是否找到关键信息)
- 定位所需轮次(需要额外追问几次才找到)
- 随轮数增加的衰减曲线
结果解读:
10 轮后 recall 开始下降 → 需要上下文压缩
20 轮后 recall 仍 > 90% → 不需要压缩(受限于 prefix cache 效率)
定位需要 > 2 轮追问 → 需要更有效的 memory/retrieval 机制
维度五(可选):自我纠错能力
给 model 返回 error 后,下一次它是否修正了行为?
测试设计:在工具返回里注入精心构造的 error
观察模型下一次调用
统计指标:
- 修正率(多少比例的 error 后下一次行为确实是修正的)
- 重复错误率(相同错误连续出现几次才修正)
- 过度修正(本来是参数问题,结果换了完全不同的策略)
结果解读:
修正率 > 80% → 模型可靠,error message 可以精简
修正率 < 50% → 需要系统级别的 fallback(如自动重试+参数调整)
过度修正频繁 → error message 需要更精确地指出问题位置
四、按失败模式配置 Harness
做完五组测试,你有一张失败模式画像:
模型 X 的失败模式画像:
✅ 指令遵循:违反率 8% → 精简 system prompt 即可
❌ 结构化输出:原生解析率 72% → 需要 JSON repair + structured contract
✅ 工具调用:参数错误率 6% → 不需要 pre-validation
❌ 上下文定位:12 轮后 recall 降至 60% → 需要 compaction
✅ 自我纠错:修正率 85% → error feedback 保持简洁
然后按画像组件选择:
| 失败模式 | 推荐的 Harness 组件 | 复杂度 |
|---|---|---|
| 指令遵循低 | Output contract + repair loop | 中 |
| 结构化输出低 | JSON repair + fallback parser | 低 |
| 工具调用不准 | Pre-validation layer + schema check | 中 |
| 上下文定位差 | Auto-compaction + summary + circuit breaker | 高 |
| 自我纠错弱 | Structured error format + retry orchestration | 高 |
最关键的原则:不要先入为主地加所有组件。每加一个组件问:它解决了哪个失败模式?ROI 是否可测量?
五、Trajectory Slicing:让调试从 O(n) 降到 O(1)
以上框架解决了”测什么”的问题,但还有一个工程问题:怎么迭代?
传统做法:改 system prompt → 重跑完整 trajectory(20 轮)→ 看结果 → 改 → 再跑 20 轮。一次 debug 循环半小时起步。
更好的做法:Trajectory Slicing
完整 trajectory(20 轮 LLM call + 20 次 tool call)
↓
切出第 n 步的完整输入:system prompt + 历史消息 + tool result[n-1] + tool descriptions
↓
作为单次 chat completion 发给不同模型 / 不同 prompt 变体
↓
只看这一步的输出变化
这个方法的好处:
- 消去累积误差:不需要跑完 20 步才知道改 prompt 有没有效,第 1 步就知道
- A/B 测试:同样输入给 Claude 和 DeepSeek,看 tool call 质量差异
- Regression test:记录关键节点的输入 + 期望输出,每次改完 Harness 跑一遍
- 定位从 O(n) 到 O(1):怀疑哪步有问题就直接拎那步出来测
要实现这个,agent loop 里每轮 persist:
{ turn_id, model, system_prompt, tool_defs, messages_so_far, tool_call_made }
然后给一个 replay 命令:输入 turn_id,不跑 agent,只跑 chat completion。
六、映射到三个产品的隐含假设
Claude Code:
- 维度一(指令遵循):几乎没有结构化 output contract → 假设 Claude 的指令遵循足够好,XML tag 只是格式约定,不是强制
- 维度二(结构化输出):依赖 Claude 原生 tool calling → 不 repair,不接受失败
- 维度三(工具调用):精简工具集(read/write/edit/bash 等),不做过多的 pre-validation → 假设模型知道该调什么
- 维度四(上下文定位):没有后端 compaction,仅靠 context window + 对话管理 → 假设 Claude 在大上下文中 recall 足够可靠
隐含前提:Claude 够强,所以 Harness 够薄。 这套策略在 Claude 上成立,但如果换到弱模型上,薄 Harness 的结果就是频繁出错。
Codex CLI:
- 维度一:明确的审批门控 + task decomposition → 承认模型在复杂任务中需要结构性引导
- 维度二:结构化轨迹日志 → 做了 instrumentation,但未必用来 repair
- 维度三:Tool registry 有明确的执行策略(允许/拒绝/审批) → 控制面比 Claude Code 更丰富
- 维度四:多 Agent 架构(orchestrator + workers)→ 通过架构而非 compaction 解决上下文限制
隐含前提:模型需要结构,但不需要怜悯。 Codex 的 Harness 比 Claude Code 更厚重,但厚重的地方是控制和隔离,不是容错。
Gemini CLI:
- 维度一和四受到 Gemini 模型自身特性驱动——更长的 context window 和不同的 prefix caching 行为
- 维度二和三则更紧地绑定了 Google 的生态工具
有什么共同点?三者在设计 Harness 时,都做了关于模型能力的隐含假设,只是没有说出来。这些假设决定了 Harness 的厚度、风格、和局限性。
如果把这些假设暴露出来、用测试数据验证,就能做出更理性的设计决策。
七、总结
写这篇不是要否定哪个项目。恰恰相反——两个项目都做出了有价值的东西,只是他们的设计假设不同。
核心想说的是:
-
Harness 是杠杆,不是目标。每个组件的价值取决于它解决了哪个具体问题。
-
测量行为,不询问意见。模型 report 和实际 behavior 之间的 gap 是系统性的,只能通过 instrumentation 桥接。
-
Trajectory slicing 让 debug 循环从 O(n) 降到 O(1)。这是你能否快速迭代 Harness 的工程前提。
-
模型升级后要重新测量。DeepSeek V3 到 V4 的结构化输出能力差了多少?如果不知道,你的 Harness 就是对着空气打拳。
最差的决策是:因为 Claude Code 用了 XML contract,所以我们也用;因为 OpenCode 用了 EventLog,所以我们也用。要对你的实际模型说话,不要对 benchmark 说话。