跳转到内容
幸运的蜗牛 Logo 幸运的蜗牛
EN

你的 AI 并不“笨”,它只是需要一个更好的 Harness

/ 25 分钟阅读 /
#ai #harness engineering
目录 Table of Contents

你的 AI 并不“笨”,它只是需要一个更好的 Harness

TL;DR:Agent 之所以失败,通常不是因为模型太弱,而是因为系统没有被定义清楚。

一个好的 harness 会做好四件事:

  • 限制模型能做什么
  • 外化模型必须记住什么
  • 验证它采取的每一步
  • 在出错时恢复执行

问题所在:十步崩塌

想象一下,你部署了一个自治 Agent 去编译一份市场研究报告。第 1 到第 3 步一切正常:它完成任务规划、搜索网页,并提取竞争对手数据。

但到第 7 步,它开始幻觉出一些统计数字,因为搜索工具返回的 payload 超过了上下文窗口,并且被静默截断了。等到第 10 步,它又输出了一段损坏的 JSON,因为整个链路里根本没有 schema 校验器。最终,整条流水线直接崩溃。

我们都见过这种“agentic collapse”。而在这种时刻,人们很容易把问题归咎于模型的推理能力。但在真正的生产级 AI 系统里,问题通常不在马本身,而在缰绳。

根因:AI 工程范式的转移

过去两年,行业一直把 AI 失败当成一种“沟通问题”。如果模型答错了,我们就以为自己只是问得还不够好,或者喂给它的文档还不够对。但在长时程、自治执行的场景里,这类方法很快就会撞上天花板。

我们正在进入 Harness Engineering 的时代,也就是围绕模型来设计系统的工程学科。一个 Agent 不只是 LLM 本身。它是被嵌入在一整套严格脚手架之中的 LLM,而这套脚手架包括代码、状态管理和恢复流程。

这个领域的演化大致如下:

时代关注点局限
Prompt Engineering指令:怎么提问脆弱;跨步骤没有持久性
Context Engineering信息:该知道什么,比如 RAG无状态;无法控制长时程执行
Harness Engineering系统设计:如何约束与运行解决连续、多步执行的控制问题

每一个新阶段都不是替代前一个阶段,而是把它吸收进来。好的 harness engineering 依然需要好的 prompt 和好的 context,只是它补上了真正的执行层,而这正是前两者都无法提供的。

接下来的自然问题就是:这个执行层究竟长什么样?

不是概念上的样子,而是结构上的样子。如果模型不再等于系统本身,那么它在系统中的位置是什么?它外面包着什么?又是谁在约束它?

从高层看,一个生产级 Agent 系统大概是这样的:

┌─────────────────────────────────┐
│ User Request │
└────────────────┬────────────────┘
┌─────────────────────────────────┐
│ HARNESS (7 layer stack) │
│ ┌───────────────────────────┐ │
│ │ LLM (The Model) │ │
│ └───────────────────────────┘ │
└────────────────┬────────────────┘
┌─────────────────────────────────┐
│ Verified Output │
└─────────────────────────────────┘

模型是被放在 harness 里面 的。它不会直接对用户说话,也不会在没有监督的情况下直接接触外部世界。所有输入在进入之前都要被过滤,所有输出在离开之前都要被验证。


一个好 Harness 的设计原则

在深入具体层次之前,先明确一下每一个设计决策都应该遵守的原则。当你不确定自己的 harness 是否真的在起作用时,就回到这四条标准上。

1. 要靠约束,不要靠劝说。
如果你能通过程序限制模型的选择范围,就不要指望它“自己选对”。一句“请始终输出合法 JSON”只是期望;一个会拒绝非法输出的 schema 校验器才是保证。

2. 把状态外化。
凡是对任务连续性重要的信息,无论是已经做了什么、还剩下什么、哪里失败了,都必须存在于上下文窗口之外。上下文窗口是易失的;磁盘文件不是。

3. 让每一步都可验证。
如果没法检查,就没法信任。harness 的每一层都应该产出能够被“生成它的那个模型之外”的东西所验证的结果。

4. 局部失败,不要全局失败。
一次工具调用失败,应该只触发当前步骤的重试,而不是让整条流水线从头再来。失败的爆炸半径,应该尽可能小。

