基于证据的AI事实核查:构建陈述真值检测系统全解析
先问一个问题当你拿到一段 AI 生成的文字你能确定它是真话吗最近在不少数据竞赛和技术社区里都能看到类似 Aletheias Quest 的项目出现。这个项目名本身很有隐喻意味——Aletheia 在希腊语里是“真理”女神Quest 则是“追寻”。把这两个词放在一起背后真正想做的就是构建一个 AI 谎言检测器输入一段陈述输出一个判断说这段陈述是真的还是假的。这个目标听起来很酷但真正工程化之后你会发现难点从来不在“选哪个大模型”而在数据怎么标、证据怎么取、结论怎么输出。模型告诉你准确率越高越要小心它只是在一个理想化数据集里自说自话。本文就以 Aletheias Quest 的构建与复盘为线索讲清楚如何设计一个最小可用的“基于证据的陈述真值检测系统”同时把踩过的坑、评估的误区、生产部署的边界都一一梳理出来。1. 这篇文章真正要解决的问题先说结论我们要解决的不是“让 AI 看穿人在撒谎”这个心理学问题而是“让系统判断某段陈述和外部证据是否一致”这个可验证的问题。如果你只是想让 AI 快速判断“一句话是真是假”第一反应通常是训练一个二分类模型输入文本输出 Positive 或 Negative。这个方案在演示环境里很好用但放到真实场景会迅速翻车原因有三个第一谎言没有稳定的文本特征。一个人可以面无表情地说出错误信息也可以因为紧张说错一句完全正确的话。模型无法从字面推断说话者的意图强行分类只会学到表面相关性。第二事实是上下文相关的。同一句话“手机销量排名第一”在不同年份、不同统计口径、不同商品类别下真值完全不同。没有外部证据支撑的孤立判断本质上就是模型在猜。第三真实业务需要的不是单一真假标签而是一条可追溯的证据链。内容审核场景里运营人员要看到模型为什么判断为假引用了哪份资料否则无法通过人工复核。所以Aletheias Quest 这类项目真正要解决的是一个“陈述与证据一致性判断”问题。它可以服务三类典型场景内容审核判断一篇文章中的核心数据、事件描述是否有可靠来源。客服质检判断客服回答是否与知识库一致避免把虚构信息发给用户。AI 生成内容风控检测大模型输出中是否存在与事实不符的幻觉描述。如果你正在做这三类场景请不要盲目购买一套“AI 谎言检测”方案。按本文这套思路自己组装成本更低也更容易向业务方解释。2. AI 谎言检测的本质与概念边界2.1 “谎言”为什么是技术难题在日常语境里“谎言”意味着说话者明知为假还要说。这里面有两个不可直接观测的要素一是说话者的知识状态二是说话者的意图。在技术层面这两点恰恰是 AI 最难获取的信息。模型只能看到文本本身最多再叠加说话者的历史发言记录但它无法知道一个人是否“明明知道真相却故意说错”。因此AI 谎言检测器必须把目标改成可计算的定义一段陈述在给定证据集合下是否成立。这个定义放弃了“意图判断”换来了工程可行性。它不回答“这个人是否在骗你”只回答“这句话和现有资料是否能对上”。从这个角度说AI 谎言检测器更像是一个自动事实核查器而不是传统意义上的测谎仪。2.2 为什么不直接让大模型回答“真或假”很多人会觉得大模型知道那么多知识直接让它判断就行。但这里有个根本性的误区。大模型的语言建模目标是“预测下一个 token 的概率分布”它学习到的是语料中的统计规律而不是事实数据库。当信息缺失或知识过期时模型会按照概率分布补一段“听起来合理”的文本这就是所谓的幻觉。如果你让大模型直接回答“这句话是真是假”它很可能不是去查证据而是基于自己的内部知识库生成一个答案。这个答案可能流畅但无法保证正确。所以在 Aletheias Quest 的架构里我们始终把大模型当作“推理组件”而不是“答案本体”。判断真假需要先拿到外部证据再由模型对证据和陈述进行对比推理。2.3 任务定义支持、反驳、未知为了让系统更稳定我们把输出设计成三分类判定结果含义适用场景支持有证据表明陈述成立可以放行、标记为可信反驳有证据表明陈述不成立标记为可疑进入人工复核未知证据不足或证据冲突不判定转人工处理“未知”这个类别非常关键。早期版本中我们只输出“真”和“假”导致模型在证据不充分时也硬生生给出判断准确率看似很高实际上一遇到陌生领域就乱猜。加入“未知”之后系统整体可靠性明显提升因为业务方能够区分“证实为假”和“暂时无法确认”。下面的表格对比了传统测谎仪和本文所讲的基于证据的真值检测能帮你快速理解两者的技术边界。维度传统测谎仪基于证据的真值检测输入生理信号自然语言陈述判断依据心率、皮肤电等生理反应外部事实证据输出是否说谎支持、反驳、未知主要风险生理反应无法直接等同于谎言证据不完备时判断失真适合场景需要专门授权的辅助审讯内容审核、事实核查、文本风控3. 为什么 AI 谎言检测容易翻车四个核心难点3.1 数据标注的悖论想训练一个检测器第一件事是准备标注数据。但“真实性”并不是一个稳定的标签。比如“2024 年新能源汽车销量同比增长 35%”这句话在不同时间节点、不同统计范围内真实性可能完全不同。如果标注员没有统一的证据来源两个人的标注结果可以完全相反。实际项目里我们做过一次内部一致性测试让五位标注员给一百条陈述打“真/假”标签最终完全一致的不到六成。这说明直接从人类标注开始构建训练集非常困难。更可行的做法是把任务拆成“从可信文档中抽取陈述作为正例用改写或替换数字的方式构造反例”。这样至少能保证标签与证据之间有一致的对应关系模型也更容易学到真正的判别逻辑。3.2 幻觉会污染检测结果当检测器本身也是大模型时它同样会有幻觉问题。你在 prompt 里让模型判断真假模型可能不参考证据而是直接调用内部知识回答。这就会造成一种很荒谬的结果模型把一个明显错误的陈述判断为“支持”因为它认为那个说法“听起来很熟”。解决这类问题不能只靠改 prompt更要在 prompt 里明确约束推理路径要求模型输出“证据原文 结论”同时拆成独立的检索和判断步骤避免模型跳过证据直接给答案。3.3 静态基准无法代表真实分布在新闻数据上训练的检测器放到评论区、短视频口播、专业报告里表现会大幅下降。原因是真实世界的表达方式和知识分布差异太大。测试时需要刻意构造对抗性子集替换数字、改变表述顺序、用同义词改写关键实体。你会发现原本在测试集上 0.9 准确率的模型在改写后的数据上可能掉到 0.7 以下。这说明模型记住了很多表面模式并没有真正理解“证据支持”关系。3.4 输出层的退化如果系统只有“真/假”两个出口模型就会在信息不足时被迫选择一个这本质上是一种过度自信。我们曾经统计过在没有任何证据的情况下二分类模型仍然会对超过 30% 的陈述给出“反驳”或“支持”的判断。加入“未知”类别后模型可以用“证据不足”作为退路这不仅是模型行为上的改变更重要的是整个产品流程变得安全了系统从“替你下结论”变成了“提醒你注意风险”。4. 环境准备与技术选型4.1 运行环境本文示例代码基于 Python 3.10需要安装以下依赖pip install openai sentence-transformers faiss-cpu numpy如果你的机器有 16GB 以上显存的 GPU可以本地部署一个开源对话模型比如 Qwen 系列或者 Llama 系列。如果只有 CPU 环境也可以调用兼容 OpenAI 接口的远程模型服务。生产环境建议使用带 GPU 的推理服务便于控制延迟和数据隐私。4.2 模块选型完整系统包含四个模块选型建议如下模块推荐选型说明判别模型Qwen、Llama 等开源模型本地部署保护原始数据嵌入模型BGE 系列中文语义匹配效果好可替换为 m3e向量检索FAISS最小实现够用数据量大再换 Milvus证据源结构化知识库、可信网页快照必须带版本和时间戳版本方面建议使用 pip 安装时能获得的最新稳定版本不要在线上环境随意升级。下面安装示例会直接给出模块名不绑定旧版本号避免误导。4.3 证据源设计这是整个系统里最容易被忽视的部分。你需要的证据不是“随便搜出来的网页”而是经过筛选、有时间标记、来源可信的语料。在实际项目中我们用的是内部知识库加上少量公开的、经过人工确认的数据集。每个证据条目都保留标题、发布时间和来源方便回溯。5. 基于证据的检测系统一个可运行的最小实现本节会给出一个完整的最小检测系统核心流程如下输入文本 → 断言提取 → 证据检索 → 一致性判别 → 输出 JSON5.1 步骤一断言提取为什么不能直接把整段文本丢给判别模型因为一段话里可能包含多个独立事实。比如“2024 年国内新能源汽车销量同比增长 35.2%比亚迪销量首次突破 400 万辆”这句话包含两个断言必须拆开分别检索证据否则其中一个断言的错误会被另一个掩盖。断言提取用大模型完成如下# file: extract_claims.py import json from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY ) def extract_claims(text: str) - list[str]: prompt ( 你是一个事实抽取器。请把用户文本拆成尽可能少的、可用于外部核实的断言。\n 要求\n 1. 每条断言必须是独立的陈述句\n 2. 只抽取带事实内容的句子不抽取建议、情感和推测\n 3. 返回 JSON格式为 {\claims\: [\...\, \...\]}。\n ) resp client.chat.completions.create( modelqwen2.5-14b-instruct, temperature0, response_format{type: json_object}, messages[ {role: system, content: prompt}, {role: user, content: text}, ], ) data json.loads(resp.choices[0].message.content) return [c for c in data.get(claims, [])] if __name__ __main__: text 2024年国内新能源汽车销量同比增长35.2%比亚迪销量首次突破400万辆。 print(extract_claims(text))这里的关键参数是temperature0。事实抽取阶段不能有随机性模型每次输出同一个输入的抽取结果必须一致。另一个关键点是response_format{type: json_object}它要求模型返回合法 JSON避免后续 parse 失败。5.2 步骤二证据检索每条断言都需要从证据库中找出相关材料。最常用的方式是嵌入向量相似度检索。下面这段代码把证据库编码后存入 FAISS 索引然后根据声明检索 top-k 条证据# file: retriever.py from sentence_transformers import SentenceTransformer import faiss import numpy as np class EvidenceRetriever: def __init__(self, corpus: list[str]): self.corpus corpus self.encoder SentenceTransformer(BAAI/bge-small-zh-v1.5) content_vecs self.encoder.encode( corpus, normalize_embeddingsTrue, batch_size64 ) self.index faiss.IndexFlatIP(content_vecs.shape[1]) self.index.add(content_vecs) def retrieve(self, query: str, top_k: int 3) - list[dict]: query_vec self.encoder.encode([query], normalize_embeddingsTrue) scores, indices self.index.search(query_vec, top_k) results [] for score, idx in zip(scores[0], indices[0]): results.append({ text: self.corpus[idx], score: float(score) }) return results这里有几个需要注意的地方使用IndexFlatIP是因为它返回内积相似度配合normalize_embeddingsTrue效果等价于余弦相似度。如果证据库很大FAISS 的IndexFlatIP检索效率会下降生产环境可以考虑换用 HNSW 索引。检索的召回率直接影响后续判断质量。如果检索不到正确证据再强的判别模型也会误判。5.3 步骤三一致性判别拿到证据后需要一个判别模块完成“支持、反驳、未知”的判断。这个模块我们用另一个大模型调用实现prompt 中明确要求模型引用证据而不是凭记忆回答# file: judge.py import json from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY ) def judge_claim(claim: str, evidences: list[dict]) - dict: evidence_text \n.join(f- {e[text]} for e in evidences) resp client.chat.completions.create( modelqwen2.5-14b-instruct, temperature0, response_format{type: json_object}, messages[ { role: system, content: ( 你是陈述真值判别器。给定断言和证据判断该断言是否被证据支持。\n 输出 JSON{\verdict\: \支持/反驳/未知\, \reason\: \一句话解释\, \confidence\: 0.0-1.0}\n 注意证据不足或证据互相冲突时输出未知。 ), }, { role: user, content: f断言{claim}\n证据\n{evidence_text}, }, ], ) return json.loads(resp.choices[0].message.content)这个 prompt 设计有两点很重要第一系统提示里直接写了“证据不足时输出未知”这比在代码里做阈值判断更灵活。如果证据只有一条且与陈述部分相关模型有理由选择“未知”。第二要求模型返回reason字段。这个字段不是摆设它是后续人工复核和日志审计的关键依据。一个只返回“支持”和 0.95 置信度的系统出了问题完全无法排查。5.4 步骤四主流程串联将上面三个模块串起来得到最终流水线# file: main.py from extract_claims import extract_claims from retriever import EvidenceRetriever from judge import judge_claim corpus [ 2024年全年新能源汽车销量同比增长35.2%, 比亚迪2024年新能源汽车销量突破400万辆, 新能源汽车渗透率首次超过50%, 某品牌2024年销量未达预期同比下降10%, ] retriever EvidenceRetriever(corpus) def run_pipeline(text: str) - list[dict]: claims extract_claims(text) output [] for claim in claims: evidences retriever.retrieve(claim, top_k2) judgment judge_claim(claim, evidences) output.append({ claim: claim, verdict: judgment[verdict], confidence: judgment[confidence], reason: judgment[reason], evidence: [e[text] for e in evidences], }) return output if __name__ __main__: sample 2024年国内新能源汽车销量同比增长35.2%比亚迪销量首次突破400万辆。 print(json.dumps(run_pipeline(sample), ensure_asciiFalse, indent2))到这里一个最小可用的 AI 谎言检测器就跑通了。它不直接回答“这句话是不是谎言”而是回答“这句话在给定证据下能不能被证实”。这两个目标之间才是真实业务里能拿到的安全感。6. 运行结果与评估方法6.1 预期的输出效果运行main.py后期望看到类似下面的输出[ { claim: 2024年国内新能源汽车销量同比增长35.2%, verdict: 支持, confidence: 0.87, reason: 证据显示2024年全年新能源汽车销量同比增长35.2%与断言一致。, evidence: [ 2024年全年新能源汽车销量同比增长35.2% ] }, { claim: 比亚迪销量首次突破400万辆, verdict: 支持, confidence: 0.91, reason: 证据显示比亚迪2024年新能源汽车销量突破400万辆与断言一致。, evidence: [ 比亚迪2024年新能源汽车销量突破400万辆 ] } ]如果证据库中缺少相关条目模型应该输出{ verdict: 未知, confidence: 0.55, reason: 现有证据无法确认该断言需要补充更多资料。 }这里要注意置信度 0.55 并不代表“可能是真”它只是模型内部不确定性的粗略反映。不要把它当成真实的概率来使用。6.2 评估指标评估一个 AI 谎言检测器不能只看准确率。至少要看下面四个指标Macro F1对“支持、反驳、未知”三个类别分别计算 F1 再取均值避免多数类主导分数。校准误差ECE把模型置信度分成 10 个桶每个桶里计算平均置信度和实际正确率的差距差距越小代表校准越好。未知率模型在测试集上输出“未知”的占比。未知率过高说明证据检索太弱未知率过低说明模型在过度自信。对抗改写后的准确率对测试集做数字替换、主体替换、语序打乱后重新评测。6.3 校准误差计算脚本下面是一个简单的校准误差计算函数# file: calibration.py import numpy as np def expected_calibration_error(confidences, corrects, n_bins10): confidences np.array(confidences) corrects np.array(corrects, dtypeint) bins np.linspace(0, 1, n_bins 1) ece 0.0 for i in range(n_bins): mask (confidences bins[i]) (confidences bins[i 1]) if np.sum(mask) 0: continue bin_acc np.mean(corrects[mask]) bin_conf np.mean(confidences[mask]) ece np.sum(mask) / len(confidences) * abs(bin_acc - bin_conf) return ece6.4 比较不同设计的效果把不同阶段的模型放在同一批测试集上你会看到这样的趋势方案准确率Macro F1对抗准确率说明纯二分类0.810.720.58容易过度自信二分类 证据检索0.840.780.69证据不足时仍会乱猜三分类 证据检索0.820.760.74准确率略降但对抗稳定性提升三分类 证据检索 校准0.810.770.73置信度更可信这里的数字只是示意用来体现一个规律追求单一准确率会误导产品方向。三分类方案在准确率上不一定占优但它在真实场景里更可靠因为它允许模型说“我不知道”。7. 常见问题与排查思路实际运行中最容易遇到以下几类问题。问题现象可能原因排查方式解决方案模型返回的 JSON 解析失败输出被截断或模型没有遵循 response_format查看原始模型输出确认是否有额外字段增加重试逻辑或把 response_format 改为 json_object 后再试断言被切得过于零碎模型把“小明昨天去了上海”拆成多条读提取结果检查 prompt 是否明确要求“尽量少”在 prompt 中加入最少条数限制必要时后处理合并检索结果为空断言太抽象或嵌入模型对领域术语不敏感打印检索分数尝试换用领域语料微调嵌入模型增加同义改写或提高 top_k 并加入重排序判别模型总是输出“支持”证据检索返回了高相似但不相关的文本或者 prompt 中缺少“未知”约束检查 evidence 字段内容看是否真实支持断言强化 prompt 中“证据不足输出未知”并加入负样本测试长文本超过上下文窗口输入文档过长统计输入 token 数先对文本做段落切分再分段抽取断言一个需要特别注意的坑是“证据检索返回高相似但不相关文本”。向量检索判断的是语义相似度不是严格的事实包含关系。比如声明“某车企销量增长”检索回来的证据可能是“某车型口碑增长”语义上相关但事实主体不一致。这种问题通常需要通过增加重排序模型或规则过滤来解决。8. 把检测器放进生产环境工程建议8.1 版本管理不要只管理模型版本还要同时管理 prompt 版本。实际上prompt 的改动对结果的影响往往比换模型更大。建议把 prompt 和模型名称一起作为系统版本号例如ver-detector-v1.2_qwen2.5-14b记录在每一次输出的 JSON 中。8.2 阈值与未知策略生产环境不要直接使用模型输出的原始判定结果。可以在“支持”和“反驳”之外设置置信度阈值。例如confidence 0.85直接输出结论0.6 confidence 0.85进入人工复核队列confidence 0.6按“未知”处理。这个阈值要根据业务误判成本来调整。如果条目被错误删除的代价很高阈值就要提高。8.3 日志与审计每一次检测结果都应该保留完整的证据快照。内容包括输入文本、抽取出的断言、检索到的证据原文、模型输出、模型版本、prompt 版本、调用时间。这样一旦业务方发现误判可以快速复现问题。否则过了一个月再想排查可能连 evidence 都找不回来。8.4 安全与合规边界AI 谎言检测器绝不应该直接用于刑事侦查、雇佣决策、信用评分等敏感场景。目前的系统只是“证据一致性判断器”它的结论充其量只能作为辅助线索。必须在产品说明中明确标注“AI 辅助判断需要人工复核”避免系统被当成自动裁决工具。8.5 持续回归检测器依赖的证据库会不断更新大模型服务也可能升级。这两个变量都会导致线上结果漂移。建议搭建一个每天运行的回归测试集包含一批固定的对抗样本和正常样本一旦发现打分出现明显波动就要回退到上个版本。9. 总结与后续学习方向Aletheias Quest 这个项目让我最深的体会是不要把“检测谎言”当成一个分类问题而要把“验证陈述”当成一个检索加推理问题。可运行的检测器核心就四件事断言切分、证据检索、一致性判别、校准评估。这四件事做好系统的可靠性远超过直接让大模型回答真假的方案