Skip to content

FDS-15 · B6 出处校验

抽取(B5)给每条结论都附了"这句话出自原文哪里"和一个位置。但模型自报的引文和位置不可信——它会编出一句看起来很像、其实原文没有的话。B6 这一关用纯程序逐条核对:引文是否真能在精修文本里对上;对得上才放行进事实库,对不上就丢弃或转人工。这一步绝不让模型自己判断,确定性、可复算。


1. 业务说明

  • 是什么:夹在"抽取(B5)产出之后、写入事实库(B8)之前"的一道程序闸门。它把 B5 每条结论自带的"出处引文 + 位置"拿来,和上一步精修(B3)产出的文本逐字核对——引文要么在原文里真的能找到,要么找不到;能找到就钉死它在原文里的精确字符区间,找不到就拦下。
  • 解决什么:模型抽取时很会"脑补"——它给你一条决策"上线时间定在 6 月底",还煞有介事地附一句引文"我们决定 6 月底上线",但精修文本里压根没人说过这句,或者说的是"6 月底争取,但要看测试"。这种看似合理实则编造的引文,人眼一条条核对核不过来,下游(待办、看板、复盘)又全靠事实库,一条假出处会污染整条链。B6 就是把这件"人核不动、又绝不能错"的事交给程序。
  • 为什么必须是程序、不能交给模型:让模型自己核对自己的引文,等于让它给自己的作业打分——它判断"这句和原文意思差不多"时,正是它最会自圆其说的地方。程序只认一件事:这段字符到底在不在那段文本里,它不懂"意思差不多",也正因为不懂,它不会被"听起来很对"骗。这是确定性(同样输入永远同样结果、可重算)与"看起来对"的根本区别。
  • 不做会怎样:就是现状——出处全靠模型一句话自报,没人验、不可追溯。点开一条结论想看出处,跳过去发现原文根本没这句;或者三个会的结论互相打架,谁也说不清哪条是真的。事实库"唯一真相"的地基就塌了。

2. 用户操作与界面(交互)

用户不直接操作 B6——它在流水线里自动跑、对用户透明。用户感知到的是结果:

  • 会议结果页每条结论旁有一个"看出处"的链接,点开能跳到精修文本里高亮的那一段(就是 B6 钉死的精确区间)。能跳得准、且高亮的就是结论说的那句——这是 B6 干对了。
  • 某条结论的引文没对上、被 B6 拦下:它不会出现在结果页的"已确认"里,而是落到 B7 的站内确认任务(见 FDS-16),由会议负责人在工作台里看到"这条 AI 抽的没找到原文出处,请确认"。
  • 整场会跑完,结果页顶部一行小结:"本场抽取 N 条,出处核实 M 条,转人工确认 K 条"——让人对这场的可信度一眼有数。

3. 数据与接口(字段级)

B6 是代码模块,不是技能,没有 SKILL.md。它在 acdm-backend 流水线里作为一个节点函数存在,输入输出都是结构化数据。

输入(来自 B5 抽取,每条一个对象):

字段类型说明
extract_idstr这条抽取项的临时 id(本场内唯一,贯穿到 B7/B8)
kindenumdecision/risk/action/milestone/fact(类型决定"未命中"是丢弃还是转人工)
texttext结论的标准化内容(B5 措辞后的,不参与比对,只随条目走)
claimed_quotetext模型自报的"原文引文"——B6 要核的就是它
claimed_spanobj null模型自报的位置 {char_start, char_end}(可能为空、可能不准,仅作提示加速,不作依据)
refined_file_iduuid指向 B3 产出的精修文本(meeting_files.kind=refined),比对的基准
content_hashtextB5 抽取时所依据的精修文本指纹(防止比对时文本已被改过,见第 4 节第 0 步)

输出(每条一个判定结果):