这些并不是抽象的理念,而是会直接影响实现方式的工程约束。你会在后面的每一层里不断看到它们的影子。


七层 Harness Stack

一个健壮的 harness 并不只是把文本在不同模块之间来回传递。它调度的是一个有类型、有状态、可观测的系统。一个真正能上生产的栈,大致长这样。

1. Cognition

这是最底层,也是定义模型操作边界的一层。你不应该给模型塞一个包罗万象的大系统提示,而应该给它一张与当前任务局部相关的“地图”:它现在扮演什么角色、成功标准是什么、有哪些严格的负向约束,也就是不能做什么

本质上,这更像是在给模型一份岗位说明书,而不是给它一套百科全书。

实践里,这通常表现为结构化的系统提示、角色文件(比如 agents.md),或者按当前步骤动态生成的任务说明。

2. Tools

harness 不应该把原始工具输出直接一股脑塞回给 LLM。它应该作为一层严格的中间件,至少承担以下工作:

  • 排序:通过 embedding 相似度或 BM25 之类的方法,只保留最相关的结果
  • 去重:在进入上下文之前,先去掉重复信息,避免浪费 token
  • Token 预算截断:对工具返回做硬上限,避免上下文溢出,这正是文章开头那个例子里导致失败的直接原因

3. Contracts & Interfaces

这是大多数团队最容易跳过,但也是最容易在生产里出事的一层。

模型说话的方式是概率性的,而 harness 说话的方式必须是类型化的。

系统里的每一个边界,无论是 LLM 和工具之间、一个 Agent 和另一个 Agent 之间,还是 harness 和外部世界之间,都应该有一个明确的 contract:严格的 JSON schema、类型化函数签名,或者版本化 API 规范。

没有这层,你就会遇到 schema drift。比如某次模型把 price 输出成字符串,另一次又输出成浮点数,下游流程不一定立刻报错,但最终结果会悄悄变脏。

contract 层的职责就是在每一次跨边界传递时做输入输出校验,在错误传播出去之前就把它拦下来。这正是“靠约束而不是靠劝说”的真正落地方式。没有 contract,你很容易在不知不觉中把错误 schema 扩散到后续系统里。

4. Orchestration

如果没有这一层,LLM 很容易出现无限循环、跳过关键步骤,或者过早宣布任务完成的问题。

harness 应该显式强制一个工作流结构,比如一个 DAG,或者一台状态机,来规定合法的迁移路径:Plan → Gather → Draft → Verify。模型可以提出动作建议,但真正决定哪些动作被允许的,是 harness。

5. Memory & State

状态必须被显式管理,否则系统迟早会“失忆”。一个成熟的 harness 通常把记忆拆成两层:

  • 工作记忆(短期):当前步骤需要用到的即时对话和上下文窗口
  • 持久状态(长期):结构化文件,比如 state.json,用来准确记录哪些子任务还在等待、哪些进行中、哪些已经完成。它能够跨越上下文重置,甚至跨越不同会话而存活

这正是“把状态外化”在工程里的具体体现。如果一条信息只存在于上下文窗口里,它迟早会丢。

6. Evaluation & Observation

一个系统不能只靠“再来一条 LLM prompt”完成验证。评估层必须是异构的:

  • 规则校验:比如校验 JSON schema、字符串长度、必填字段是否齐全
  • 工具校验:比如把代码丢给编译器、运行测试套件,或者通过浏览器自动化(比如 Playwright)真正去点一遍 UI
  • LLM-as-judge:只在主观或语义层面的判断里使用,比如语气、连贯性、是否友好,这些没法靠确定性规则检查

7. Constraints & Recovery

在自治环境里,工具失败、API 超时不是例外,而是常态。

harness 必须强制 幂等性。如果某一步失败了,系统应该只重试这一步,而不会破坏整体状态,也不会把之前已经完成的工作重复做一遍。这就是把一个脆弱的 demo 变成一个有韧性的系统的关键,也是“局部失败,不要全局失败”的工程化版本。


一个完整的 Agent 运行示例

为了更直观地看到这些层如何阻止系统崩塌,我们来走一遍完整流程,用前面的市场研究 Agent 举例,并且包含一次真实失败。

