
做RAG项目落地的时候我被客户一份测试报告问住了模型回答和检索回来的资料“大体对得上”但一追问细节就露馅——要么引用了原文里根本没有的统计数字要么把两个不同来源的数据混在一起说要么干脆自己编了一个听起来很专业的术语。这类现象就是典型的大模型幻觉而要系统性地评估它第一步就是分清两个维度忠实性Faithfulness和事实性Factuality。这两个词在日常讨论里经常被混用但它们在评估体系里指向的问题完全不同混在一起做评测最后只会得到一张看不懂的报告。我见过太多团队拿着一个“幻觉率”指标到处跑结果模型改了一版之后“幻觉率”降了业务方却反馈错误更多了。原因就是幻觉评估本身没拆开忠实性衡量的是模型输出是否忠于给定的上下文材料事实性衡量的是输出是否符合真实世界知识。这篇文章就把这两个概念、各自的评估方法、指标计算和落地踩坑完整讲一遍适合正在做RAG、微调或者大模型评测的工程师参考。文章里的代码和参数都是可以直接抄走用的那种不是我编的“理想实验”。1. 先弄清楚忠实性和事实性到底在评估什么1.1 两个概念的分野答非所问和胡说八道是两回事很多人把“模型输出错误”统称为幻觉然后随便挑一个指标就开始跑。但严格来说幻觉评估应该分成两条线。忠实性Faithfulness衡量的是给定上下文S模型生成GG是否被S支持、能否从S中推导出来。它关心的是“模型有没有忠实于输入材料”本质是上下文一致性评估。比如做摘要任务时原文没提到的内容被模型写进摘要这就是不忠实哪怕写进去的句子在真实世界里恰好是对的它仍然算忠实性错误。事实性Factuality衡量的是模型生成GG是否符合真实世界知识。它关心的是“模型说出来的东西是不是真的”本质是真值评估。哪怕G完全忠于输入材料但材料本身过时或错误输出照样是不事实的。用一个考试类比忠实性像“开卷考试时有没有抄对材料”事实性像“答案本身对不对”。开卷考试抄了一段过期教材抄得很忠实但不是事实闭卷考试蒙对了答案事实正确但这个过程根本没有“忠实”可言。这个分野在工程上特别重要。RAG管线里如果模型输出“不事实”问题可能在检索没召回正确文档、知识库数据本身就是错的、甚至提示词设计让模型强行采用了错误片段如果模型输出“不忠实”问题基本锁定在生成阶段是模型没有按检索结果作答这时候你要调的就是解码参数、指令遵循能力、或者是反幻觉微调的方向。这两个错误的修复路径完全不同混在一起根本没法定位问题。1.2 为什么容易混淆摘要任务里的交集与分歧忠实性和事实性之所以被混用主要是因为它们在很多任务里表现高度相关。尤其在抽象摘要和RAG问答场景下输入材料通常来自真实世界模型如果忠实于材料输出往往也符合事实。于是很多人默认“忠实就等于事实”评测的时候只跑一个指标。但实际情况要复杂很多。我拆过一批金融舆情摘要原文是一份未经证实的市场传言模型老老实实把传言摘要出来了。你拿事实性指标去打它“不事实”拿忠实性指标去打它“很忠实”。这时候只给一个幻觉率你根本不知道是数据源该清洗还是模型该约束。学术上有一套对应的分类方式内在幻觉intrinsic hallucination指输出与输入上下文冲突外在幻觉extrinsic hallucination指输出与上下文无关、但可能在真实世界中成立。内在幻觉基本对忠实性问题外在幻觉则跨在忠实性和事实性之间。所以有些综述文章会把忠实性当作事实性的一个子集来看但这在工业落地时反而容易误事。我现在的做法是评估矩阵里忠实性和事实性分成两列单独标注必要时候再加一列“来源可验证性”专门记录材料本身是否权威、是否可查证。这样一来一份测试报告至少能回答三个问题模型有没有抄错材料抄的材料本身有没有问题如果材料没错模型有没有额外编内容2. 忠实性评估怎么判断模型有没有“照着稿子念”2.1 NLI蕴含判定最主流的忠实性评估底座忠实性评估里最成熟、最简单的一类做法是把问题转化成一个自然语言推理NLI任务。NLI模型的输入是一对句子模型判断“前提”是否蕴含“假设”。映射到评估场景前提是原始上下文假设是模型生成的内容。模型如果输出的是“蕴含”entailment说明生成内容是忠于原文的如果是“矛盾”contradiction或者“中性”neutral就要怀疑存在幻觉。这个思路的优点是零训练成本、速度快、开箱即用。经典的底座模型包括DeBERTa-large-MNLI、BART-large-MNLI以及T5系列在ANLI数据上微调的版本。中文场景可以找基于中文MNLI或CMNLI微调的模型但坦白说开源中文NLI底座的质量整体不如英文稳定我后面会专门讲怎么补救。实操上长文本不能直接整段丢进NLI模型需要先切片再逐对判断。我惯用的参数是这样窗口大小256到512个token超过512会截断关键信息。滑动步长128个token保证上下文覆盖。聚合策略取所有片段蕴含概率的均值或者取最低分作为整段风险分。业务要求高时用最低分要求平滑时用均值。判定阈值蕴含概率大于0.6判为忠实小于0.4判为不忠实中间算不确定。NLI方案有两个系统性短板我用下来踩得比较深。第一个是数值不敏感原文写“收入增长5%”模型输出“收入增长10%”不少NLI模型会判成蕴含。第二个是否定句容易翻车原文“公司没有裁员”模型输出“公司裁员了”部分模型也会误判。所以纯NLI方案更适合做初筛不能作为唯一依据。2.2 基于QA的回译验证用问题倒推答案是否在原文里比NLI更精细的一类方法是QA-based评估核心思路是“哪里有信息哪里就能回答问题”。流程分三步根据模型的生成内容用问题生成模型question generation自动生成一组问题问题要覆盖生成内容的关键信息。用同一个阅读理解/问答模型分别拿“原始上下文”和“模型生成内容”来回答这些问题。比较两组答案的一致性答案差异越大说明生成内容越可能脱离原文。代表方法有QAFactEval和QuestEval。它们的共同点是比NLI更能抓住局部错误比如数字、日期、专有名词的改变。原因很简单问答任务对信息残缺很敏感原文里没有的答案模型硬回答出来两组答案一定对不上。实操细节上问题生成这一步现在可以直接用ChatGLM、Qwen这类指令模型来做提示词里要求“只问与关键事实相关的问题每个问题只针对一个实体或一个数值”。QA模型可以用抽取式阅读理解也可以用生成式问答但抽取式更稳定它会直接把原文里的片段抽取出来方便做精确比对。答案一致性匹配我用过三种精确匹配、token级F1、语义向量相似度。从效果看token级F1和语义相似度都比较稳精确匹配对措辞变化太敏感容易把正确答案判成错误。这个方案也有代价计算量大长文本生成几十个问题再逐个回答跑一轮要花很久。而且问题生成器本身如果质量不行评估结果会失真。所以我通常只在NLI初筛出风险样本后再上QA验证做二次确认而不是全量跑。2.3 信息抽取对齐从实体关系到完整三元组第三种忠实性评估思路是把文本转换成结构化信息再对齐适合对精确度要求极高的场景比如金融、医疗报告摘要。做法是这样的用信息抽取工具把原文和生成内容分别抽成结构化表示常见形式包括实体列表、关系三元组头实体、关系、尾实体、事件参数触发词、参与者、时间地点。然后做对齐比较检查生成内容里是否存在“原文没有的实体”、“关系颠倒的三元组”、“张冠李戴的事件参数”。开源方面OneKE是我用过比较顺手的知识抽取框架它把命名实体识别、关系抽取、事件抽取统一在一个框架里能直接输出结构化三元组。也可以用传统的OpenIE工具但输出粒度更粗糙清洗成本高。对齐这一步有两种做法简单场景用字符串规范化匹配把大小写、时态、别名归一化后做精确匹配复杂场景让LLM做语义对齐把两边的三元组集合丢给模型让它逐个判断“生成内容的三元组是否被原文支持”。我实测下来LLM做对齐的准确率明显更高但速度比规则慢。折中方案是两个结合先规则过滤明显一致的剩下模糊的再让LLM判。这种结构化对齐的方式有个天然好处它能给出“错误类型”而不是一个孤立的分数。比如你能看出来错的是实体对象还是关系方向这对后续做坏例分析特别有用。3. 事实性评估怎么判断模型有没有“睁眼说瞎话”3.1 闭卷问答与知识探测模型内部知识的地基测试事实性评估的第一类主流做法是“知识探测式”不给模型任何外部资料直接问它常识或事实性问题看它回答得对不对。这个思路测试的是模型内化在参数里的知识是否可靠。最有名的基准是TruthfulQA。它包含817个问题问题设计得很“阴险”专门挑那些人们普遍存在错误常识或者容易说错的话题。评估时看两个维度truthfulness即回答是否避免传播错误常识informativeness即回答是否真的提供了有用信息而不是拒绝回答。比如模型回答“我不确定”虽然很安全但informativeness会很低。实际跑TruthfulQA时我建议直接用生成式回答加LLM裁判而不是用多选。多选模式会显著低估模型的真实幻觉风险因为模型可以在选项里“猜”出正确答案。裁判模型我固定用同一个版本不要今天GPT-4o明天GPT-4.1结果完全不可比。裁判的提示词里要明确给出正确参考并要求输出“正确/错误/不确定”三分类再附一句判断理由。这类方法的局限很明显它只能测模型“脑子里的静态知识”测不了使用外部资料时的动态表现。RAG场景里模型很多情况下会正确引用检索文档即使内部知识是错的这种情况下闭卷评估完全测不出来。所以知识探测更适合做基座模型选型、微调前后知识保留情况检查不适合做业务系统的幻觉验收。3.2 检索增强验证让外部知识库当裁判第二种事实性评估思路是把模型输出当成一组“声明”来做事实验证。最典型的代表是FActScore。FActScore的做法分三步把模型生成的长文本拆解成原子事实atomic facts。所谓原子事实就是不能再拆的独立陈述每个事实包含完整的主谓宾结构比如“张三出生于1980年”是一个原子事实“张三出生于1980年并且在2005年加入了某公司”应该拆成两个。对每个原子事实用检索系统去知识库比如维基百科里找证据判断该事实是否被支持。计算支持率被支持的原子事实数量除以总原子事实数量。我沿用FActScore的经验把答案按实体分组这样可以算出每个实体的支持率定位到底哪类实体最容易出错。这个价值很大比如做人物传记生成时发现“出生日期”这一列支持率特别低说明模型在时间信息上不可靠后续反幻觉训练就主攻数值类错误。原子事实的分解可以直接用LLM完成。我用的提示词会要求“把段落拆成最小粒度的独立事实每个事实必须能单独判断真假”并且在输出上做JSON约束。如果拆出来的事实粒度过大比如“张三是一位著名的科学家和企业家”支持率会虚高因为这种模糊陈述很容易在资料里找到佐证。粒度过小又会把同一件事拆碎验证起来很慢。如果业务场景没有外部知识库可用还有一个变体叫SelfCheckGPT不查外部知识库而是让模型对同一个输入多次采样生成多个回答然后判断待评估的输出是否和其他采样结果保持一致。它利用的是“模型如果对知识不确定采样结果会混乱”这一特性。实测下来SelfCheckGPT对“编造型幻觉”的检测能力不错但对“所有采样都一致地错”的情况无能为力——模型可能每次采样都错同一处。3.3 常见事实性评估基准TruthfulQA、FActScore到HaluEval除了TruthfulQA和FActScore还有几个数据集在社区里用得比较多我列一个表方便你对照选型基准领域规模评估方式适合场景TruthfulQA常识知识817题生成或多选基座模型知识可靠性测试FActScore人物传记约500篇原子事实分解外部验证长文本生成、人物类问答HaluEval通用问答/摘要/对话约35000条幻觉二分类检测RAG、通用问答、聊天FEVER事实验证185445条claim检索外部证据判定验证类任务、检索评估HaluEval-Wild自然开放对话数千条幻觉检测真实开放域对话评估选基准的时候我有个经验别只看一个benchmark。TruthfulQA分数高不代表RAG问答里幻觉少因为题目是通用的不贴近你的业务领域。FActScore需要外部知识库如果知识库本身和业务领域有偏差评测结果也不可信。HaluEval覆盖面广但数据质量参差实测下来有些标注明显过时。所以我的做法是对外汇报用TruthfulQAFActScore两个指标说明“模型本身知识可靠度”和“长文本生成幻觉率”对内分析用HaluEval和自己构造的领域测试集MDD对真实业务更有参考价值。领域测试集的构造方法后面讲。4. 实操搭一套可落地的幻觉评估流程4.1 评估管线总览数据准备、模型调用、结果聚合讲完方法落地才是硬道理。一套完整的幻觉评估管线大致长这样输入准备从测试集里抽样本构造统一格式的输入保存成JSONL。生成调用待评估模型固定temperature和top_p记录生成结果。评估按评估目标选择忠实性或事实性评估器对生成结果打分。聚合把多条样本的结果聚合成整体指标并且按错误类型分组统计。报告输出结构化报告供人分析。这里最关键的一点是生成阶段的参数必须固定。很多人跑评测的时候temperature一会儿设0.3一会儿设0.7跑出来的“幻觉率”波动非常大最后根本分不清是模型变好了还是随机性在起作用。我自己固定temperature0.1top_p0.9所有对比实验都用同一组参数。评估器模型可以在线的用大型模型在本地用中等规模模型配合vllm部署。需要说明的是评估器本身也会出错所以低成本阶段可以先跑一个规模较小的评估模型比如7B到13B级别等候选版本收敛后再用更强模型复跑一遍做最终验收。下面是一段评估管线的骨架代码可以直接当模板用import json from openai import OpenAI client OpenAI(base_urlhttp://localhost:8000/v1, api_keyEMPTY) def generate(question: str, system: str 你是一个严谨的助手): resp client.chat.completions.create( modelqwen/Qwen2.5-14B-Instruct, messages[ {role: system, content: system}, {role: user, content: question}, ], temperature0.1, top_p0.9, max_tokens512, ) return resp.choices[0].message.content def evaluate_faithfulness(source: str, output: str) - dict: # 这里可以替换成NLI模型调用、QA验证或者LLM裁判 prompt f判断下面的生成内容是否忠实于给定材料。 材料{source} 生成内容{output} 只输出JSON{{faithfulness: 忠实/不忠实, reason: 判断理由}} result generate(prompt, system你是一个严格的忠实性评估器) return json.loads(result)实际跑的时候把数据源换成自己的测试集评估函数换成你要用的方法后面的事情就是批量调用加结果落盘。4.2 核心指标计算细节蕴含概率、支持率与置信区间评估流程跑完之后最常计算的核心指标主要有三个NLI蕴含概率、QA答案匹配分数、FActScore支持率。NLI蕴含概率的计算是把所有句子对的蕴含概率平均。更稳的做法是加一个置信区间而不是只看点估计。用bootstrap抽样1000次算出95%置信区间指标差0.01在置信区间重叠时根本不算差异。QA答案匹配分数的计算推荐用token级F1def token_f1(pred: str, gold: str) - float: pred_tokens pred.lower().split() gold_tokens gold.lower().split() common set(pred_tokens) set(gold_tokens) if not common: return 0.0 precision len(common) / len(pred_tokens) recall len(common) / len(gold_tokens) return 2 * precision * recall / (precision recall)FActScore支持率的计算也很直观def factual_score(supported: int, total: int) - float: return supported / total if total 0 else 0.0举个例子一条生成内容被拆出10个原子事实其中8个能在外部知识库中找到证据那FActScore就是0.8。如果某个实体相关的3个事实里只支持1个它的支持率就是0.33这个信号比整体0.8更有分析价值。原子事实分解的提示词我提供一个比较稳定的模板请把以下段落拆成最小粒度的事实陈述每个事实必须是独立的、可验证的、包含主谓宾的简单句。 要求 1. 每个事实只能表达一个信息点 2. 不重复、不遗漏 3. 输出为JSON数组 段落{text} 输出格式[事实1, 事实2, ...]拆完事实后把每个事实丢给检索系统找证据。我用的策略是BM25和向量检索并行各取top3文档然后合并去重。如果两个检索器都找不到证据这个事实会被标记为“证据不足”在最终分数里单独列出来不算进“明确支持”因为证据不足和明确矛盾是两种性质。4.3 与RAG、微调、本地部署场景的结合方式不同落地场景幻觉评估的侧重点完全不一样。我把最常碰到的三种场景拆开讲。先看RAG场景。RAG的评估要分两层第一层是“检索增强问答结果是否忠于检索到的文档”也就是把检索到的文档当作S去做忠实性评估看生成是否偏离了检索结果第二层是“生成内容是否符合真实世界知识”这个时候外部知识库变成裁判。如果第一层分数低问题在生成端模型没有按检索结果答如果第二层分数低但第一层高问题在检索端或知识库本身模型忠实引用了错材料。这套两层评估做完RAG调优的方向就清楚了。再看微调场景。用LlamaFactory这类平台做SFT或DPO时幻觉评估可以当早停标准来用。做法是在训练前先跑一遍baseline的幻觉指标然后在每个checkpoint上跑同样一批测试集观察指标变化。如果某个checkpoint的事实性分数掉得厉害说明训练过程破坏了已有知识如果忠实性分数持续上涨但事实性掉说明模型在“过度贴合训练格式”开始背训练集的东西了。最后看本地部署场景。如果你用vllm把评估模型部署成一个独立服务评估脚本不需要频繁加载模型权重速度和稳定性都会好很多。我在内网服务器上的做法是主服务部署业务模型评估服务单独部署一个裁判模型两边完全隔离评估跑批不会影响线上服务。这样即便评估任务量大也不怕把业务拖挂。5. 踩坑实录常见问题与实操心得5.1 常见问题速查表评估器本身也会“幻觉”幻觉评估做久了你会发现评估器自己也不完美。我整理了一份高频问题速查表帮助大家快速定位排查方向。现象可能原因解决办法NLI评估器对中文长文不敏感开源NLI底座多在英文数据上训练用双语/中文底座或改用基于LLM的裁判长文档截断后误报不忠实窗口过小关键信息被切掉调大窗口用滑窗重叠策略FActScore支持率虚高原子事实拆得不够细强化拆解提示词要求“最小粒度”LLM裁判偏爱长回答裁判被详细回答带偏裁判提示词里加“长度不应影响判断”不同版本裁判模型结果不可比评估器版本漂移固定裁判模型版本升级后重跑基线检索证据不足造成误判知识库覆盖不全单独标记“证据不足”不计入明确支持人类标注和评估器结论不一致评估器与业务判断标准不同用少量标注集对齐评估器阈值和规则模型把所有回答都写成“不确定”事实性指标里没同时看informativeness同时报告truthfulness和informativeness这里最想强调的一点是评估器的一致性不能只看表面。我刚开始做的时候直接用LLM裁判跑了一版结论感觉结果很合理结果人工复核后发现裁判在“数字幻觉”上漏判严重。后来我拿50条人工标注做金标准和裁判结果一对比算出来的Krippendorffs alpha才0.51完全不到可以信赖的水平。从那以后每个评估器上线前我都会先做一个30到50条的人工对齐。5.2 实操心得从只盯总分到按错误类型分析最后分享几个实操心得都是被项目毒打出来的。第一评估器阈值不要拍脑袋定。我有一次把NLI的蕴含阈值从0.5调到0.7整条业务线的“忠实率”瞬间从92%掉到78%业务方直接炸了。阈值必须结合人工标注来确定先标50条数据画出评估器分数分布找一个能让“人工判定忠实”和“评估器判定忠实”重合度最高的切点。第二不要用单一总分汇报幻觉。哪怕你的指标体系再标准汇总成一个“幻觉率”之后分析空间就没有了。我现在每份评估报告都按错误类型拆实体幻觉、关系幻觉、数字幻觉、逻辑幻觉、编造内容。做法是让裁判模型在判“不忠实”或“不事实”的同时输出错误类型标签。这样一整条流水线跑完报告能告诉你“错误主要集中在数字幻觉”而不是笼统的“模型有幻觉”。第三领域测试集比benchmark更可靠。通用benchmark适合横向对比但真正决定业务能不能用还得在“自己的文档自己的问题”上测。我维护了一个100到200条样本的领域测试集覆盖常见业务场景、边界场景、以及之前线上出过错的坏例每次模型迭代都跑这一套。领域测试集跑出来的指标哪怕比通用benchmark低也远比通用benchmark可信。最后再分享一个小技巧评估报告尽量做成结构化输出。每条样本除了分数还要带上“来源片段”“模型输出”“问题类型”这三类信息。排障时候直接按问题类型筛选比从头看一遍原始对话高效太多了。这套做法从我第一次做幻觉评估一直沿用到现在也是我认为最值得复制的一套工作流。