上个月我在自己的技术栈里翻出一个bug,它已经被"修好"整整三周了。
其中一个工具的超时参数以毫秒为单位,模型一直发的是"30s"。调用居然成功了,因为模型和工具之间夹着一层我当初赶工写下、后来忘得一干二净的类型转换垫片。它解析字符串、做乘法、把整数传下去。绿色对勾亮起,再没人回头看它一眼。
这层垫片就是整场幻觉的缩影:一个悄悄修复畸形工具调用的代理层,并不会让你的智能体更可靠。它只是把失败从看得见的报错,挪成了看不见的纠正。成功率上去了,工具调用准确率一点没动。
为什么它感觉有效
因为仪表盘站在你这边。加网关之前,你有一墙的校验错误和少量跑崩的轨迹;加网关之后,错误没了,轨迹跑完了。你手上每一个信号都在说系统变好了。
但模型本身什么都没变,它猜错的概率还是那么高,你只是不再统计这些猜错了。你测到的不是准确率,是修复启发式的宽度。这是两个不同的数字,只有其中一个跟模型有关。
这种坑我见过三种发作方式。
- schema不再是契约。一旦下游有一层愿意同时接受"30s"、30和"30000",你的schema就不再是规格说明,而是一条背后挂着解析器的建议。这个解析器成了整个栈里评审最少、测试最少的代码,却要在每一次调用上运行。我从没见过哪个修复启发式配了测试套件,倒是见过不少带着# temp注释的。
- 模型永远不知道自己猜错了。这是最容易被跳过的一环。在单条轨迹内部,错误信息就是反馈信号。你把{"error": "timeout_ms must be an integer"}作为工具结果返回,模型读到它,下一轮就会发整数,这个闭环才叫工作。静默修复把闭环剪断了:模型为一个发错的调用拿到了成功结果,于是这一轮会话剩下的时间里它会继续这么猜,之后每一轮会话也这么猜。你把提示词训练成了只有靠垫片才能跑的形状。
- 你失去了判断模型升级有没有用的能力。换上新模型,跑评测,看数字——它们会一模一样,因为修复层把差异吸收掉了。你分不清"这个模型的工具调用更好"和"我的启发式上周二变宽了"。这一条最贵:你花钱做的评测,看不见它要评估的东西。
这个论点诚实的版本
宽松解析确实有正当场景:日期、来自HTML表单的数字字符串,凡是歧义出在编码层、而不是出在模型对任务的理解上,都算。如果一个工具本来就同时接受2024-03-01和2024-03-01T00:00:00Z,把两者归一化不是修复,只是解析。这没问题,做就是了。
我在意的分界线是:这个纠正是否响亮、是否被计数。转换日期可以,但要递增一个计数器并记录原始输入。如果这个计数器一周都是零,就把转换删掉;如果它在往上爬,你手上就有了一个终于看得见的提示词问题。
静默修复性质不同。它是一种没有计数器、没有日志行、没有责任人的纠正。那不是可靠性特性,那是指标洗白服务。
当然,有些取舍我会接受:面向客户的聊天场景里,工具调用失败比稍微错一点更糟;演示日;需要老客户端继续工作的迁移窗口。这些场合静默修复站得住脚。但要说出来,给它定一个到期日,别再管它叫可靠性。它是止血带。止血带有它的位置,但没人会把它绑在健康的手臂上然后称之为健身。
我现在的做法
在工具边界做严格校验,除非schema里声明了可接受的联合类型,否则不做任何转换。
失败时,把校验错误作为普通工具结果返回给模型,用平实的语言写清期望的形状。重试一次。然后让这一步大声失败,让轨迹死在它该死的地方。
校验之前永远先记录原始参数。你最终收到的bug报告会是"它昨天干了件怪事",而原始参数是唯一能回答这个问题的东西。
把校验失败次数当成一等指标,和延迟、成本并列。如果这个数字持平而任务成功率在涨,说明有别的东西在干活,你该把它找出来。
如果你已经有一层今天删不掉的修复层:让它跑影子模式。让它算出纠正、记录下它本来会修什么,但不应用。跑两周,你就能确切知道你的可靠性里有多少是真的、多少是垫片撑的。我猜结果会让你往坏的方向吃惊——我自己的就是。
今年真正提升我智能体可靠性的改动全是减法:更窄的schema、更少的工具、更严的类型、更多被暴露出来的失败。我加过的每一个宽容层,后来都后悔了。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.