我给了AI智能体一个计算器,因为我想在概率性工作流中拥有一个硬性的、可检查的节点。
模型可以解读请求并解释结果。计算器
会执行计算。这看起来是一个分工明确的安排。
然后我把一个乘号改成了加号。
计算器仍然通过了其Rust库测试套件中的389个测试(共390个)。
唯一的失败是将结果与NIST认证的长利(Longley)回归数据集结果进行了对比。
这比一个完全构建失败的场景更让我困扰。我原本以为确定性计算比让语言模型即兴进行算术更安全。但确定性并不意味着值得信赖。一个程序可以永远返回同样的错误答案。
“真相来源”这个说法突然显得太过舒适。在AI智能体将权限委托给工具之前,这种权限应该受到挑战——并且应保持可被证据撤销的状态。
计算器只是一个样本。更大的想法是一种将可检查、可重放的工具放置到概率性系统内部的方法。
有趣的区分不在于模型权重与“真正的CPU”之间。模型推理也在处理器上运行,语言模型可以学习真正的算术程序。有用的边界在于生成答案与在经过测试的契约下执行定义的操作之间。
关于程序辅助语言模型(PAL)的研究做出了一个相关的拆分:语言模型读取并分解自然语言问题,而Python解释器等运行时环境执行生成的程序。模型提供灵活的解读;运行时提供可执行的语义。
这就是我想要的智能体中的分工:
这并不会让整个智能体变得确定,也不会使模型变得不必要。智能体仍然可能选择错误的工具、提供错误的参数、误解单位或误读结果。
承诺更小:一个声明变得可检查、可重放且可独立测试。
概率性的对立面不是值得信赖,而是可重复。
CPU可以精确地犯错。
校准不能完全自指。实现不应是自身预期答案的唯一作者。
这就是我选择NIST的原因——并不是因为政府权威将结果变成数学真理,而是因为NIST有着长期的建设共享、独立评估参考的制度实践。
国会于1901年成立了国家标准局(NBS),以加强美国的计量基础设施。1968年的标准参考数据法案授权了一个联邦项目,用于收集、严格评估、出版和分发标准化的科学和技术参考数据。NBS于1988年更名为NIST。1999年,通过统计参考数据集项目(通常简称为StRD),这一传承延伸到了统计软件领域。
StRD将数据集与特定统计程序的认证预期值配对。其收集包括各种难度的生成案例和真实案例。对于线性程序,NIST在其计算中携带了500位数字,这样普通的浮点表示误差就不会成为基准。
长利(Longley)是该集合中一个数值上具有挑战性的小规模线性回归数据集。它的认证结果给我的测试提供了实现本身无法制造的东西:一个在待测试代码之外产生的预期答案。
这个限定条件很重要。StRD并不认证Oddly Exact,不证明统计引擎正确,也不使软件可追溯到NIST。NIST明确表示,这些数据集是评估软件的辅助工具,并且没有机制建立软件可追溯性。
参考数据并不是整个程序的预言机。它只是针对其覆盖的计算的独立见证者。
基于示例的测试会问,熟悉的输入是否仍然产生熟悉的输出。变异测试则问一个更令人不安的问题:
如果我引入一个小而合理的缺陷,测试套件能注意到吗?
我使用cargo-mutants将多元回归标准误差计算中的乘法替换为加法:
- residual_std_dev * sum_sq.sqrt()
+ residual_std_dev + sum_sq.sqrt()
该变异保留了有效的Rust代码、有效的类型以及一个看起来合理的数值结果。在库测试套件中,390个测试中仍有389个通过。针对NIST长利值的断言是唯一的失败。
公开的变异记录保留了确切的命令、环境、结果以及该实验所建立结论的边界:
我不能声称长利是整个仓库中唯一能捕捉该变异的测试;运行在失败的库套件后停止了。我可以声明一个更窄且更有用的结论:数百个测试容忍了一个语义破裂的公式,而一个锚定在独立产生值上的断言拒绝了它。
绿色测试数量只是那些测试当实现有意义地变化时是否会变红的证据。变异测试测量的是这种敏感性,而不是欣赏数量。
公式变异挑战了数值含义。我的下一次审计则挑战了计算周围的承诺:工具接受什么、拒绝什么以及它将执行多少工作。
第一个裂缝是广告中的契约与可执行契约之间的矛盾。生成的JSON Schema禁止额外的属性,但Rust反序列化器在请求根级别以及表达式节点内部都静默地接受了一个未知字段。
答案在数值上仍然是正确的。但这并不意味着行为无害。针对schema进行校验的调用者看到一个比直接与二进制通信的调用者更严格的工具。拼写错误或误解的字段可能在没有警告的情况下消失。
对于一个面向智能体的工具,静默地解释不同的契约本身就是一种正确性缺陷。
第二个裂缝是资源边界。表达式引擎限制了诸如整数大小、表达式深度和精度等。然而,优化接口却接受没有上限的网格分辨率和迭代次数。
我请求了一个包含十亿采样点的网格搜索。在外部的一秒看门狗终止它之前,修复前的二进制文件没有产生任何JSON。这个工具有输入验证,但它并没有赢得我认为已经构建的有界工作保证。
这些失败变成了永久性的回归请求。反序列化器现在在两个层级都拒绝未知字段。优化器现在发布并强制执行10万次迭代和100万网格点的上限。同样的十亿点请求在进入搜索之前返回一个类型化的resource_limit响应。
契约挑战Gist是所有三个修复案例的可运行回放:
那个脚本比绿色运行的截图更有价值。它允许另一个观察者直接交叉检查边界。
一个新的保护措施只是另一个声明,直到测试证明它们关心它。
在修复了优化器限制之后,我选择了cargo-mutants为两个新验证器生成的每个变异。十一个变异试图移除检查,用无条件成功替换它们,或者改变它们的边界比较。测试捕获了所有十一个。
聚焦优化器变异记录捕获了选择过程和结果:
这完成了循环:
声明 → 攻击 → 失败 → 修复 → 变异 → 回放
如果我仅发布最终的绿色套件,你会看到信心。通过保留更改后的操作符、挂起的请求、契约矛盾、修复以及试图撤销它的变异,我可以展示信心的理由。
伤疤是校准记录的一部分。
Oddly Exact 只是一个计算器,但方法可以迁移。在将狭窄的工具权限委托给智能体工作流之前,我现在会问五个问题:
契约是否明确且可执行? schema、解析器、运行时、限制和失败模式必须一致。可执行文件不强制执行的文档只是建议。
是否存在独立参考? 在这个统计案例中,NIST StRD将选定的预期答案移出了我的实现之外。其他领域可能使用标准规范、参考实现、经过验证的语料库或单独派生的测试预言机。
哪些属性应该在多个输入中保持不变? 基于属性的测试生成案例并检查规律,而不是记住单个示例。一个Oddly Exact的属性检查:将样本中的每个值平移一个大的精确偏移量,在严格的浮点容差内样本方差保持不变。
测试能否检测出看似合理的损坏? 变异测试更改操作符、比较、返回值和保护措施。存活的变异并不证明实现是错误的;它揭示了一个套件目前无法区分的行为变化。
失败是否是有界、有类型且可重放的? 智能体必须能够区分被拒绝的计算和不可用的工具。除以零、资源耗尽、格式错误的JSON以及崩溃的进程不应该坍缩成同一个模糊的失败。
这些层级做不同的工作。外部参考挑战特定的答案。属性挑战跨输入空间的行为。变异挑战测试本身。契约和资源攻击挑战围绕这一切的边界。
没有一个能提供普遍的正确性。它们一起创造了一个更窄且更有用的结果:证明某个特定工具在声明的契约内对于经过验证的请求值得拥有临时权限的证据。
对于该契约内经过验证的请求,工具的结构化结果可以作为智能体操作层面的真理来源。
这是一个运行时角色,而不是无谬误的声明。如果智能体遇到不一致,它可以检查参数、公开冲突或咨询另一个独立信任的工具。它不应该悄悄地用随意的散文算术替换结构化结果。
信任关系在不同时间以相反方向运行:
新证据总能将工具带回第一阶段。这是一个受到挑战的来源:在操作中受信任,可上诉,并且始终只需一个反例就能被修订。
美妙之处不在于源代码是真理。
美妙之处在于源代码可以被交叉审查。
我结束这个实验时,并没有得到一个AI智能体可以永远信任的计算器。我得到了更有用的东西:一个其权限可以被证据撤销的计算器。
AI工具辅助了编辑研究和修订。我验证了技术声明,并对最终文本负责。
——
一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。