Agent 的成败,一半在模型之外:谈谈 Harness

ppylox
·10/7/2026·17 阅读

过去两年,关于 Agent 的讨论几乎都集中在模型侧:参数规模、推理能力、上下文窗口、工具调用准确率。但如果你真正把一个 Agent 从 demo 推到生产环境,很快会发现一个尴尬的事实:

同一个模型,换一套运行框架,产品体验可以差出一个数量级。

有的 Agent 能连续工作几小时、几十次工具调用而不崩;有的跑到第七轮就开始胡言乱语,或者陷入"调用—失败—重试"的死循环。模型没变,变的只是它外面那层"脚手架"。

这层脚手架,业界通常叫 Harness(运行框架)。一句话概括:

Agent = Model + Harness

模型提供"智能",Harness 提供"能持续做出可靠行为的能力"。本文拆解后者到底在做什么,以及为什么它值得单独被当成一个工程问题来对待。


一、Harness 的五个核心职责

一个严肃的 Harness,至少要解决下面五类问题。它们彼此耦合,缺一个都会在长期运行中暴露。

1. 上下文管理:不是"塞得下",而是"塞得对"

上下文窗口从 8k 涨到 256k,很多人以为问题解决了。实际上窗口越大,浪费越大——你付的是"每轮重复计费"的钱。

关键设计点:

  • 压缩阈值与保留策略要分开。触发器可以是"占窗口比例"(如 75%),但"保留多少逐字对话"必须独立控制。只按"最近 N 轮"保留是错的——轮次和体积无关,一轮里可能塞了一篇 10 万字的文档。
  • 逐字尾巴要双重约束:既限制轮数,也限制 token 数。实测中,"保留最近 6 轮"在极端情况下压完仍占阈值 94%,等于白压。
  • 缓存友好:压缩会打断 KV cache。所以压缩频率和缓存命中率是一对矛盾指标,需要一起盯——高命中率(90%+)说明前缀稳定,压得太勤反而更贵。
  • 工具输出是真正的膨胀源。在一个真实运行记录里,工具输出占了全部消息的 88%(14,894 条 / 全库)。单条不大,但条数极多。所以"折叠"要有选择性:优先丢重复的成功输出,保留错误痕迹和可恢复指针(文件路径、命令、URL)。

2. 工具契约:容错要前置到入口

模型调用工具时拼错参数,是最常见的失败形态,而且往往集中在特定工具上。

一个反直觉的经验:同一个错误会以不同面貌重复出现。比如模型把参数包进了一个信封:

{"name": "http_request", "args": {"method": "GET", "url": "..."}}

而工具期望的是扁平参数。校验器会报"未知参数 ‘args’"。这类问题在多个工具上先后出现过,集中在联网类工具——因为模型对它们的参数结构记得最糊。

解决方式不是逐个工具打补丁,而是在入口做统一解包,但必须严格设条件。只解"信封形态确实成立"的调用,至少要同时满足:

  1. 顶层键恰好是 {name, args} 两个;
  2. name 与当前工具名一致;
  3. args 是对象;
  4. 当前工具的 schema 里没有真的叫 name / args 的参数。

第 4 条最容易漏——有个用于转发调用的"桥接工具",它的 schema 恰好就是 name + args。少这条条件,就会把正常调用弄坏。

另一个原则是错误信息要"可学习":报错时带上正确用法(真实列名、允许的参数清单),模型下一轮就能自我纠正。含糊的报错只会换来同样的错误。

3. 停止条件:防止"热情过头"

Agent 最常见的失败不是"不够努力",而是没有节制。所以 Harness 必须有不依赖模型的刹车:

闸门 作用
步数上限 限制迭代轮次
工具调用上限 限制动作次数(与步数是两个独立维度)
墙钟上限 兜底总时长
成本上限 兜底花费

有个坑值得单独说:成本上限的判定通常是 cost >= limit,所以设成 0 会立刻判死,与"不限制"的直觉正好相反。想关闭它,只能设一个实际不可达的大值。

