Agent 结论发出前先对账:值域对账验证层 + n=3 重复对照的搭建方法
这两天 Agent 圈还在卷"模型越来越聪明"——更大的参数、更长的上下文、更强的推理。可查数场景有个反直觉的现实:结论错了,往往不是模型算错了,而是它把 A 层级的东西填进了 B 层级的榜单,还写得自信满满。
我没跟着换更大的模型。我干了另一件事——把我自己维护的开源数据智能体工作台 daw 里,2026 年 9 月 1 日那天落地的验证层(verify)完整拆了一遍。它的核心就一句话:不信任回答者的自我声明,结论发出前,先拿数据库的真实值域对一遍账。
看完我可以负责任地告诉你三件事。第一,结构级错误(层级错配、口径错配)是可以用机械检查当场抓出来的,不用等用户来骂。第二,单次运行的结果说明不了问题,同一个问题跑 3 次,才能分清"稳定缺陷"和"抽样噪声"。第三,纪律写成模板比写成说教管用,而且管用的程度是可以度量的。
1. 学习目标:学完你会得到什么
一句话:学完这篇,你会给自己的查数 Agent 搭一套"验证层 + 重复对照"的组合——结论里的每个实体,先过值域对账;每个评测问题,跑 3 次再下结论;交付纪律,写成可机械检查的模板。
先给大家科普一个概念「值域对账」。翻译成人话:数据库里某列合法取值的全集,就是"值域"。比如"定金台账的 dept_lv2 列"里只有战区级团队名,那结论榜单里出现的每一个团队名,都必须在这个集合里。榜单里冒出一个"武汉咨询部"——它在订单表里是部门级名,不在战区值域里——那就是层级错配,当场亮红灯。
这篇的全部代码都在 daw 仓库里:tools/verify_claims.py(验证层)、tools/eval_score.py(打分器调用验证层)、AGENTS.md(完整的实验记录)。
2. 前置知识:先说清验证层不做什么
验证层有三个定位,动手前必须先对齐,否则写着写着就变味。
第一,验证层不判分,只亮灯。v1 的设计是:红灯只是"疑似层级错配"的高价值信号,交由人工或上层定性,不直接判 FAIL。为什么?因为早期提取器有伪影,误报直接判死刑会把好答案一起杀掉。先亮灯、攒数据、看红灯与真实错误的吻合度,精度够了再收紧。
第三,验证层用的值域,必须和金牌复标同源。daw 的做法是:domain_sql就是一条直查 SQL,跑在和金牌复标同一个数据库连接上。值域不是手写的一份清单——手写清单会漂移,直查 SQL 不会。
3. 第一步:给每个评测用例写 verify 配置
验证层的输入不是"随便一段文本",而是评测用例里的一段配置。长这样(来自verify_claims.py的文档):
"verify":[{"name":"好差榜单团队 ∈ 定金台账 dept_lv2 值域","domain_sql":"SELECT DISTINCT dept_lv2 FROM ...","anchors":["用得最好","用得最差","用得好","用得差"]}]三块内容,一个都不能少:
name:这条检查在查什么,人能读懂。domain_sql:值域的直查 SQL,返回合法值全集。daw 支持-- @connection_name前缀指定数据源,缺省取 daw.db 里第一个 postgres 连接。anchors:榜单句的锚点词。结论很长,验证层只看含锚点的句子——"用得最好的是 XX,用得最差的是 YY"这种榜单句。不锁定锚点,正文里随口提到的团队名会被误当成榜单实体。
锚点的作用,是把"宣称"和"随口提到"区分开——提到一个名,不等于把它当榜单答案交出来了。
4. 第二步:从榜单句里提取候选实体
取到结论文本后,验证层只看最后一个工具调用之后的那段纯文本(结论段),按句号和换行切句,挑出含锚点的句子,然后从句中提取候选实体。
提取用的是一条组织实体后缀正则(ENT_RE):台账和订单表里的团队名都以这类后缀收尾——咨询部、事业部、战区、战队、军团、直辖、咨询中心、某部、某区、组、OB。用后缀锚定提取,而不是"随便切词",是为了避免把"渗透率""客群特殊"这类散文片段误当实体。
光有正则还不够,真实结论里有两种脏东西,v2 就是为它们而生的:
脏东西一:前缀散文。结论里写"双优的苏州二部"“郑州系和武汉咨询部”,正则会把整段吞进来或把"和二部"当实体。解法是"剥前缀散文":按连词和助词(和、与、及、或、的、顿号、逗号)切分保尾段,剥完剩下"苏州二部"“武汉咨询部”。v1 没做这一步,“和二部”"管理盲区"这类伪影全是这么来的;v2 加上剥离后,短语伪影清零。
脏东西二:合法的"零使用"提及。“武汉咨询部没用这个工具"里的实体确实不在值域里,但它不是错配,而是合法业务事实:这家确实零定金。v2 的解法是"零使用语境降级”:实体前后 15 字窗口里出现"没用、未使用、未收、零定金、0 笔、未开、从未"这类词,红灯降级为 warn。
还有两类直接跳过、不当实体:长度小于 3 的、还含助词连词的(比如"武汉的战区",那是短语片段不是实体);泛指描述短语(“用得”“主力”“全国”“所有”“各战区”“各团队”)。
翻译成人话:提取器的活儿不是"多抓",是"抓准"——抓错一个,后面全是误报;漏抓一个,错配就溜过去了。v1 到 v2 的全部工作,就是在这一步把"抓准"做扎实。
5. 第三步:值域对账——精确或包含匹配
拿到候选实体后,对账逻辑本身只有一句话:候选实体对值域做精确或包含匹配,命中算过,没命中亮红灯。
包含匹配是有讲究的:苏州三部 ⊆ 苏州三部直辖,这种人工截断也算命中——模型把全称截短了,不算错配。匹配条件是候选长度 ≥3,且候选在值域值里、或值域值在候选里。写的时候注意:包含匹配是双向的,只做单向会漏掉"模型写了全称、值域里是简称"的情况。
对账的输出是 findings 列表,每条带check、level、detail。level 分四档:
red:答案实体不在值域内——疑似层级或口径错配。warn:零使用语境提及(合法业务事实),或结论中没找到榜单锚点句(可能漏检,人工确认)。error:domain_sql执行失败——值域本身没取到,对账没法做。- 无 findings:这条检查通过。
还有一个边界:结论文本为空,直接返回一条 warn——“结论文本为空,无法对账”。对账的前提是"有结论可对",没结论时不该静默通过。
实战效果是验证层第一枪就命中:2026 年 9 月 1 日的换模型对照里,deepseek 把订单表部门级名(武汉、大连、主动咨询部)当定金台账团队列进好差榜单,值域对账把 6 个真实错配实体全部机械拦下;同一批 glm 的记录,验证层 0 红灯。v2 上线后用 12 条存量记录验证:glm 全部 0 红灯(合法零定金提及全降级),deepseek 真错配全数保留,短语伪影清零——红灯数约等于真实结构错配数。
6. 第四步:n=3 重复对照——分清稳定缺陷和抽样噪声
验证层解决了"这一轮对不对",n=3 解决的是"这个失败是偶然还是必然"。
做法很简单:同一个评测问题,同一个模型,跑 3 次,分别计分,再看 3 次的通过率和失败模式。daw 在 2026 年 9 月 1 日定案了这个协议,理由是当天的实测数据:
- deepseek 的"团队好差"问:5 轮里只有 20% 通过,3 轮踩层级陷阱——失败是稳定复现的,不是抽了一次坏签。
- deepseek 的"定金单数"问:3 轮 0% 通过,来源表漏标稳定复现——这是纪律缺口,不是噪声。
- glm:3 轮 67%——召回路径 2 个工具、52 秒全对;重算路径 10 个工具、217 秒 FAIL,漏标来源表。同一模型内部都分层了:召回路径全对,重算路径会漏。
三个结论:第一,deepseek 的失败是稳定行为,不是抽样噪声。第二,知识信任度本身是被测能力——召回路径和重算路径的表现可以天差地别。第三,验证层第一枪命中——结构级错误当场现形。
n=3 还有个跑器教训:eval_run的缺省模型查询会"自我复读"——上一条 eval 任务成了下一条的模型来源,解法是缺省查询排除eval-任务。评测工具的 bug 比被测模型的 bug 更隐蔽,因为它污染的是"尺子"本身。
7. 第五步:把纪律写成模板——剂量-响应定案
验证层兜底之后,daw 还做了一个对照实验,回答"纪律到底该怎么写"。
实验对象是结论末尾的"来源:"清单行。三个版本:
- v1 说教纪律:提示词里写"要标注来源"——来源行合规率 0%。
- v2 要求有行:要求结论末尾必须有"来源:"清单行,缺行等于未完成交付——deepseek 的 count 问从 0/3 到 3/3,glm 从 0/3 到 3/3。
- v3 规定行格式:"来源:"行并进口径,格式定死——口径行合规从 0/3 到全线 3/3,crosscheck 双模型整卷通过率从 0%/33% 到 100%。
这条曲线叫"剂量-响应":说教(0%)→ 要求有行(v2)→ 规定行格式(v3),合规率随模板精度单调上升。结论很硬:模板化(可机械检查的交付形态)比说教式纪律更可遵守。把纪律写成模板,是提示词工程在这个产品里剩余的有效杠杆;模板降低违规率,验证层兜底拦截残余,两者互补。
工程细节:模板 v3 的检查是评分时即时计算的,基线直接从存量记录算出,不用重跑——打分器新检查必须是"即时计算",否则每加一个检查就要重跑整批评测。
8. 为什么这样设计:三条取舍
第一,先亮灯,不判死刑。v1 红灯只报告不判 FAIL,因为提取器精度还没验证过。先攒 12 条存量记录验证"红灯 ≈ 真实错配",再收紧;一上来判 FAIL,误报会逼你关掉整个验证层。
第二,值域直查,不手写清单。值域来自domain_sql直查,和金牌复标同源。手写清单漂移一次,验证层就从"裁判"变成"另一个错误来源"。
第三,验证层和模板分工。模板是"事前约束",验证层是"事后拦截"。只做模板,残余错配没人拦;只做验证层,模型永远在撞墙。两者互补,才是"生成 → 验证 → 纠错 → 再验证"四步环。
9. 避坑清单
- 红灯先只报告不判 FAIL,吻合度验证过后再收紧——一上来判死刑,误报会逼你关掉验证层。
- 实体提取必须剥前缀散文,否则"双优的苏州二部"这类伪影会淹没真红灯。
- 零使用语境必须降级为 warn——合法业务事实判成错配,等于惩罚模型说真话。
- 值域用直查 SQL,和金牌复标同源——手写清单一定会漂移。
- 同一问题跑 3 次再下结论;召回路径和重算路径要分开看。
- 打分器新检查必须即时计算;评测跑器的缺省查询必须排除评测任务本身。
10. 给开发者的建议
- 先给最痛的一个榜单问加一条 verify 配置:
domain_sql+ 锚点词,半小时就能跑起来。 - 提取器先用后缀正则 + 剥散文 + 零使用降级三件套——这三件是 v1 到 v2 全部的精度来源。
- 评测集里每个问题固定 n=3,把"通过率"和"失败模式是否稳定"一起记录。
- 把你最想让模型遵守的一条纪律,写成"行格式"模板(行首锚点 + 格式定死),而不是提示词里的一句说教。
- 验证层拦下的红灯,攒够一批就复盘一次——红灯分布本身,就是下一轮纪律该往哪写的最准信号。
不信任自我声明,只信任对账结果。结论发出前,先问一句:它说的实体,在值域里吗?这个问题回答了,查数 Agent 才算有了第一道真正的"出厂检验"。