Step 1:用户请求
“比较竞争对手 A 和竞争对手 B 的定价。”

Step 2:编排与状态
Planner LLM 把这个任务拆成一个 DAG,其中有两条可以并行的分支。state.json 把“抓取竞争对手 A”标记为 IN_PROGRESS

Step 3:工具调用
LLM 触发网页搜索。Tool 层抓回 50 条结果,做 BM25 排序,去掉重复片段,再只返回前 3,000 token,保证整体仍然在预算范围内。Contract 层随后校验工具输出是否符合预期 schema,再交给模型。

Step 4:评估
LLM 生成了定价数据。Evaluation 层运行规则式 schema 检查,发现 JSON 里缺少必填字段 currency

Step 5:恢复
harness 在用户看到错误之前就把它拦下来了。因为这个动作是幂等的,它会把精确的错误 trace 回传给 LLM,让模型只对这一小步做本地重试,而不是整条流水线重头跑一遍。

Step 6:状态更新
修正后的数据通过验证。state.json 把竞争对手 A 标记为 COMPLETED,harness 接着处理竞争对手 B。

Step 7:硬失败
网页搜索工具对竞争对手 B 返回了空结果,原因是目标网站挂了。harness 检测到空 payload,于是记录失败,并触发兜底方案:改用另一个查询词重试。关键是,在这之前 state.json 不会被更新,也不会写入任何半成品或损坏数据。

Step 8:兜底成功
替代查询返回了有效结果。Contract 层校验 schema,Evaluation 层确认所有必填字段齐全,直到这时 state.json 才把竞争对手 B 标记为 COMPLETED

这个循环会在长任务里重复几十次甚至上百次。和文章开头那个“十步崩塌”不同,在这里即使工具真的失败了,系统也能自己吸收冲击并恢复。没有幻觉,没有静默失败,也没有整条流程直接崩掉。


四个前线经验:高级陷阱

当你把这套架构扩展到可以连续运行几小时的规模时,一些单靠 prompt 调优永远解决不了的新故障模式就会冒出来。下面这四类,是团队在生产里最常踩到的坑。

陷阱 1:Context Anxiety

当 Agent 持续工作、上下文窗口逐渐被填满时,模型往往会表现出一种工程师们常说的“context anxiety”。

当 token 使用逼近上限,通常超过容量的 70%,或者延迟突然升高时,模型会开始跳步骤,或者提前结束任务。它看起来像是在赶时间,仿佛自己也感受到了“墙正在逼近”。

解决办法:
原地做摘要并不够,因为这依然让模型继续运行在一个嘈杂、退化过的上下文里。更稳妥的方式是执行 Context Reset。harness 应该监控利用率,并在程序层面触发重置:

# 这个阈值来自经验,应根据模型和具体负载进行调整。
if (tokens_used / max_context) > 0.7:
save_state_to_disk(state)
terminate_current_instance()
launch_fresh_agent(state)

harness 会把当前精确的项目状态保存到持久存储里,终止当前 LLM 实例,然后拉起一个上下文完全干净的新 Agent。这个新 Agent 重新读取状态文件,自我定位后继续往下执行。这样做成本更高,但对超长任务而言,可靠性会高很多。

陷阱 2:自我评分幻觉

如果你让 AI 给自己的工作打分,它通常会带着不应有的自信,通过一些其实很一般的输出。

这不是某个模型特有的 bug,而是一个结构性缺陷。生成结果的那组权重,本来就不适合再去批评它自己刚刚产出的内容。

解决办法:
Sprint Contract 来强制职责分离。在工作开始前,让 Generator Agent 和一个独立的 Evaluator Agent 先对“完成”的标准达成一个具体、可验证的定义。其中有两条规则不能妥协:

第一,Evaluator 必须真的 执行。它应该运行代码、用无头浏览器验证界面,或者把输出拿去做 schema 校验,而不只是读一遍文本然后给出评价。只有无法伪造的验证,才算验证。

第二,Evaluator 必须在干净上下文里工作,而不是直接读取 Generator 的完整推理轨迹。否则它会继承 Generator 的假设和盲点,这会直接破坏“独立审查”的意义。给 Evaluator 的应该只有输出结果和成功标准,而不是 Generator 的整个思维过程。

陷阱 3:优化“看起来正确”的幻觉

