跳到主要内容

你的错误信息现在成了 Prompt:为 AI Agent 编写失败输出

· 阅读需 12 分钟
Tian Pan
Software Engineer

统计一下你的堆栈跟踪(stack traces)的阅读者。对于大多数内部工具,过去的答案通常是“偶尔有一位疲惫的工程师”。如今,你的错误输出的最大阅读者几乎肯定是一个处于重试循环中的语言模型。编程智能体(Coding agents)每天成千上万次地阅读你的 linter 警告、CLI 使用说明字符串、API 错误主体以及测试失败信息——频率远高于任何人类。而且与人类不同,智能体会字面理解每一个词。

这改变了错误信息的“本质”。它不再仅仅是失败的文档,而是注入到下一次尝试的上下文窗口中的指令——这是你几个月前编写的提示词,现在正引导着一群你从未见过的智能体集群。一个精确的错误能让循环在一次重试中收敛。而一个模糊或误导性的错误则会让智能体陷入恶性循环:错误的修复、--no-verify 的权宜之计、幻觉出来的参数标识、消耗殆尽的 token。如果你维护着一个工具、一项服务或一个构建系统,你其实已经在进行提示词工程(prompt engineering)了。你只是在错误字符串中进行的,而且很可能是无意为之。

重试循环是一场你未曾察觉的对话

当人类遇到错误时,错误信息只是众多输入中的一个。他们对系统有一个心理模型,可以询问同事,或者直接 Google 一下。错误信息只需要“足够好”,能指引有知识的读者找到正确方向即可。几十年来,糟糕的错误信息之所以能存在,正是靠这种宽容。

智能体没有这些退路。当工具调用失败时,错误主体通常是进入循环的唯一新信息。智能体阅读它,更新计划,然后立即、按字面意思地、以机器速度执行。它不会耸肩。无论你的错误信息说了什么,那都会成为智能体对出错原因的定论。

这就是为什么误导性的错误比以往代价更高。看看这些经典案例:

  • 推卸责任型错误:“无效请求(Invalid request)”。哪里无效?智能体开始猜测,随机更改一个字段,然后重试。五次尝试后,它已经探索了一系列错误的假设,而这些假设本可以通过你的一句话来消除。
  • 过时的提示:“你是想输入 --force 吗?”——这里的建议逻辑自两个版本前就没更新过。人类可能会注意到该参数标识已不存在。智能体会原封不动地运行它,得到第二个错误,现在它的上下文中有了两层困惑。
  • 虚假的成功:退出代码(exit code)为 0,但 stderr 中埋着一条警告。人类偶尔能发现这一点。智能体几乎永远发现不了——退出代码是它们循环中最强的信号,而你刚刚告诉它们一切正常。

这些情况以前就很糟糕。新出现的是乘数效应。一个流行 CLI 中令人困惑的错误过去只会浪费人类零散的几分钟注意力。现在,它被每一个接触你工具的智能体集群视为指令执行,在每一次重试中,永远持续——直到你修复那个字符串。

能让智能体收敛的错误信息剖析

有效的错误信息都有共同的特征,看起来非常像一个好的提示词:陈述发生了什么,提供相关上下文,并告知下一步该怎么做。具体来说,一条易于智能体理解的错误信息包含四个要素。

命名的、稳定的错误类型InputValidationErrorRateLimitedFileNotFound——一种智能体可以进行模式匹配的分类。构建智能体工具的从业者一致认为,将错误分类并提供特定指导,效果远好于通用的“请重试”。因为“请重新读取文件,自你上次读取后它已更改”是可操作的,而“操作失败”则不是。

回显出错的数值。不要只说“无效参数”,而要说“参数 regionus-esat-1;有效值为 us-east-1us-west-2”。回显输入可以消除智能体“认为”发送的内容与你“收到”的内容之间的差距——这种差距对智能体来说是不可见的,而且是极其常见的根本原因,因为模型在传输过程中经常会弄乱字符串。

具体的下一步行动。这是将错误从死胡同转变为提示词的关键。“请先运行 auth login。”“30 秒后重试。”“缩小查询范围;此次查询返回了 14,000 行,而限制为 50 行。”Anthropic 给工具构建者的建议直接指出了这一点:不要只提供一个干巴巴的 TOO_MANY_RESULTS,要告诉智能体去分页或过滤——错误信息应该引导智能体进行更好的调用,而不仅仅是拒绝当前的调用。

针对已知陷阱的明确禁令。在急于取得进展的压力下,智能体会采取权宜之计——跳过钩子(hooks)、删除锁文件、强制推送(force-pushing)。如果某个失败模式有一个诱人但具有破坏性的“修复方法”,请在错误信息中说明:“不要删除迁移文件;请运行 migrate repair。”系统提示词中的负面引导会在长对话中衰减,但在出错瞬间提供的负面引导则能精准命中。

在 API 上下文中,还有一个字段非常有价值:可重试性(retryability)。一个布尔值 retryable(或具有机器可读类型的 RFC 7807 风格的结构化主体)可以让调用框架决定是重试、退避(backoff)还是移交给人工处理,而无需为了这个问题消耗模型调用。确定性包装器可以处理的结构化数据,比模型必须解读的文字描述成本更低。

冗长陷阱:你的堆栈追踪正在吞噬上下文窗口

相反的失败同样具有破坏性。如果说简略的错误会让智能体(agent)陷入饥饿,那么冗长的错误则会毒害它。

