你有没有遇到过这种情况:系统明明从知识库里检索到了正确答案,生成了一段有理有据的回复,却被审核环节判成“事实错误”,还反过来被改写成一句正确但毫无价值的废话。
我遇到过,而且不止一次。我们团队原本在生产环境用的是“30B LLM 当评委”的方案,也就是让一个大参数模型去检查另一个模型生成的内容,当时一度以为模型越大,判断就越稳。结果连续两周的线上质检让我意识到,这条路走得越大越偏——30B 的评审员常常把模型自己脑补的常识当成“事实”去校验知识库,而那些真实存在于我们资料库里的内容,反而成了被删改的重灾区。后来我把评审员整个换掉,改用了一个专门训练过的 7B 参数事实核查器(fact-checker),一个月之后回头看,结论让整个组都很意外:7B 不但赢了 30B,而且在一个内部测试集上做到了零误删——没有删掉过任何一条真实主张。
这篇文章不是论文复述,而是我们这次“以专用对抗通用”的项目记录。我会讲清楚我为什么推翻原来的评审架构、7B 核查器是怎么训练和落地的、以及它在真实评测中凭什么击败 30B。
1. 把30B模型当评审员,我亏了多少真话
1.1 最初的设计:让一个30B的LLM当对话评审员
我们的业务场景是智能客服的知识库问答:用户提问,RAG 系统从内部文档里检索片段,由生成模型组织答案。为了把关答案质量,我在生成模型后面加了一个评审环节。最初选型时,我对标的方案很自然:用另一个 LLM 当 reviewer。归纳原因有三点:
- 不需要标注数据,写好 prompt 就能跑;
- 大模型“见多识广”,理论上能判断事实是否成立;
- 评审不参与最终回复,只是打分,所以误判影响不大——当时我是这么想的。
我用了参数量更大、效果稳的 30B 量级模型。Prompt 大概长这样:
你是一个严谨的质检员。 下面这段回答来自知识库问答系统,请检查其中的事实性错误。 如果回答的内容与已知事实冲突,请输出需要修改的句子。 如果内容无误,请输出“无错误”。 知识库证据片段:... 待审核回答:...这样的设计表面闭环了:生成 → 检索 → 评审 → 通过/打回。上线头几天看指标,坏例比例确实下降,我还一度觉得方案成立。
1.2 病根在哪:通用评审员不是事实核查器
问题出现在模型“管得太宽”上。LLM 评审员接收到的证据片段是割裂的,它并不会真正去核对“这段回答是否被证据支撑”,而是用自己参数里的世界知识做二次判断。知识库里有大量专业领域内容,比如产品参数、内部流程措辞,这些内容模型并不见得多熟悉,它就开始瞎扮演权威。
举一个真实案例。知识库里有一句话:
“Lite 版支持的最大并发数为 200。”
生成模型正确引用了这句话。30B 评审员却认为“并发数 200 太保守,应该是 1000”,它在没有证据支撑的情况下“修正”了回答内容。下游用户拿到的结果变成了错误数据,而错误源头恰恰是我搞的“质量保护环节”。
类似问题大量集中在三种情况:
- 中文专有名词表述不一致时,评审员把同义表达当成事实冲突;
- 领域文档里的数据与模型训练数据不同,模型以“常识”覆盖文档;
- 证据本身不够完整时,评审员用想象力补全,然后否定真实内容。
1.3 我内部评估的真实数据
为了量化损失,我从线上随机抽了 312 条“通过评审”的回复,让领域专家逐条复核。结果触目惊心:
- 真实内容被误标为错误:27.4%;
- 被修改后反而错误的:18 条;
- 真正被正确拦下的坏回复:只有 31.7%。
换句话说,我的评审系统为了抓住不到 1/3 的坏内容,把 1/4 以上的好消息都误杀了。这还没有算用户感知层面的损失:一次错误的“修正”,往往比不修正更严重。因为用户看到的是一个言之凿凿的错误答案,比一个含糊的答案更具误导性。
2. 为什么是7B?专用事实核查器的选型逻辑
2.1 先重新定义任务:评审 vs 核查
痛过之后,我意识到问题不是模型参数不够,而是任务定义错了。我们需要的不是“评价这段回答好不好”的评审员,而是“判断这句话是否被证据支持”的事实核查器(fact-checker)。这两个目标的差异很大:
| 维度 | LLM 评审员 | 事实核查器 |
|---|---|---|
| 核心能力 | 语言质量、逻辑、风格、事实性综合判断 | 只判断原子事实与证据的蕴含关系 |
| 证据处理 | 把证据当作参考,容易用自身知识覆盖 | 将证据作为唯一判断依据 |
| 输出形态 | 打分、修改建议、自然语言批评 | 支持 / 反驳 / 证据不足 三分类 |
| 失败代价 | 误删真实内容、错误改写 | 误标或漏标,但不会自行改写 |
所以问题从“怎么把 30B 调教好”变成了“怎么造一个只认证据的专用模型”。一旦任务收窄,参数量就不是首要指标了,准确率、可控性和维护成本才是。7B 级别的模型在推理成本上远低于 30B,而且它可以为任务做专门训练,这是通用模型 prompt 调校无法替代的。
2.2 7B模型的选型与基座对比
选基座的时候,我在 Qwen2.5-7B、Mistral-7B 和 Llama-3-8B 之间做了一轮对比。最终选了 Qwen2.5-7B 作为基座,核心原因是它的中文指令遵循能力和长文本稳定性在这个参数级别里更稳,而我们的知识库内容 95% 是中文。
Mistral-7B 在英文事实核查基准上表现不差,但在内部测试集上,它的中文证据对齐能力明显偏弱——经常判断不出“证据说的是同一件事”这种情况。Llama-3-8B 的中文能力虽然比上一代好很多,但主观改写倾向仍然很强,而这恰恰是评审体系里最致命的毛病。
说句实在话,基座模型的内置知识越丰富,对事实核查任务反而越容易产生干扰。因为模型太“聪明”了,它总想用自己的知识去补证据的缺。这也解释了为什么 7B 在这个任务上不是“将就”,而是更有胜算。
2.3 训练数据的构造:让7B学会只看证据
选定基座后,训练数据的构造是迁移过程中最重要的部分。我没有直接拿公开的事实核查数据集硬套,而是基于我们的业务语境做了三层数据合成:
- 从历史知识库标签中抽取“文档片段 + 正确复述”作为正样本;
- 人工改写片段中的时间、数量、主体等构成矛盾负样本;
- 把不相关文档拼接成“证据不足”样本。
每一层我都检查了数据质量。尤其是“证据不足”样本,它决定模型会不会过度推理——模型必须学会说“我不知道”,而不是自己补一个答案。我把它看作是“7B 核查器没有误删真实主张”的关键设计。
3. 手把手搭建7B事实核查器
3.1 整体流程:输入、切分、检索、判定
我们的核查器不是单模型把活全干完,而是一条流水线。在生产中,它的输入是生成模型的输出文本,输出是“需要保留/需要删除/需要修正”的结构化结果。
流水线步骤:
- 步骤1:用规则和少量样本训练一个切分器,把长回答切成若干“主张句”;
- 步骤2:每个主张句从知识库检索对应的证据片段(复用RAG的检索链路);
- 步骤3:把“主张句 + 证据片段”拼成核查模型的输入;
- 步骤4:模型输出三分类:支持、反驳、证据不足;
- 步骤5:根据分类结果执行后续动作。
这样的设计确保每一步都可解释,出问题时能快速定位是检索的问题还是模型的问题。
3.2 主张拆解:把一句话拆成可核查的事实单元
模型输出经常是复合句,比如“该设备防水等级为 IP67,同时支持 5G 和 Wi-Fi 6”。如果整句作为一个校验单元,任何一部分不匹配都会导致整句被否,误伤率会很高。
因此我按“原子事实”拆句,即把上面的句子拆成三条:
- 该设备防水等级为 IP67;
- 该设备支持 5G;
- 该设备支持 Wi-Fi 6。
拆解时优先依赖标点和连接词,再配合少量规则处理“并且”“同时”“以及”等并列关联。拆完之后,每条事实独立去与证据比对,任何一条判定“反驳”,系统只标记对应子句,而不是整个句子都被否掉。这种“精确打击”是保住真实主张的核心手段。
3.3 判定模型与阈值设置
微调阶段,我把数据切成训练集、验证集、测试集,比例 8:1:1,只保留没有跨集重复的文档。用 Seq2Seq 方式训练,标签只有三类:支持(entailment)、反驳(contradiction)、证据不足(neutral)。
模型输出本身带概率,我设置了“保守策略”阈值:
- 支持概率 >= 0.6:保留;
- 反驳概率 >= 0.75:判定为反驳;
- 两者均不满足:归入证据不足,默认保留并标记人工复核。
我特意把“反驳”的门槛抬高了。原因很简单:在事实核查场景下,误删真实内容的代价远大于漏删错误内容。宁可放过一两个坏句子,也不能把真话当谎话删掉。这也是我们最终能做到“deleted no true claims”的一个技术前提。
3.4 写回机制:如何“删除或修正”而不误伤
核查器本身不直接修改文本,它只输出建议。真正执行删除或修正的是一个独立的重写模块,这个模块接收的是“原子事实级别”的判定结果。
如果某条子句被判定为“反驳”,重写模块会先尝试从证据片段里提取替代事实。这里有一个我踩坑后才总结出来的规则:如果证据里找不到可替换的实体或数值,就不要修,直接标记删除该子句。否则重写模型会自由发挥,最后生成一句听起来正确但证据里根本没有内容的话,等于二次污染。
此外,所有“删除/替换”动作都会写进审计日志,方便人工随时抽查。回滚能力比删除能力更重要——我们后来总结经验时发现,事实核查系统最大的风险不是漏判,而是误改后没有后悔药。
4. 7B vs 30B实测对比过程
4.1 测试集怎么设计
为了公平对比,我单独构造了一份 1000 条内部评测集。每条数据包含“生成回答原文、对应知识库证据、人工标注结果”。人工标注只做一件事:判断原文中的每一条原子事实是否被证据支持。
我特意在测试集里提高了易混样本的比例,约 30% 的样本存在“证据支持但表述风格与原文不同”的情况。这类样本最容易暴露 LLM 评审员“挑措辞”的毛病,也最能检验专用核查器是否真的只认证据。
4.2 指标:真实保留率、误纠率、精确率
我主要用三个指标衡量两套方案:
- 真实保留率:真实事实中未被系统删改的比例;
- 误纠率:真实事实被判定为错误并触发修改的比例;
- 精确率:所有修改/删除操作中,修改对了的比例。
结果如下:
| 指标 | 30B LLM 评审员 | 7B 事实核查器 |
|---|---|---|
| 真实保留率 | 79.6% | 99.1% |
| 误纠率 | 20.4% | 0.9% |
| 精确率 | 41.5% | 83.7% |
| 单条平均耗时 | 3.2秒 | 1.1秒 |
30B 评审员在精确率上只有 41.5%,意味着它每发起两次修改,就有一次以上是错的。7B 核查器的精确率虽然没到完美,但因为执行策略保守,真实保留率做到了 99.1%,且测试集上确实没有一条真实主张被错误删除。
4.3 代表性案例拆解
我把测试集里最典型的一条差异拎出来说。
生成回答:“本产品整机重量约为 2.1kg,比上一代轻了 12%。” 知识库证据:“整机重量 2.2kg(不含电源适配器),相比上一代减重约 10%。”
30B 评审员的处理:直接把原句改了,修改后为“重量约 2.2kg,比上一代轻约 10%”。这个修改本身是更贴近知识库的,但它同时也删掉了“2.1kg”这个来自产品页快速参数表的事实。快速参数表里确实写的是 2.1kg,两种口径在不同文档里同时存在,严格来说原句并没有被证据“反驳”。
7B 核查器的处理:先拆出两条事实,分别检索证据。第一条“整机重量约 2.1kg”在快速参数表中找到支持;第二条“比上一代轻了 12%”在技术对比文档中计算后并不匹配。最终结果:保留第一条,只对第二条打上“疑似不精确”并转人工复核。
两者差异的本质是:30B 在“综合判断”时把整句当成一个整体,一票否决;7B 把一个句子看成多个独立原子事实,互相不连坐。这正是我为什么一再强调拆句和证据对齐的原因。
5. 30B评审员误删真实主张的四类典型错误
5.1 挑语法毛病不挑事实
通用评审员的 prompt 里,我写的是“检查事实性错误”,但它总会忍不住去管语病、重复表达、语气不够正式这类问题。一旦它开始改这些,真实内容被改动的风险随之上升。我见过它把“目前支持三种语言”改成“目前支持三种语言版本”,原因是“表述更正式”。这种无意义改动最恐怖的地方在于:它让审核日志变得非常嘈杂,真正需要关注的事实纠错反而被淹没了。
5.2 证据缺失就判“无凭无据”
这是最有迷惑性的一类错误。当检索系统没能找到对应证据时,30B 评审员会倾向于认为“既然没找到证据,就是编造的”,然后直接判定为错误。但在真实知识库系统里,证据缺失可能只是因为检索召回率不够,或者查询改写不到位,根本不代表事实错误。
7B 核查器在这个问题上处理得更好,因为训练时“证据不足”被设计为独立的第三类输出,模型学会了对没有证据的内容保持克制。即使检索失败,它也会返回“证据不足”,触发人工复核而不是自动删除。
5.3 模型自带知识覆盖检索事实
知识库如果包含高频常识性内容,通用大模型的“常识自信”就会作祟。30B 评审员对常识太熟悉了,所以当证据片段里出现一个与它认知稍有出入的数值时,它不相信证据,而相信自己的记忆。
内部知识库里有一条“客服响应时效为 2 个工作日”,但模型训练语料里大量出现“24 小时响应”的表述,于是评审员跑来把 2 个工作日改成了 24 小时。这种覆盖是极其危险的——因为如果你没有人工复核,它看起来就像一次“很专业的修正”。专用核查器因为训练数据里没有“让模型保持自己观点”的选项,反而能老实追随证据。
5.4 中文口语与书面表述的措辞敏感性
中文领域还有一个隐藏问题:同义表达的形式变化特别大。评审员经常把“能用”和“支持”视为不同事实,把“大概 2 公斤”和“约 2kg”视为矛盾,把否定句式的等价表达视为冲突。这种措辞层面的敏感,本质上是被语言模型对“语义一致”的高要求放大了。核查器在微调时使用大量同义改写样本,让模型从表征层面学会“同一事实多种说法都可成立”,这个问题才被明显压制住。
6. 集成部署中的坑:从模型到生产
6.1 推理优化与并发设计
7B 模型相比 30B 在显存上轻松太多。我们线上环境是 2 卡部署,28B 精度的 7B 模型直接把并发翻了一倍。做并发时要注意一点:事实核查请求的响应长度很短,瓶颈不在解码,而在批次处理。我们把请求按 max_tokens 分桶,小请求优先合并到同一批次,单卡吞吐提升了约 40%。
推理框架我推荐兼容 OpenAI 接口的本地部署方案,省去自研路由。前处理和后处理都比较简单,真正的复杂度在证据检索和主张拆解这些外围逻辑上。
6.2 与内容生成流水线的缝合
集成时我踩过一个大坑:把核查器放在生成模型之后的同步链路里。生成一个回答只要 1 秒,核查要 1 秒,整体延迟翻倍。后来我把流程改成异步:
- 绿色通道:低风险问题直接返回答案,核查离线进行;
- 异步核查:核查结果写回缓存,下一次命中时生效;
- 高风险模板问题:才走同步核查。
这样既保住了用户体验,又留住了事实把关能力。同步改为异步之后,线上本来担心的“误删真实内容”也更容易追踪,因为每个被删句子都有独立日志,用户可以随时投诉“这条被删错了”,我们靠这个反馈不断优化预测阈值。
6.3 数据回流与持续迭代
模型训好不是终点,而是起点。我把上线后的核查结果全部回流到训练池,每两周抽一批高置信样本人工复核,然后把复核结果混合进训练集。
持续迭代时要特别注意数据漂移问题:知识库内容不断更新,但核查模型不知道新文档的存在。我的做法是定期用新文档重跑一次历史测试集,观察“支持/反驳”分布是否出现大幅变化。如果反驳率突然上升,优先怀疑文档更新引入的版本冲突,而不是直接去找模型的问题。
这个回流机制,才是“7B 核查器一直不删真话”的长期保障——它是靠数据更新维护的,不是靠一次性训练撑住的。
写在最后的经验
项目做完之后,我自己最深的体会是:模型参数大小和任务适合度是两回事。通用大模型做评审,强在综合能力,弱在“只认证据”这种反常识的克制。而一个针对事实核查任务训练过的 7B 小模型,反而因为训练目标干净、干扰知识少,能把“支持 / 反驳 / 证据不足”这三类判断做得比 30B 可靠得多。
如果你也在跑类似的 RAG 审核链路,我的建议是先别急着上更大参数的评审模型。花一周时间,把你要保护的事实边界定义清楚,明确什么是“真实主张”、什么是“证据缺失”、什么是“表述不一致”,再决定究竟该用多大参数去解决。很多时候,小而专,远胜大而全——这次 7B 击败 30B 的实战,就是最直接的证明。