Ulyssify 会屏蔽让你分心的应用和网站。它有一个助手 Anka,能用自然语言帮你把屏蔽规则设置好。上周,它以一种很有启发的方式出了错:
下面这段对话为英文原文:
那个网站其实进了另一个列表,行为也并不相同。助手确认了一件从未发生的事,被质疑时又开始猜测。
一个很诱人的诊断是「模型还不够聪明」。但我们没有止步于此,而是回溯了整段对话,发现了更值得说的结论:再大的模型也不可能答对。 那些事实从来就没有进入它的上下文,它们在两个环节丢失了。
漏洞一:助手看到的数据视图会过时
我们的设置界面永远不会落后,因为它们直接渲染后端返回的内容。助手看到的却是另一回事:那是某个人在功能上线当天手写的一个摘要函数,只列出了寥寥几个字段。后来产品长出了带有真实时段的日程安排,却没有人更新那个摘要,也没有任何机制强制谁去更新。
所以助手根本看不到用户已经设置了一个午夜时段的日程,于是它提议去重建那些用户其实早已拥有的东西。这不是愚蠢,而是失明。
修复:一致性是一项测试,而不是一句承诺
我们不再指望任何人去记住这件事。现在有一个 CI 测试会读取界面真正渲染的那些字段,并要求其中每一个要么呈现给助手,要么带着一条写明的理由被排除。只要你往界面里加了一个字段,却没有替助手做出决定,构建就会变红,并给出一条消息告诉你该怎么做。
漏洞二:助手叙述的是它的计划,而不是结果
进错列表这个 bug 更糟。服务器已经执行了操作,并如实存下了发生的一切,包括那个网站真正进了哪个列表。可是助手的后续回复,是用它自己先前的计划,再加上一句笼统的「执行成功」拼出来的。那份存好的事实,在模型看到它之前一步就被丢掉了。
现在,后续回复是基于存好的结果生成的:真实的列表,真实的状态,真实的 id。如果某样东西进了意料之外的地方,助手会如实说出来,因为它被告知的就是这个。而且请求里的任何内容都无法注入到那条消息里,每一个事实都由服务器生成。
从未出问题的那部分:两扇门,一道关卡
有一个特性,让这件事只是尴尬,而没有变成危险,它也是我们设计中最会全力捍卫的一环。助手没有属于自己的 API。它的操作在 HTTP 之下的一层进入后端,运行的正是人类点击时所运行的那一批带关卡的函数。
这一点之所以重要,是因为对手是谁。在一个约束装置里,最有动机去破解屏蔽的人,正是用户自己,在凌晨一点,和自己过去的决定讨价还价。如果助手有属于自己的写入路径,那么每一句提示词都可能成为一个漏洞。正因为它走的是人类那条路径,任何会放松强制的操作,无论请求措辞如何,都得排在用户自己设定的冷却期后面。助手可能会出错,但它不可能成为一条绕行的捷径。
要说清这不是什么:它不是「行动前先问一下人」,那一点别处已经讲得很充分。它是一个关于底层管道的更强的主张。「助手能做人类能做的一切」,应当指的是能力上的对等,而不是一套平行的 API 界面,因为人类同样无法跳过冷却期。
这里面关于审批的那一半,业界已经讲得不少:12-Factor Agents 论证了要把人留在环路之中,Anthropic 的智能体工具编写指南则讲了如何把有意义、高信噪比的结果返回给模型。而关于底层管道的那些主张,也就是助手的写入路径就是人类那条带关卡的路径,以及它的确认是由存好的服务器事实重新拼装而来,是我们还没见到有人写下来的部分,所以我们把它写了下来。
我们把这次事故变成了一个永久的测试,从而闭合了这个环路:那次对话里的确切配置如今成了一个测试夹具,CI 会断言助手的上下文包含了答对所需的每一个事实。
- 一份手写的数据摘要,早就已经过时了。把界面与助手之间的一致性做成一个会失败的测试,而不是一句要靠人去信守的承诺。
- 喂给模型的应当是来自服务器的结果,而绝不是它自己的计划。一个只会叙述意图的助手,迟早会确认一件虚构的事。
- 让助手的写入走和人类点击相同的、带关卡的代码路径。这样一来,一个糊涂的助手只会令人尴尬,而绝不会带来危险。
- 在争论模型有多聪明之前,先修好它能看到什么。一个更大的模型,若在缺失事实的基础上推理,也只是用更漂亮的文字去编造。