当一个 LLM 被置于不可能或者互相冲突的约束中,比如“修复这个 bug,但不要改任何代码”,或者“把它缩短,但又要保留全部信息”,实践者们反复观察到一种固定行为模式:

模型不再真正解决问题,而是开始优化“看起来像是正确的”。结果会变得流畅却空洞:编造数据、逻辑表面合理但实际不可运行,或者形式上满足 prompt 字面要求、却背离真正意图。

关于 steering vectors 和模型内部表征的最新研究,包括 Anthropic 对语言模型内部状态的探测,都说明这可能不仅仅是表层文本预测出了偏差。模型在面对冲突压力时,内部状态似乎真的会发生可测量的变化,尽管这个方向还处在早期阶段。

解决办法:
实践层面的结论很直接。LLM 是沿着当前上下文轨迹继续预测下一个 token 的。如果你的 harness 回传给它的是带情绪、带攻击性的错误信息,比如“你太蠢了,这完全不对”,那么你实际上是在把上下文往“失败叙事”上推,模型后续输出的质量通常也会进一步退化。

harness 回馈给模型的内容必须是严格客观的:编译错误、断言失败、schema 不匹配。给模型一个待解决的问题,而不是给它一个需要摆脱的坏名声。

陷阱 4:记忆整合循环

如果 Agent 要作为一个长期运行的系统存在,那么持久状态管理绝不是一次性配置完就结束的。

随着时间推移,记忆日志会越来越臃肿、越来越矛盾:旧决策和新决策互相冲突,重复条目在每一次读取时都浪费 token。

一些生产级 Agent 系统采用了一种通常被称为 Memory Consolidation 的方式,也就是定期自动处理并压缩 Agent 积累下来的工作日志。公开资料和一些开源框架的实践都显示,这种模式效果非常明显。一个有代表性的案例是:某个 harness 把 32K token 的嘈杂重复历史,压缩成了一个只有 7K token 的干净状态文件,而信息几乎没有损失。

解决办法:
实现一个自动化的 consolidation 循环。当 Agent 空闲时,比如任务之间,或者低优先级时间窗口里,触发一个后台任务,读取原始日志、去重、以最新信息为准解决冲突,然后写回一份干净且压缩后的状态文件。这样系统在下一次运行时会更快、更便宜,也更准确。你可以把它理解成 AI 工作记忆版本的“磁盘碎片整理”。


从哪里开始:最小可用 Harness

如果这套七层栈让你觉得压力很大,不要试图第一天就把所有东西都做出来。先从第 7 层,也就是“约束与恢复”开始,再一路往回补。

你可以容忍 prompt 还不够完美,也可以容忍工具集成暂时比较粗糙。但你无法容忍一个会在失败时破坏自己状态、或者悄悄吞掉错误的 Agent。

一个第一天就该具备的 harness,至少应该包括这四个东西:

  • state.json:一个记录任务状态的结构化文件。即便进程死掉,你也知道该从哪里继续
  • Retry wrapper:每一次工具调用都包在 try/catch 里,至少带一次自动重试和指数退避
  • Schema validator:每一个 LLM 输出都先过 JSON schema 校验,格式错了就触发重试,而不是直接把系统打崩
  • Tool output truncation:对所有工具返回做硬性 token 上限。上下文窗口里的静默截断,是导致幻觉最常见的来源之一

这四个组件一个下午就能搭起来。一旦你的 Agent 学会了“优雅地失败”,你才真正获得了继续把它做聪明的资格。

结语

软件的未来正在转向 agent-first。

随着模型逐渐具备自主生成和验证复杂系统的原始能力,人类价值的重心也在发生转移。未来最重要的,不再只是会不会写语法,而是能不能设计出一套让自治执行保持可靠的约束系统。

未来十年最成功的构建者,不会是写代码最漂亮的人,而会是把 harness 做得最好的人。他们会为最快的马打造最稳的缰绳。而这些缰绳,本质上都只是几条原则的持续贯彻:约束、外化、验证、恢复。


如果你想看每一层背后的实现细节,比如状态存储、验证节点、Sprint Contract,以及应该从哪里起步,可以继续阅读配套 FAQ:从理论到生产:Harness Engineering 落地指南