[控场AI]
· 7 分钟阅读· 3,924 字

CodeSmith万行Rust打造测试通过真相核查机制

CodeSmith万行Rust打造测试通过真相核查机制

CodeSmith v0.5.0用重放对账机制,让模型的测试通过声称接受退出码裁决。

CodeSmith v0.5.0针对"模型声称测试全绿但实际未通过"的幻觉问题,推出结果验证器:当轮次结束、模型宣称验证成功时,引擎把本轮已执行过的验证命令原样重放一遍,以退出码对账,产出四元组裁决。裁决分为VerifiedPass、VerifiedFail、VerifyError、Unsubstantiated四种,其中最后一种因检测成本为零而价值最高。安全上遵循"approved-replay only",只重放本轮已过审的命令,用34个测试/构建前缀做资格门。裁决以只追加方式注入对话尾部以保护KV缓存,并记入jsonl账本供`/verify stats`统计,其中声称匹配率刻意将Unsubstantiated排除在分母之外。

CodeSmith Meta-Ability 测试验证机制

当模型说"测试全绿"时,它在说什么

把智能体投入工作的开发者都经历过这个场景:模型改完代码,跑了一次测试,然后在总结中写道"所有测试通过"。你信了,提交代码,CI亮红。翻日志才发现——测试跑在过期的目录里、跳过了失败用例,或者根本就没有运行任何东西,只是按照习惯补上了那句话。

这不是道德缺陷,而是概率头脑的出厂设置。CodeSmith v0.5.0对这个问题给出了工程回答:当一轮对话结束、模型刚刚说完"测试全绿"的那一刻,引擎会把模型本轮自己执行过的验证命令原样重放一遍,用退出码来对账。裁决以四元组形式写回对话,默认开启,实现于一个914行的模块中。

提交ab45cdee和39e77390共同封堵了"进化有记录但无闭环"的缺口。ROADMAP中保留着账目记录:实地审查发现持续进化记录了问题却没有闭环机制,改进项编号P3-8。失误账本记录了债务,MEMORY.md存了教训,但从未把模型的"声称"和世界的"事实"放在同一张桌子上核对。

裁决四元组的设计哲学

四种裁决类型

输出形状是这样的——每条裁决携带四个要素:结论、维度、证据位置、失败类型。落到代码里就是四种裁决类型:

  • VerifiedPass:重放通过,声称属实
  • VerifiedFail:重放失败,声称与实际不符
  • VerifyError:重放本身未完成(超时/沙箱拒绝/工具不可用),绝不能被伪装成通过或失败
  • Unsubstantiated:模型声称成功,但本轮从未执行任何验证类命令

第四种是整个检查器产生的最有价值信号。它的检测成本为零——不需要重放任何东西,只需注意到声称背后没有执行证据支撑。配置文件中的注释说得更直白:这是最强的幻觉信号,因为连重跑都不需要。

配置示例

引擎侧接线只有两个钩子点。轮次结束的触发条件只有一行:仅当result_claim_verifier开启且轮次状态为Completed时,才扫描最终的助手消息。冲刷发生在请求之前:缓冲的裁决被渲染成一个runtime_event并推入历史。

关于钩子点的实现语境:文中提到的两个钩子点,是 CodeSmith 引擎在对话轮次生命周期中的固定插入位置。所谓"轮次结束"钩子,指的是模型完成一次完整响应、生成最终助手消息之后的回调时机——此处是扫描声称的唯一窗口,因为只有在助手消息定稿后才能确定它到底"说了什么"。而"请求前冲刷"钩子,则位于下一次向模型 API 发起请求之前,负责把缓冲区里积攒的裁决序列化并注入历史。这种"计算与投递分离"的安排并非偶然:它让验证逻辑的耗时不会阻塞当前轮次的结束,也保证了裁决信息的注入时机恰好落在下一次上下文被消费之前,避免无效的提前写入。两个钩子的职责边界清晰——一个负责产出裁决,一个负责投递裁决,中间由缓冲区解耦。

复放权限的保守边界

这个模块最重要的不是"验证什么",而是"凭什么验证"。重放模型跑过的命令意味着引擎在用户背后又执行了一次shell——这为什么不会出问题?

安全契约写在模块文档里,第一条就是approved-replay only:重放走对话自己的工具分发器,逐字符复用模型本轮已经发送过的输入——相同命令、相同工作目录、相同超时。验证不授予本轮未曾拥有的任何权限:这个输入已经通过过一次审批门,现在只是再播放一遍。

第二条是资格门。不是任何exec_shell历史都能被重放,只有以已知测试/构建前缀开头的命令才有资格。测试类白名单有20个前缀,构建类有14个,从cargo test到dotnet test。复合命令按&&和;拆分后逐段判断,VAR=value环境赋值会先被剥掉。

白名单的取舍体现了真正的工艺:构建类刻意排除了裸make(会误匹配make install、make clean)和cargo clippy(那是lint不是build)。后台调用永不被重放。

