指出依赖 AI 事后解释作为审计线索的陷阱,提倡捕获计算副产物而非生成的叙述,建立可信的系统决策追踪。
如果你的系统所谓的审计轨迹,就是“问模型为什么那样做”,那你其实并没有审计轨迹。你得到的只是第二份模型输出:它和第一份输出由同一个过程生成,而且在架构层面没有任何保证,能够证明它反映了真实发生的事情。
这一点很容易被忽略,因为你得到的回答通常语言流畅、细节具体,而且听起来合情合理。Anthropic 关于 chain-of-thought 忠实度的研究(Lanham 等,2023)发现,这些自我报告往往无法反映生成原始答案时所进行的计算——模型给出的是一个听起来连贯的故事,而不是一份执行轨迹。流畅性与忠实度并不相关。生成过程中的任何机制,都没有将二者绑定在一起。
举个具体的例子:我曾看到一个 AI 系统被问到“这个结论是从哪里来的”,它回答说,自己“绘制了某位用户公开作品背后的知识图谱”。它引用的来源确实存在——这一点经过了独立验证。但关于它如何找到这些来源的说法,纯粹是模型生成的叙述;这段叙述来自那个正在被质疑其执行过程的系统本身,外部根本无从核验。

这里所说的凭据(receipt),是实际计算过程中作为副产物产生的数据,而不是事后为了描述该过程而生成的内容。它由三个属性定义:
它是计算的副作用,而不是一次独立的生成调用。计算过程本身会把它记录下来。整个流程中不存在让语言模型回答“所以,你刚才到底做了什么”的步骤。
它包含的是字面值,而不是描述。包括实际求值的表达式、实际读取的输入,以及实际返回的数据——而不是对这些内容的转述。
它可以被独立地重新计算。拿到凭据后,第三方——无论是人还是系统——都可以重新执行计算,检查结果是否一致,而无须相信原始过程对自身行为的任何说法。
从设计上看,自我报告无法满足其中任何一项。凭据是与计算存在因果关系的数据;自我报告则是与计算最多只存在相关关系的文本——而根据忠实度研究,两者往往甚至连相关关系都没有。
.me 是一个声明式语义内核,它通过 explain(path) 为派生值提供了这种模式:
me.explain("robots.surgeon.canProceed")
// →
{
value: true,
expr: "canLift && softGripReady && !needsHumanReview && contextAllowsMotion",
derivation: {
inputs: [
{ path: "robots.surgeon.canLift", value: true },
{ path: "robots.surgeon.softGripReady", value: true },
// ...
]
}
}
这里没有发生任何生成。explain() 会查找实际执行求值的表达式,以及求值过程中实际读取的输入字面值,然后不加修改地将它们返回。如果该路径并非由任何内容派生而来,只是一个普通的存储值,它就会直接说明这一点(derivation: null),而不是为了填补空白编造一套因果故事。
它并不只是通过文档表达设计意图,还针对真实断言进行了演示:Robots_Contexts.ts 中有四个机器人,它们会根据不同上下文得出不同的决策;测试会断言返回的依赖项列表包含真正的上游路径,并通过 CI 检查。它的开销也经过了基准测试,而不是主观推测:对于完全公开的轨迹,p95 延迟只增加约 0.007ms。
要使用这种模式,你并不需要这个特定的内核。凭据的属性属于架构设计,并不依赖具体工具。对于构建在 LLM 之上的 Agent 系统:
工具调用:记录实际发送的参数,以及实际收到的原始响应。不要记录模型对响应的摘要——要记录响应本身。
RAG:展示实际检索到的文本块及其真实的相似度分数。不要只展示模型对检索结果的转述。
多步骤 Agent:在每一步执行时,由编排代码维护状态日志,并让它独立于模型对自身行为的描述。日志就是凭据。模型的叙述只是对日志的评论,不能替代日志。
当你确实要求模型解释自身行为时:应把答案视为一个需要对照凭据进行验证的假设,绝不能把它本身当作凭据。
这种方式无法让通用 LLM 的自由文本 chain-of-thought 变得忠实——开放式生成并不存在固定的表达式图,无法像 .me 公开派生值那样将其披露出来。它真正能做的,是消除这样一类情况:明明可以获得真实的计算轨迹,你却仍然依赖模型的自我报告。在大多数生产环境的 Agent 系统中,这恰恰涵盖了调试和审计真正关心的绝大多数场景。
包含完整底层论证的文章:me.explain(Why Did You Say That)
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。