一个 400 行的 Java 堆栈追踪可能只包含 3 行有效信号。对于一个拥有滚动条和 Ctrl-F 的人类来说,另外 397 行只是烦心事。但对于智能体来说,它们是对后续操作的征税:它们挤占了正在讨论的实际代码,将早期的决策挤出窗口,并降低了后续每一步的质量。上下文是有限的资源,而错误输出是其中最大的未经预算的消耗者 —— 智能体通常不得不通过 head 管道处理构建输出来苟延残喘,但这反而让他们丢失了真正重要的那几行。

更糟糕的是,重复会产生复利。一个尝试 5 次相同失败的重试循环会累积 5 份相同的追踪副本。最终,上下文窗口变成了冗余失败文本的垃圾填埋场,模型开始对噪声进行模式匹配 —— 所谓的“状态损坏”往往仅仅是由未经整理的冗长错误引起的。

修复方法是编辑性的,而非技术性的:

  • 以原因开头,而非底层细节。 第一行应该用领域术语说明出了什么问题。调用栈(Frames)和内部细节应放在下方,或者通过标志位(flag)隐藏。
  • 用指针截断,而非省略号。 “完整追踪已写入 /tmp/build-4821.log” 既保留了访问权限,又没有 Token 开销。需要细节的智能体可以去读取文件;不需要的则能节省两千个 Token。
  • 去重重复内容。 “与前次尝试相同的错误 (×3)” 这一行能告诉智能体一些第三个相同追踪无法提供的信息:你的修复方案不起作用,请更换策略。
  • 将冗长程度设为参数。 默认简洁,深度排查才使用 --verbose。当智能体知道有这个选项时,它们非常擅长主动请求更多信息。

这一原则反映了上下文工程的普适规律:目标不是获取更多信息,而是获取能让下一步成功的最精简的高信号 Token 集。

规模化场景下的“你是指?”

建议提示(Suggestion hints)值得特别关注,因为它们是任何错误界面中最像提示词(prompt)的部分。当 git 说 “你是指 git status 吗?”时,人类将其视为建议。智能体将其视为答案。“未找到命令”处理器、模糊标志匹配、测试失败中的“类似问题”链接 —— 每一个都是对智能体下一步动作的自动补全,而智能体接受自动补全的频率是人类从未有过的。

这使得提示质量成为了双向的杠杆点。一个好的提示能缩小搜索空间:输入错误的子命令,重试一次,搞定。一个糟糕的提示比没有提示更糟,因为智能体会在动用自己的判断力之前先听从提示。如果你的模糊匹配器建议了一个语义错误但文本接近的标志 —— 比如在用户误输入 --daemon 时建议 --dry-run —— 你不仅没帮上忙,还主动将数千个循环引向了一条看起来像是在进展的错误道路。

还有一个更微妙的后果:你的提示现在正在与模型的先验知识竞争。当智能体在你的工具中遇到错误时,它带着对类似工具的训练数据记忆而来,并会乐于引入它们的惯例。错误消息是你用事实覆盖这种先验知识的唯一机会 —— 在 npm install 失败的那一刻说明 “此项目使用 yarn 而非 npm”,胜过 README 中同样一段无人阅读的文字。在错误中声明自己惯例的工具会被遵循;而假设用户了解惯例的工具,则会得到模型先验所提供的任何结果,而那可能描述的是完全属于别人的工具。

错误文本即 API 设计 —— 像评审 API 一样评审它

如果错误即提示词,那么它们理应享有我们赋予提示词和公共接口的流程,而不是自第一个 panic() 以来一直处于的“事后才想起来”的状态。

将错误字符串纳入评审范围。 误导性的消息和正确的消息一样容易被合并,因为评审者通常把错误文本看作装饰。要像对待 API 响应 Schema 的变更一样对待错误文本的变更 —— 因为对于你的智能体消费者来说,它们本质上就是一回事。

评估恢复能力,而非仅仅是失败。 大多数测试套件只断言触发了正确的错误。几乎没有测试能断言,一个只拿到该错误文本的新智能体能够恢复。第二种测试现在运行成本很低 —— 将错误连同工具文档交给模型,看看它建议的下一步行动是否正确。Anthropic 关于工具构建的指南非常直白:你不可能在第一次尝试时就搞对工具的人机工程学;你需要通过观察智能体实际遇到的失败来发现问题,然后重写。错误消息是应用这种循环收益最高的地方,因为它们是纯文本 —— 修复一个错误只需更改字符串,不需要重构。

像管理行为一样管理提示版本。 如果智能体根据你的建议采取行动,那么改变建议就是改变行为。如果某个版本发布的 “你是指” 逻辑提供了错误的建议,那么在整个发布窗口期内,该建议都会被大规模执行。

监控遥测数据中的螺旋特征。 来自同一会话的重复相同失败、在特定错误后飙升的 --force/--no-verify 使用率、集中在某一错误类型上的重试次数 —— 这些都标志着你的错误消息作为提示词失败了。每一个峰值都代表一个值得重写字符串。

内化这一点的团队正在悄然积累优势。他们的工具能在一次或两次重试中收敛智能体循环;而其他人的工具则会耗费十次。这种差距不会出现在任何基准测试中 —— 没有评估指标会测量“CLI 失败文本的质量” —— 但它会体现在 Token 账单、实际耗时以及人类不得不介入以解开僵局的频率上。

堆栈追踪设计于一个读者即作者、且是在写下 Bug 五分钟后阅读的时代。随后,它在一个读者是带着搜索引擎的陌生人的时代幸存了下来。而现在的读者是一个以你的错误文本作为其全部失败理论的模型,它在接下来的几百毫秒内决定你的系统应该对此做些什么。为那个读者而写吧。它是你的散文所能拥有的最字面意义上的受众 —— 也是最有可能完全按照你所说的去做受众。

References:Let's stay in touch and Follow me for more thoughts and updates