字段类型说明
extract_idstr原样带回
verdictenumhit_exact / hit_substr / hit_fuzzy / to_human / dropped
source_refjsonb null命中时钉死的精确出处:{file_id, char_start, char_end, speaker, ts, match_type, similarity};未命中为 null
similarityfloat模糊对齐时的相似度分(0~1);精确/子串命中记 1.0
reasonstr判定说明(人能读:"全文未找到该引文,最高相似 0.41,判定编造")
  • source_ref 的字段形状与 FDS-17 project_facts.source_ref 完全一致——B6 放行的条目,其 source_ref 就是 B8 入库时直接落进事实库的那个出处,字符区间由 B6 钉死,不再由模型说了算
  • speaker / ts 从精修文本对应区间所属的那一段(B3 的 segments,见 FDS-12)反查带出,不取模型自报。

接口(acdm-backend 内部,流水线编排调用,不暴露给技能):

  • verify_citations(refined_file_id, content_hash, items[]) -> results[]:批量核验一场会的全部抽取项,返回每条判定。同步、确定性、无副作用(不写库;写库是 B8 的事)。
  • 读精修文本经事实库侧的只读取文接口(按 refined_file_id 取文本与 segments),B6 不直连数据库表(与 FDS-04 §5 一致:正确性模块也走接口,边界清晰)。

4. 技术实现(分步)

核心是一句话:把引文和精修文本用同一把尺子洗干净,然后看引文这段字符到底在不在文本里;在,就钉死它的精确区间;不在,就拦下。 全程没有任何一步问模型"你觉得对不对"。

第 0 步 · 防呆:确认比对基准没被换过。 取精修文本后先算它当前的指纹,和输入带来的 content_hash 比。不一致说明 B5 抽取后精修文本又被改过(比如人改了纪要触发重精修)——此时位置全失效,整场退回重抽,不在错的基准上做无意义的核对。

第 1 步 · 文本规范化(关键:两边用同一把尺子)。 模型抄引文时常顺手改了标点、空格、全半角、大小写,字面比对会因为这些无意义差异全军覆没。所以比对前,把"精修文本"和"引文"都过同一套规范化(规范化 = 统一成一种标准写法,消除不影响语义的表层差异):

  • 统一空白:连续空格/换行/制表压成一个,首尾去空。
  • 统一标点:中英文标点归一(", 。 ! ?" 与 ", . ! ?" 等价),成对引号/括号归一。
  • 全角半角、英文大小写归一。
  • 逐字符记录映射:规范化会改变字符串长度,必须同时建一张"规范化后第 i 个字符 ↔ 原文第 j 个字符"的对照表。最终要写回事实库的区间是原文坐标(用户点出处要跳回没被洗过的原文),不能是规范化后的坐标。这张表是后面所有"钉死区间"能落回原文的前提。

第 2 步 · 先按自报位置精确比对(快路径)。 若模型给了 claimed_span,就在规范化文本里取那一段,和规范化后的引文逐字比。一致 → hit_exact,经映射表换算回原文坐标,钉死区间,放行。这是最省事的路径,但自报位置只是加速提示:对不上绝不据此判错,继续走下一步(模型给的位置经常偏)。

第 3 步 · 全文找子串(忽略位置)。 在整篇规范化文本里搜规范化引文这段子串:

  • 恰好一处:hit_substr,换算回原文坐标钉死,放行。
  • 多处都能对上(第 4 步 · 命中多处):取离 claimed_span 最近的那一处(模型位置虽不精确,但通常落在大致范围,够用来区分重复句)。若没有 claimed_span、或多处距离都差不多无法区分 → 不猜,转 B7 人工(让人指认是哪一处)。宁可慢一条,不可钉错一条。
  • 一处都没有:进第 5 步模糊对齐。

