幻觉是大模型落地过程中绕不开的一道坎。模型会以非常流畅、自信的语气输出完全错误的信息,比如编造不存在的论文引用、虚构API参数、给出过时的政策条款。这类问题的本质在于:大模型的知识存储在参数里,训练数据一旦截止,模型对新知识一无所知;而对训练数据中低频出现的细节,模型又倾向用概率上“合理”的内容进行补全,而不是承认不知道。要缓解幻觉,业界目前主要有两条路线:一条是在生成前给模型补充外部知识,即检索增强生成(RAG);另一条是在生成后对输出内容进行校验,即事实核查。本文将两条路线结合起来,从原理到代码完整讲一遍。

幻觉到底是怎么产生的
要治理幻觉,先得理解它的成因。从技术层面看,大模型的训练目标是“给定上文,预测下一个token”,这个目标本质上是在拟合训练语料的概率分布,而不是学习一个事实数据库。当模型遇到训练集中很少出现的问题时,最大概率的续写内容未必是事实,而只是“看起来像事实的表达”。这就是为什么模型编造参考文献时,作者名、期刊名、年份往往都像模像样,但组合起来根本不存在。
幻觉大致可以分为两类:事实性幻觉和忠实性幻觉。事实性幻觉指输出内容与世界真实情况不符,比如把某个演员的出生地说错;忠实性幻觉指输出内容与用户提供的上下文或指令不符,比如你给了模型一份文档让它总结,它却在总结里加入了文档中没有的信息。两类幻觉的治理手段不同:事实性幻觉主要靠RAG补充外部知识,忠实性幻觉则需要在Prompt中明确约束,并加强生成后的一致性校验。
此外,采样温度也直接影响幻觉率。温度越高,输出越发散,模型越容易“自由发挥”。对于事实问答类任务,把temperature调低到0到0.2之间,能在一定程度上减少随机编造。但这只是治标,真正有效的方案还是要让模型“有据可依”。
RAG:让模型先查资料再回答
检索增强生成的核心思想很朴素:在把问题交给大模型之前,先从外部知识库中检索出相关内容,拼接到Prompt里,让模型基于给定材料作答,而不是单靠参数记忆。这样做有三个好处:一是知识可以随时更新,只需更新知识库而不用重新训练模型;二是回答有出处可查,用户可以核对原文;三是能显著降低长尾知识的幻觉率。
一个典型的RAG流程分为离线和在线两部分。离线部分负责构建知识库:把文档切分成合适大小的片段,用Embedding模型将片段转成向量,存入向量数据库。在线部分处理用户请求:把用户问题同样向量化,在向量库中做相似度检索,取回Top K个相关片段,重排后与原始问题一起送入大模型生成答案。下面是一个基于LangChain的简化实现:
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
# 1. 加载并切分文档
loader = TextLoader("company_docs.txt", encoding="utf-8")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个片段约500字符
chunk_overlap=50 # 片段之间保留重叠,避免语义被切断
)
chunks = splitter.split_documents(docs)
# 2. 向量化并入库
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh-v1.5")
vectorstore = FAISS.from_documents(chunks, embeddings)
vectorstore.save_local("faiss_index")
# 3. 构建问答链,k=3表示召回3个片段
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
return_source_documents=True
)
result = qa.invoke({"query": "公司年假有多少天?"})
print(result["result"])
实践中有几个容易踩的坑需要注意。文档切分不能太机械,纯按固定长度切会把一句话拦腰斩断,建议优先按段落、标题等语义边界切分;Embedding模型要选与业务语言匹配的,中文场景不要直接用英文模型凑合;检索回来的片段最好加一层重排(Rerank),用交叉编码器对粗排结果精排,召回质量能提升一个档次。另外,Prompt中务必明确要求“仅根据提供的资料回答,如果资料中没有答案,请直接说不知道”,这条约束对抑制忠实性幻觉非常有效。
事实核查:生成之后再加一道关卡
RAG解决的是“让模型有据可依”,但检索本身也可能召回错误或过期的内容,模型也可能无视给定的资料自说自话。因此成熟的方案都会在生成之后再做事实核查,形成“事前增强、事后校验”的双保险。事实核查的常用手段包括以下几种。
第一种是置信度评估。让模型对自己输出的每一条陈述给出置信分数,或者利用开源工具对每句话拆分后逐一验证。低于阈值的句子标记出来,提示用户谨慎参考。第二种是多源交叉验证:对关键论断从多个独立知识源检索证据,如果多个来源相互矛盾或都找不到支撑,就判定该论断存疑。第三种是引用比对,要求模型输出时附带来源片段,再计算输出内容与来源的语义相似度,相似度过低说明模型可能脱离了材料在自由发挥。
下面是一个简单的输出自检Prompt模板,可以嵌到生成流程之后:
你是一个事实核查员。请对以下AI生成的内容逐条检查:
【AI回答】
{answer}
【参考材料】
{retrieved_docs}
检查要求:
1. 将回答拆分为独立的事实性陈述
2. 判断每条陈述是否被参考材料直接支持
3. 输出JSON格式结果,包含字段:
statement(原句)、verdict(supported/unsupported/contradicted)、
evidence(支撑依据所在的材料片段)
4. 任何不被材料支持的陈述,都要如实标记,不要主观放行
这种“自我核查”方式实现成本低,效果却不错,尤其适合与RAG配合使用。如果预算允许,还可以引入专门的自然语言推理(NLI)模型做前提与假设的一致性判断,或者接入第三方事实核查API对高风险内容(如医疗、法律、金融)做二次把关。
工程上的整体架构与经验总结
把前面的内容整合起来,一个低幻觉的生成系统大致是这样的架构:用户提问先经过意图识别与问题改写,改写后的查询进入混合检索(向量检索加关键词检索,弥补纯向量检索对专有名词、编号类查询的弱势),召回内容经Rerank精排后拼接进Prompt,模型在低温度下生成带引用的回答,最后由核查模块逐条校验,不合格的内容要么重新生成,要么以风险提示的形式呈现给用户。
上线后的持续运营同样重要。建议把用户反馈中标记为“答错”的case收集起来,定期分析是检索没召回、片段切分不合理,还是模型无视了上下文,针对性优化。同时监控几个关键指标:引用命中率(回答中有引用的比例)、核查通过率、拒答率。拒答率不是越低越好,一个从不承认“不知道”的系统往往意味着幻觉风险更高。
最后要明确一个认知:幻觉无法被百分之百消除,只能被持续压低。参数化知识与检索知识会互相补充,事实核查则提供最后一道防线。与其追求一个永远不出错的模型,不如构建一套“可验证、可追溯、可纠错”的工程体系,这才是当前阶段应对幻觉最务实的路径。