声称检测本身也有讲究:使用连续短语匹配而非正则表达式,因为"测试未通过"不包含"测试通过"这个子串——否定词天然打断了短语。一个已知的残留误报是"not all tests pass",代价是多一次重放和账本上一个诚实的退出码。

裁决如何返回对话

计算裁决只是一半工作,如何把它送回去才是设计见功夫的地方。

路由方式像LSP诊断信息:轮次结束时裁决挂在待处理缓冲区上,下次API请求前作为合成的<codesmith:runtime_event>用户消息冲刷进去,只追加在尾部,绝不改写历史。

为什么这么绕?因为只追加的对话历史是KV缓存的生命线。把裁决插入历史中间,前缀缓存就整块作废;挂在尾部,模型下轮开口时必须先解释为什么上轮说全绿而重放回来是红的。

统计口径的诚实性

单条裁决是给模型看的,长期账本是给人看的。每条裁决追加到~/.codesmith/evolution/verdicts.jsonl,一行一个JSON:时间戳、裁决、声称原文、命令、退出码,以及是哪个模型说的。

日志记录是尽力而为的:路径解析失败或写入失败只记WARN然后吞掉,因为日志绝不能破坏产生事件的引擎。读取时畸形行会被跳过。

读数来自/verify stats。核心指标是声称匹配率,其核算基础写在注释里:分母只计真正有命令支撑的可验证声称;分子计重放后仍然成立的。

pub fn claim_match_rate(&self) -> Option<f64> {
    let checked = self.verified_pass + self.verified_fail;
    if checked == 0 {
        return None;
    }
    Some(self.verified_pass as f64 / checked as f64)
}

这个核算基础是诚实的。Unsubstantiated永不计入分母——把"从未验证"和"验证后崩溃"混进一个比率,读数就失去了区分幻觉类型的能力。一个指标把分母画在哪里,暴露了系统想让你看到什么。

KV 缓存的机制背景:这里牵涉到大模型推理服务中一项关键的工程优化。在自回归生成中,模型处理提示词时会对每一层注意力计算键(Key)和值(Value)向量,这些向量一旦算出便可缓存复用,下次遇到相同前缀时无需重算,从而大幅降低延迟和算力开销。这种缓存的前提是前缀严格一致——一旦在对话历史中间插入或修改内容,被修改位置之后的所有缓存全部失效,需要整段重算。正因为如此,任何"就地编辑历史"的做法都会带来剧烈的性能惩罚。把裁决追加到尾部则完全避开了这个问题:原有前缀一字未动,缓存照常命中,新增内容只在末尾续写。这也解释了为何整个系统把"只追加"当作铁律——它既是正确性约束,也是性能约束。

闭环的第一张多米诺骨牌

把机制串成一条线:

  1. 轮次正常结束,引擎扫描最终助手消息,短语匹配捕获"测试通过/构建成功"类声称
  2. 反向搜索本轮历史中最近的验证类命令(20+14前缀,分段拆分,剥离环境赋值,排除后台调用)
  3. 命令通过对话自己的分发器逐字符重放——相同命令、相同cwd、相同超时、不授予新权限
  4. 退出码沉淀为四种裁决之一;四元组渲染为runtime_event挂在对话尾部,模型下轮必须对账
  5. 同一裁决落入jsonl账本,/verify stats提供分类计数和声称匹配率

三个属性及各自代价:

默认开启——信任不再免费,代价是每个爱宣布胜利的模型每次多一次重放,慢测试仓库需要权衡[verification] result_claims开关。

只重放零授权——安全边界保持保守,代价是当模型从未运行验证命令时,引擎无法替它运行,只能报告Unsubstantiated。

裁决是证据不是指令——引擎不改写提示词、不碰审批门,代价是路线修正依赖模型自己结账,碰上不愿眨眼的模型,账单要多花几轮才能收回来。

四个元能力篇章在这里收束。回头看会发现它们始终落在同一个地基上:裁决只能挂在对话尾部,快照只追加,账本只追加——任何写入上下文或历史的字节都不得被悄悄修改。

短语匹配为何优于正则:声称检测采用连续短语匹配而非正则表达式,背后有一个容易被忽视的语言学原因。在自然语言中,否定通常通过在动词短语前添加否定词来实现(如"未通过""不成立"),但这个否定词与肯定表述之间并没有稳定的、可被正则精确捕捉的边界。上文举的例子很典型:"测试未通过"这串字符里确实含有"测试"和"通过",但因为中间插入了"未",它们并不构成连续的"测试通过"子串,短语匹配天然不会误触。反观正则,若想用模式同时兼顾肯定与否定,往往需要复杂的前后向断言,且极易遗漏边缘表达。短语匹配用最朴素的连续性检查,反而获得了对否定结构的免费免疫力。代价则是偶尔的残留误报,如"not all tests pass"这类英文表达仍会被命中,但系统选择承受这点成本,因为多一次重放远好过漏掉一次幻觉。

#CodeSmith#测试验证#AI编程#智能体幻觉#Rust#结果核查#自动化测试
分享:

相关推荐