错误消息是一段提示词:它是你教会模型下一次尝试的唯一机会。
当一次工具调用失败时,错误字符串会直接回到模型的上下文,并成为它紧接着那个决策最具影响力的单一输入。堆栈跟踪什么也教不了它;"error: 400" 教它去瞎猜;而一句说清「哪里错了、该改成什么」的话,常常一回合就修好了循环。本文把错误消息当作工具设计中头等重要的一部分——为一个会读它、并立刻再试一次的模型而写。
模型的下一次调用,几乎完全取决于上一个错误。
撞上错误的人会翻文档、读源码、问同事。模型只有一份可供学习的东西:你返回的那段文本。它不会再去找一遍模式;它会对着你的错误字符串去做模式匹配地重试。这让错误消息成为整个工具里杠杆最高的一句话——它在功能上,就是在模型决定该怎样换个做法的那一刻注入的一段即时提示词。就照这个去写。
好的错误点出原因,并开出修复药方。
有用错误的最低限度有三部分:什么被拒绝、为什么被拒绝、正确的调用长什么样。缺任何一个,模型就在瞎猜。「输入无效」三个都不占。「amount 必须是以分计的正整数;你传的是 39.99;请传 3999」三个都占,于是重试立刻成功,因为修正就在消息里,而不是模型得去重新推导的东西。
# Useless: the model guesses, retries, fails the same way Error: 400 Bad Request ValidationError: field 'amount' invalid # Useful: cause + value + the corrected call, in words Error: 'amount' must be a positive integer in cents. You sent 39.99 (dollars). Retry with amount=3999.
把出错的值在错误里回显回去。「你传的是 39.99」让模型看见自己的错误,而不必去重新推理它可能哪里做错了——这是错误设计里收益最高的单一习惯。
用文字明确区分「可重试」与「终态」。
模型需要知道再试一次有没有可能成功。一个暂时性失败(「被限流,2 秒后重试」)邀请退避;一个终态失败(「订单 42 已退款;不要重试」)必须关停循环,否则你会得到一个无限相同重试在烧预算。别让模型从一个它一知半解的 HTTP 码里去推断这点——直接说出来。「重试不会成功;该订单不存在」会终结一个「404」本会无限空转的循环。
有副作用的工具上一个含糊的错误,正是失控循环的诞生方式:模型分不清「已完成,停」和「暂时性,再试」,选了再试,然后无限重发。可重试/终态信号是一个遏制控制,不只是个锦上添花。
错误应把模型引向正确的工具,而不只是引离错误的调用。
最好的错误会做正向路由。如果模型用名字而非 ID 调用了 delete_user,错误应当说「需要一个用户 ID;请先调用 search_users 取得一个」——点名那个能产出缺失输入的工具。这把死胡同变成了一份计划。一个只说「不行」的错误,把工作流留给模型去重新发现;一个说「不行,改做这个」的错误,就是一次恢复的运行与一次被放弃的运行之间的区别。
绝不让工具静默失败,或对成功撒谎。
最糟的错误,是那个没被返回的错误。一个吞掉失败、返回空结果,或在什么都没发生时返回 200 的工具,会让模型在一个虚假前提上自信地继续——而那个错误会在三步之后浮现为无从定位、任何追踪都无法锁定的诡异行为。部分成功是同一个陷阱:「已退款但通知失败」必须如实报告,而不是报为成功,否则智能体会向用户报告一个谎言。诚实的失败,每一次都胜过省事的沉默。
何时简短的错误才是对的选择。
啰嗦、富有教益的错误要花上下文 token,而一个每次失败都倾倒 500 行堆栈跟踪、或整个有效模式的错误,比一个生硬的错误更毒害窗口。让错误有教益,而非百科全书式——一句原因加上修正后的调用,胜过一墙诊断信息,免得模型为了找到它需要的那一条事实而在里面跋涉。