还有一个更隐蔽的问题:观测字段的"假承诺"。比如记录"这一轮是被哪道闸拦下的",如果写入时机在"模型刚返回、工具还没跑"的时刻,那时闸门根本还没评估,字段只能是空的。正确做法是在否决发生后回填,而且要覆盖两条路径——"继续重试"那条,以及"次数用尽判 NO_PROGRESS"那条。只补前者,会精准漏掉所有被杀死的回合。

4. 权限与围栏:能力越大,越需要边界

Agent 能改文件、执行命令,所以"它能碰什么"必须是显式配置,而不是约定。

实践中会暴露一个结构性缺口:文件写入工具通常带路径信息,可以静态拦截;但 shell 工具没有。于是围栏能挡住"写文件",却挡不住"用 shell 写文件"。

可靠的补法是外观测:执行类工具运行前后各拍一张项目根快照,发现围栏外的改动就告警并落事件。注意三点:

  • 只告警、不拦截(shell 的能力面太宽,没有可靠的静态拦截点);
  • 必须排除持续写入的目录(日志、spool),否则每条命令都误报,告警立刻变噪音;
  • 在真实项目上标定开销:约 400 个文件、单次快照数毫秒,每条命令多 10ms 左右——完全可以接受。

5. 记忆与自进化:把"这次学到"变成"下次会用"

如果一个 Agent 每次都从零开始,它永远只是"一次性的聪明"。

需要三层:

  • 会话内:上下文与摘要;
  • 跨会话:长期记忆文件(问答稳定的事实、用户偏好);
  • 程序性:从成功轨迹里抽出可复用的流程,审批后变成技能。

这里最容易出问题的是晋升逻辑的"对象选择":比如"取最新候选"时取错了对象,会让门禁永远判在错误的东西上。这类 bug 不报错,只是安静地不生效——比崩溃更难发现。


二、一个真实的成本结构

把上述能力都装上之后,你会看到一组很有意思的数字(来自一个长期运行的 Agent 实例):

  • 单次请求约 120k token,窗口上限 256k,压缩线在 192k;
  • 压缩 55 次,触发点稳定在 192k±2k——说明阈值控制是准的;
  • 但压后地板在 24k–180k 之间剧烈波动——最坏一次压完仍占阈值 94%,等于没压;
  • 缓存命中率 96–98%——前缀稳定性做得不错。

这组数字给出的结论很清楚:

“触发阈值准"不等于"压缩有效”。 决定效果的是"压完之后还剩多少",而不是"什么时候开始压"。

这也是为什么"逐字尾巴加 token 上限"这类小改动,往往比调阈值更能解决问题。


三、三个反直觉的结论

  1. 减少步数,比优化单步更划算。 合并独立的读取/查询动作、降低工具的失败率(比如改进失败提示的定位精度),收益远大于把每一步的 prompt 打磨得更精致。步数是乘数。

  2. 不要中途动态增删工具。 它破坏前缀稳定性、拉低缓存命中,而且会让模型的工具选择行为变得不可预测。工具集应该在回合边界变化。

  3. 窗口上限不是越大越好。 更大的窗口意味着更多"每次都重复付费"的 token。在缓存与压缩机制到位之前,加窗口只是把账单拉长。


四、结语

把 Agent 做好,难的从来不是"让模型调一次工具",而是让它在第 50 次调用时依然可靠、依然便宜、依然不越界。

模型的进步会持续抬高上限,但下限由 Harness 决定——它决定了 Agent 在真实环境里是"偶尔惊艳的玩具",还是"可以托付任务的工具"。

所以,当你下次评估一个 Agent 产品时,除了问"用的是哪个模型",更值得问的是:

它的 Harness 是怎么设计的?


本文基于一个长期运行的有状态 Agent 运行时的实测经验整理。文中数据来自该实例的埋点统计,不代表所有实现的普遍水平。

评论 (0)

暂无评论,来写第一条吧