第 5 步 · 模糊对齐 + 相似度阈值(容错最后一关)。 找不到完全一致的子串,可能是模型抄时漏字/多字/小改个别词(还算如实转述),也可能是整句编的。用一个确定性的近似匹配:在规范化文本里用滑动窗口(窗口长度 ≈ 引文长度上下浮动)找与引文最相近的片段,算两者编辑距离(把一个字符串改成另一个最少要增删改几个字),归一成 0~1 的相似度分(1 = 完全一样)。这个算法同输入永远同输出,可复算、可审计。按两道阈值判:

  • 相似度 ≥ 高阈值(如 0.92):认定是如实转述只是抄写有微小出入 → hit_fuzzy,把最相近片段在原文里的区间钉死(注意:钉的是原文里真实存在的那段,不是模型那句),放行。
  • 中阈值 ≤ 相似度 < 高阈值(如 0.75~0.92):像、但不敢替模型背书 → to_human,转 B7 由负责人裁定(见 FDS-16)。
  • 相似度 < 中阈值(如 < 0.75):原文根本没有相近的话 → 判定编造。按 kind 分流:决策/风险/待办这类一旦为真影响大的,即便编造嫌疑也走 to_human(让人最后看一眼,避免误杀真信息);一般 fact 类直接 dropped。无论丢弃还是转人工,都写一条审计日志(extract_id、引文、最高相似分、判定理由),可追溯为什么拦它

第 6 步 · 阈值的确定与冻结。 高/中阈值不是拍脑袋——用一批人工标注好的样例(哪些引文是真转述、哪些是编造)跑一遍,选能把"真的别误杀、假的别放过"分得最开的值,写进代码常量并记录依据。一旦定下,生产期不随意改;要调必须重跑样例、记录新依据(和事实库版本失效同思路:判定规则也得可追溯)。

第 7 步 · 批量与边界。

  • 一场会几十上百条,批量核;单条互不影响,任一条异常(如引文为空)单独记 dropped + 原因,不拖垮整批。
  • 引文过短(如只有几个字"同意""推进")子串匹配会满篇命中且无意义——设最小引文长度,过短的直接 to_human(太短的"出处"本就不足以追溯,交人确认)。
  • 全程不写任何库:B6 只产出判定;hit_* 的条目连同钉死的 source_ref 交 B8 入库,to_human 的交 B7,dropped 的只留审计日志。职责单一。

5. 技能 or 代码

全部代码(acdm-backend),不是技能,一行都不进 SKILL.md

依 FDS-04 §2 的判据——"错了会算错数据/出假结论的 → 代码"——B6 是这条判据最典型的样板:它的全部价值就在于不被"听起来很对"骗,而模型恰恰最擅长生成"听起来很对"。把核验写成技能让模型自律,等于撤掉这道闸门。规范化、子串/模糊匹配、阈值、区间换算全是确定性算法,同输入永远同输出、可复算、可审计——这正是"防瞎编"必须具备的性质,模型判断不具备。技能(B3 精修、B5 抽取)只负责"生成";"验真"永远在代码侧,两者职责不混。


6. 二开归类

🔴 从头自建(acdm-backend),放在流水线"抽取(B5)之后、入库(B8)之前"的固定关卡——这是单场会议流水线里一个写死的代码节点,不可被技能替代、不可被跳过。

  • 为什么不复用 deer-flow 的东西:deer-flow 没有"核对模型自报引文是否真在原文"这类能力——它面向"生成与检索",不面向"对生成结果做确定性验真"。这是本模块特有的正确性需求,upstream 无对应扩展点,只能自建。与 FDS-04 §6 三条不让步一致:防瞎编是代码红线,不外包给模型机制。
  • 复用什么:复用项目已有的取文接口(读 B3 精修文本与 segments)、复用 B7 站内确认通道(转人工不自造一套)、复用 B8 入库接口(放行条目交它写,B6 自己不碰库)。新建的只有"规范化 + 匹配 + 判定"这一块确定性核验逻辑。
  • 自建边界:只做"判定 + 钉死区间",不做生成(那是 B5)、不做写库(那是 B8)、不做派人(那是 B7)。一个纯函数式的关卡。

7. 与人机协同模型关系

