Agent在执行任务时输出的内容看起来逻辑通顺、语气笃定,可一旦去核对细节,就发现时间、数字、人名甚至事件本身都是编的。这种现象就是幻觉。幻觉不是偶发的bug,而是大模型生成机制带来的固有风险:模型本质上是根据概率分布预测下一个token,它并不知道自己说的是真是假。要缓解这个问题,单靠换更大的模型或者调prompt远远不够,工程上更有效的路径是给Agent装上两道保险,一是检索增强,让模型基于外部知识回答,二是事实核查,在输出之前把关。

幻觉到底是怎么产生的
要治理幻觉,得先弄清它从哪来。大模型在训练阶段把海量语料压缩进参数,形成一种模糊的“记忆”。当用户问到训练数据里没有、覆盖很少或者已经过时的内容时,模型并不会老老实实回答“不知道”,而是倾向于利用语义相近的知识拼凑出一个看起来合理的答案。这就像一个人考试遇到没复习的题,宁可瞎编也不想交白卷。
具体拆开看,幻觉可以归为三类诱因。第一类是知识缺失型,模型的知识截止于训练数据时间点,问它最新的政策版本、产品价格、内部文档内容,它只能靠猜。第二类是上下文误导型,用户在提问里夹带了错误前提,比如“Python 4有什么新特性”,模型顺着错误前提往下编。第三类是推理链偏差型,Agent在多步任务中,前一步生成的错误结论被塞进上下文,后续步骤基于错误继续推演,误差像滚雪球一样放大。
针对这三类诱因,对策也不一样。知识缺失靠检索增强补外部知识,上下文误导靠输入侧的预处理和指令约束,推理链偏差则需要在Agent执行过程中插入校验环节。下面重点展开工程上收益最大的两条线。
检索增强生成:给Agent外接一个知识库
RAG的核心思路很直接:在模型生成答案之前,先把用户的问题拿去知识库里检索相关内容,把检索到的片段和原始问题一起送进模型,要求模型只依据这些材料作答。这样模型从“闭卷考试”变成“开卷考试”,幻觉率会显著下降,而且知识库可以随时更新,不依赖重新训练模型。
一套基础RAG流水线包含四个环节:文档切分、向量化、检索、生成。每个环节都有讲究。切分时建议按语义段落切,每块300到500字,并保留10%左右的重叠,避免关键信息被切在两块中间断了上下文。向量化用通用的embedding模型即可,中文场景选支持中文的模型效果更好。检索阶段推荐混合检索,也就是向量检索加关键词检索(比如BM25),两者取并集后再用重排序模型精排,召回率比单一向量检索高出不少。
import hashlib
def chunk_documents(docs, chunk_size=400, overlap=40):
"""按固定长度切分文档,保留重叠区域防止语义断裂"""
chunks = []
for doc in docs:
text = doc["content"]
start = 0
while start < len(text):
end = min(start + chunk_size, len(text))
chunk_text = text[start:end]
chunk_id = hashlib.md5((doc["id"] + str(start)).encode()).hexdigest()
chunks.append({
"id": chunk_id,
"doc_id": doc["id"],
"text": chunk_text,
"meta": doc.get("meta", {})
})
if end == len(text):
break
start = end - overlap
return chunks
def retrieve(query, vector_store, top_k=5):
"""混合检索:向量召回 + 关键词召回,简单合并去重"""
vec_hits = vector_store.semantic_search(query, top_k=top_k * 2)
kw_hits = vector_store.keyword_search(query, top_k=top_k * 2)
merged = {h["id"]: h for h in vec_hits + kw_hits}
# 此处可接入重排序模型,如bge-reranker
ranked = rerank(query, list(merged.values()))
return ranked[:top_k]生成环节同样关键。prompt里必须明确要求模型只根据检索到的材料回答,材料里没有的信息要么直说不知道,要么标注为推测。可以在system prompt里写清楚:如果上下文不足以回答问题,请直接回复无法根据现有资料回答,禁止自行补充事实。这条约束看似简单,但对压制模型“编造补全”的冲动非常有效。
RAG不是银弹,它也有自己的坑。检索质量差时,模型拿到一堆不相关的片段,反而会被带偏;知识库里本身有错误内容,错误会原样传递到答案。所以RAG上线前要对检索环节做评估,用一批标准问答对测召回命中率,命中率低于80%就该回头优化切分策略或换embedding模型。
事实核查:在输出前再设一道关卡
检索增强解决的是“模型不知道”的问题,但模型拿到正确材料后仍可能答错,比如理解偏差、过度概括、把材料里没说的结论说出来。这时候就需要事实核查。工程上最实用的是基于证据的逐句校验:把Agent生成的答案拆成一个个原子陈述,每条陈述单独去知识库或外部数据源检索证据,然后用一个小模型或规则判断该陈述能否被证据支持。
def verify_claim(claim, knowledge_base):
"""对单条陈述做证据校验,返回判定结果"""
evidences = knowledge_base.search(claim, top_k=3)
if not evidences:
return {"claim": claim, "verdict": "unsupported", "reason": "未检索到相关证据"}
prompt = (
"判断以下陈述能否被证据支持。"
"只能回答:supported / contradicted / insufficient。\n"
f"陈述:{claim}\n证据:{[e['text'] for e in evidences]}"
)
verdict = llm_call(prompt)
return {
"claim": claim,
"verdict": verdict,
"evidence_ids": [e["id"] for e in evidences]
}
def fact_check(answer_text):
"""拆分答案为原子陈述并逐条核查"""
claims = split_to_claims(answer_text)
results = [verify_claim(c, kb) for c in claims]
failed = [r for r in results if r["verdict"] != "supported"]
return {"passed": len(failed) == 0, "details": results}除了逐句校验,还有两种常用手段。一是多源交叉验证,针对关键数据(比如金额、日期、政策条款),从至少两个独立来源检索比对,一致才放行。二是置信度评估,让模型对每条关键陈述给出置信度分数,低于阈值的自动触发人工复核或者二次检索。生产环境里建议把这两种方式组合使用,关键事实走多源验证,一般陈述走证据校验,兼顾准确性和成本。
需要注意的是,核查环节本身也是模型在跑,它自己也可能出错。为了降低核查模型的幻觉率,应选择与主生成模型不同架构或不同数据来源的模型来做裁判,减少两者犯同样错误的概率。同时核查结果不要直接静默丢弃,而是把“contradicted”的陈述连同证据回传给生成模型,让它修正后重写,这样形成一轮自我纠错循环,通常一到两轮就能收敛。
把检索和核查组合成一个完整的Agent架构
单点优化不如整体设计。一个抗幻觉能力较强的Agent,通常把流程组织成四层:第一层是意图识别和问题改写,把用户的口语化提问转成适合检索的标准查询,并识别出错误前提提前纠正;第二层是RAG检索,按前文说的混合检索加重排序拿到高质量上下文;第三层是受约束的生成,prompt里明确要求引用来源,答案中的关键事实附带材料编号;第四层是事实核查,对输出逐条验证,不通过的触发重写。
这个架构里还有两个细节值得强调。第一是引用溯源,让模型输出时标注每个事实来自哪段材料,用户可以一键跳转核对。这不仅是提升信任感的手段,也倒逼模型少写没有依据的内容,因为无来源的事实会暴露得很明显。第二是评估闭环,上线后持续收集用户反馈和核查失败案例,定期回测知识库质量和检索命中率,把幻觉率作为核心指标纳入监控看板。
最后说一句务实的判断:幻觉没法被彻底消除,只能被压缩到业务可接受的水平。对客服问答这类容错率较高的场景,做好RAG加基本核查就够了;对金融、医疗这类高敏场景,建议再加上人工复核关卡和输出白名单机制,宁可回答得保守一点,也不要让Agent在关键事实上赌运气。