RAG 幻觉问题:从上线翻车到稳定运行的血泪史
生产环境里的 RAG 系统不是论文里那么美好。分享我们踩过的坑和实际有效的解决方案。
# RAG 幻觉问题:从上线翻车到稳定运行的血泪史
去年我们上线了一个基于 RAG 的智能客服系统,主打"准确回答用户问题"。上线第一周,用户投诉量翻了三倍——不是没人回答问题,而是回答得越来越离谱。
最严重的一次,用户问"我的订单什么时候发货",系统回复:"根据我们的政策,发货时间取决于月球相位和您的星座运势。"
当然,这是夸张了。但真实的幻觉案例确实让人头疼:系统引用了不存在的文档、捏造了产品功能、甚至编造了虚假的客服电话。
幻觉的根本原因
RAG 系统的幻觉不是突然出现的,它是训练数据和检索机制缺陷的必然结果。
**问题一:检索结果不完整**
我们的向量数据库里只有产品文档,没有用户常见问题列表。当用户问了一些文档里没覆盖的问题,LLM 要么说"我不知道"(其实文档里有相关但不在检索结果里),要么开始编造。
**问题二:LLM 的"讨好倾向"**
GPT-4 和 Claude 这类模型在被训练时,被要求"尽量有帮助"。这意味着即使检索结果不支持答案,模型也可能尝试给出一个看似合理但实际上错误的回答。
**问题三:引用伪造**
更麻烦的是,模型会自己编造引用。你让它"基于提供的文档回答",它会引用"文档1第3节",但那个文档里根本没有那一节。
解决方案:我们的实践
方案一:检索后验证
这是最有效的方法。在 LLM 生成答案之前,先用一个轻量模型验证检索到的文档是否真的支持答案。
def validate_answer(query, documents, answer):
"""用轻量模型验证答案是否有文档支持"""
validation_prompt = f"""
请判断以下答案是否完全基于提供的文档:
问题:{query}
文档:{documents}
答案:{answer}
如果答案有文档支持,返回 YES;如果包含捏造内容,返回 NO。
只返回 YES 或 NO。
"""
result = llm.generate(validation_prompt, max_tokens=10)
return result.strip() == 'YES'
这个验证步骤增加了约 200ms 延迟,但把幻觉率从 15% 降到了 3% 以下。
方案二:检索阈值控制
不是所有检索结果都值得用。我们设置了一个 confidence score 阈值,低于阈值的检索结果直接标记为"未知"。
def search_with_threshold(query, threshold=0.75):
results = vector_db.search(query, top_k=5)
filtered = [r for r in results if r.score >= threshold]
if not filtered:
return [{"text": "未在知识库中找到相关信息", "score": 0}]
return filtered
这个简单改动解决了 60% 的幻觉问题——因为大部分幻觉都来自低质量检索结果。
方案三:让模型承认不知道
我们修改了 system prompt,明确告诉模型:"如果检索结果不支持回答问题,请直接说'我不知道',不要尝试编造答案。"
You are a customer service assistant.
Use ONLY the provided documents to answer questions.
If the documents do not contain enough information,
reply with "I don't have enough information to answer that question."
NEVER fabricate information or make up citations.
这个改变看似简单,但效果显著。模型不再"为了回答而回答",而是学会说不知道。
方案四:引用检查
我们在输出层加了引用验证:如果模型声称引用了某个文档,我们检查该文档是否真的在检索结果中。
def validate_citations(answer, retrieved_docs):
"""检查答案中的引用是否真实存在"""
doc_ids = set(doc.doc_id for doc in retrieved_docs)
# 提取答案中的引用标记 [doc_xxx]
citations = re.findall(r'\[doc_(\w+)\]', answer)
for cite in citations:
if cite not in doc_ids:
# 移除虚假引用
answer = answer.replace(f'[doc_{cite}]', '')
return answer
效果对比
| 指标 | 优化前 | 优化后 |
|------|--------|--------|
| 幻觉率 | 15% | 2.8% |
| "不知道"响应率 | 5% | 18% |
| 用户满意度 | 62% | 89% |
| 平均响应延迟 | 1.2s | 1.5s |
延迟增加了 0.3 秒,但用户满意度提升了 27 个百分点。这个 trade-off 很值得。
教训总结
2. **检索质量决定上限**:垃圾进,垃圾出
3. **承认不知道比编造答案更好**:用户能接受"我不知道",不能接受错误答案
4. **持续监控很重要**:上线后第一周就是靠用户反馈发现幻觉问题的
RAG 系统不是"装好就能用"的东西。幻觉是结构性问题,需要多层防御。我们的经验是:检索阈值 + 答案验证 + 引用检查 + 明确的 system prompt,这四层组合把问题控制在了可接受的范围。
还有很长的路要走,但至少现在系统不会在凌晨 3 点给用户发幻觉客服消息了。