B6 是任务流里一个纯机器、无 AI 参与的内层关卡,体现"AI 与人共写"里机器把关那一半:

  • AI(B5 技能)负责"提出"——抽出结论并主张出处;代码(B6)负责"验真"——不接受单凭主张,必须经程序核对;人(B7)负责"裁定"——程序拿不准的(像但不敢断、命中多处无法区分)派回负责人。三方各管一段,谁都不替别人拍板。
  • 对应工作台模型:B6 放行的事实进项目级账本(事实库);B6 拦下的变成任务卡(B7 确认任务)出现在负责人工作台。它是"AI 产出"与"可信事实"之间那道不可绕过的质检,确保进账本的每条都有真出处。

8. 验收(可证伪)

  • 能挡编造:构造一批"原文里根本没有的引文"喂进去,全部被判 to_humandropped,无一条被当作命中放行进事实库。
  • 不误杀如实转述:构造一批"原文确有、仅标点/空格/全半角/个别字小改"的引文,全部被判 hit_*(精确/子串/模糊)放行,且钉死的区间能在原文里高亮出对应那句
  • 区间钉得准:随机抽 20 条放行项,按其 source_ref 跳回原文,高亮段落与结论所指一致(配合 FDS-17 §8 的同项验收)。
  • 重复句不钉错:精修文本中存在多处相同句子时,命中能落到与上下文相符的那一处;无法区分的转人工而非随便钉一处
  • 可复算:同一场同一批输入跑两次,每条 verdictsource_refsimilarity 完全一致(确定性,无随机)。
  • 基准防呆:故意在抽取后改精修文本(改 content_hash),B6 拒绝在旧基准上核对并触发整场重抽,而非给出错误区间。

9. 现状

没有。当前出处全靠模型自报——抽取时模型顺带说"这条出自某句、大概在某位置",没有任何程序核对:引文可能是编的、位置可能是错的、点开"看出处"可能跳到不相干处甚至跳不过去,也无法追溯某条结论凭什么进了库。下游(待办/看板/复盘)又全部依赖这些未经核验的出处,假出处会顺着链路污染一片。本章从零补上这道"抽取之后、入库之前"的确定性闸门,把"出处可信、可追溯"这件事从"靠模型自觉"变成"靠程序保证"。


10. 风险与取舍

  • 风险:阈值定太松→放编造进库;定太紧→误杀如实转述、把人淹没在确认任务里。取舍:用人工标注样例校准并冻结阈值(第 6 步),宁可偏紧(误杀转人工成本可控,放假进库污染地基不可控);拿不准一律转 B7,不让程序在灰区替人拍板。
  • 风险:模糊匹配在长引文上算量大,一场会几百条可能慢。取舍:先精确/子串快路径拦掉绝大多数(模型多数会照抄),只有少数落到模糊;模糊只在引文长度邻域开窗,不全文两两比;批量异步跑、不阻塞用户提交。必要时对超长引文截断取代表片段比对。
  • 风险:精修文本本身有错(B3 漏了原话)→ 真引文反被判编造。取舍:这类落 to_human 而非直接 dropped,由人发现"是精修丢了"并回退重跑 B3(与 FDS-12 §10"宁可保留也不臆造删改"呼应)——B6 只对"引文 vs 精修文本"负责,精修质量由 B3 验收兜底,职责不越界。
  • 风险:规范化过度→把本不同的两句洗成一样,误判命中。取舍:规范化只动确定无语义的表层差异(空白/标点/全半角/大小写),不做近义替换、不做去停用词;凡可能改变语义的归一一律不做,误判方向永远偏向"判不命中转人工",不偏向"误命中放行"。
  • 风险:模型学会"抄一段原文真句子当引文,但配一条原文没有的结论"——引文真、结论假,B6 只验引文存在性会放过。取舍:B6 的边界明确只验"引文是否真实存在于原文",不验"结论是否由该引文支撑"(那是语义判断,确定性程序做不了)。"引文真但结论歪"由 B5 抽取技能的 eval 样例约束 + B7 人工抽检兜底,不让 B6 越界去做它做不可靠的语义裁决——一个关卡只做它能做对的那件事。

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析