解决大模型“幻觉“问题的工程化方案:外挂知识库检索增强生成实战
解决大模型"幻觉"问题的工程化方案:外挂知识库检索增强生成实战
写在前面:大模型很强大,但"一本正经地胡说八道"这件事,在工程落地时是要命的。本文不聊理论,只谈我们在生产环境里踩过的坑和验证过的方案。
一、为什么必须做 RAG?
去年上线的一个客服问答系统,上线第一周就出了事故:用户问"你们产品的退款政策是什么",模型自信地给出了一套"7天无理由退款"的流程——但我们的产品实际政策是"14天有条件退款"。
这就是典型的幻觉(Hallucination):模型基于训练数据中的统计模式"编造"了一个看似合理但完全错误的答案。
为什么微调不够?
- 知识更新成本极高,业务文档每周都在变
- 微调后的模型仍然可能"记住"错误的旧知识
- 对于事实性问题,微调本质是"让模型背下来",而不是"让模型查清楚"
RAG 的核心价值:把大模型从"闭卷考试"变成"开卷考试"。模型不再依赖参数记忆,而是基于检索到的真实上下文生成回答。
二、RAG 系统整体架构
一个能扛住生产流量的 RAG 系统,远不止"向量数据库 + LLM"这么简单。我们的架构分层如下:
┌─────────────────────────────────────────┐
│ 应用层 (API Gateway) │
├─────────────────────────────────────────┤
│ 查询理解 │ 多轮对话管理 │ 意图识别/路由 │
├─────────────────────────────────────────┤
│ 检索层 (Retrieval) │
│ 混合检索 │ 重排序 │ 查询改写/扩展 │
├─────────────────────────────────────────┤
│ 数据层 (Knowledge Base) │
│ 文档解析 │ 分块策略 │ 向量化/索引 │
├─────────────────────────────────────────┤
│ 生成层 (Generation) │
│ Prompt 工程 │ 上下文压缩 │ 引用溯源 │
└─────────────────────────────────────────┘
三、数据层:决定 RAG 上限的环节
很多人把 RAG 做砸,问题不在模型,而在数据准备。
3.1 文档解析:别小看 PDF 和表格
业务知识库往往不是干净的 Markdown,而是:
- 扫描版 PDF(需要 OCR)
- 复杂排版(页眉页脚、多栏布局)
- 嵌套表格和图表
我们的方案:
- 使用
Unstructured+PaddleOCR做文档解析 - 对表格单独处理:将 HTML 表格转为 Markdown 格式,保留行列关系
- 关键元数据(文档标题、章节层级、发布时间)必须保留,用于后续过滤
3.2 分块策略:不是越小越好
初期我们按固定 512 token 分块,结果检索到的片段经常"断章取义"。
优化后的分块策略:
| 策略 | 适用场景 | 实践建议 |
|---|---|---|
| 语义分块 | 长文档、技术规范 | 按段落/章节边界切分,保持语义完整 |
| 滑动窗口 | 需要上下文的问答 | 设置 20% 重叠,避免关键信息被截断 |
| 父子块 | 需要精准定位 + 完整上下文 | 子块用于向量检索,父块用于生成上下文 |
**父子块(Parent-Child Chunking)**是我们目前的主力方案:
- 子块:细粒度(如 128 token),用于高精度语义检索
- 父块:粗粒度(如 1024 token),检索到子块后取其父块作为上下文输入 LLM
# 伪代码示意
child_chunks = split_by_sentence(doc, chunk_size=128)
for child in child_chunks:
parent = find_parent_section(child) # 找到所属章节
store_vector(child.embedding, metadata={"parent_id": parent.id})
3.3 向量化与索引
- Embedding 模型:不要用 OpenAI 的
text-embedding-ada-002做中文场景。我们对比后选择了BAAI/bge-large-zh-v1.5,在中文语义相似度上明显更好。 - 向量数据库:Milvus 适合大规模(亿级),Qdrant 适合中小规模且部署简单。我们最终选了 Qdrant,因为运维成本低。
- 索引策略:HNSW 索引 + 余弦相似度,ef_construction 设为 200,在召回率和查询速度间取得平衡。
四、检索层:从"召回"到"精准召回"
4.1 混合检索:向量 + 关键词
纯向量检索有个致命问题:对专有名词、产品型号、代码片段的召回率很低。比如用户搜 “API-2024-ERR-Timeout”,向量检索可能完全找不到。
混合检索方案:
- 向量检索:语义相似度,Top-K=20
- BM25 关键词检索:字面匹配,Top-K=20
- 使用 RRF(Reciprocal Rank Fusion)合并排序
def reciprocal_rank_fusion(vector_results, bm25_results, k=60):
scores = defaultdict(float)
for rank, doc in enumerate(vector_results):
scores[doc.id] += 1 / (k + rank + 1)
for rank, doc in enumerate(bm25_results):
scores[doc.id] += 1 / (k + rank + 1)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
4.2 查询改写(Query Rewriting)
用户原始查询往往表述模糊,直接拿去检索效果差。我们在检索前增加了查询改写模块:
- 意图澄清:“这个怎么配置?” → 结合对话历史补全为 “Nginx 反向代理怎么配置?”
- 查询扩展:生成 3-5 个同义表达,分别检索后合并结果
- HyDE(假设文档嵌入):让 LLM 先根据问题生成一个"理想答案",用这个理想答案去做向量检索。在医疗、法律等垂直领域效果显著。
4.3 重排序(Rerank)
初筛召回 20 个文档后,用专门的 Cross-Encoder 做精排:
- 模型:
BAAI/bge-reranker-large - 输入:Query + Document,输出:相关性分数
- 只取 Top-3 ~ Top-5 送入生成层
重排序这一步的收益非常明显:初筛 Top-5 的准确率从 65% 提升到 89%。
五、生成层:控制幻觉的最后一道防线
5.1 Prompt 工程:让模型"只根据材料回答"
我们迭代了十几版 Prompt,最终定稿的核心结构:
你是一位专业的技术支持工程师。请严格根据以下参考资料回答用户问题。
如果参考资料中没有相关信息,请明确回答"根据现有资料,我无法回答这个问题",不要编造。
参考资料:
{retrieved_context}
用户问题:{user_query}
要求:
1. 回答必须基于参考资料,优先引用原文
2. 如果涉及操作步骤,请按顺序列出
3. 如果参考资料之间存在矛盾,请指出并说明
4. 回答末尾列出引用的文档来源
关键点:
- 明确给出"拒绝回答"的指令,降低编造概率
- 要求列出引用来源,方便用户溯源和审核
5.2 上下文压缩
有时候检索到的 5 个文档加起来超过 8k token,而模型上下文有限。我们做了两层压缩:
- 基于相关性的 Map-Reduce:对每个文档片段,先用小模型判断"是否与问题相关",过滤掉低相关段落
- LLM 摘要:如果单篇文档过长,用 LLM 提取与问题相关的关键段落
5.3 引用溯源(Citation)
生产环境必须回答"这个结论出自哪份文档的哪一段"。我们的实现:
# 在分块时保留原始位置信息
chunk_metadata = {
"doc_id": "refund_policy_v2.pdf",
"page": 3,
"section": "2.1 退款条件",
"text": "..."
}
# 生成时要求模型标注引用
# 输出格式:根据《退款政策》第3页第2.1节[1],退款需满足...
前端渲染时,将 [1] [2] 等引用标记转为可点击链接,直接跳转到原文。
六、工程化落地:容易被忽视的细节
6.1 数据 Pipeline 自动化
知识库不是静态的,业务文档每天都在更新。我们搭建了定时同步 Pipeline:
业务文档源(Confluence/语雀/Git)
↓
定时爬虫(Airflow DAG,每小时)
↓
文档解析 + 分块 + 向量化
↓
写入向量库(自动去重/更新/删除)
↓
索引一致性校验(抽样检索验证)
去重策略:按文档 URL + 版本号做唯一键,避免重复入库。
6.2 离线评估体系
没有评估指标的 RAG 系统就是黑盒。我们建立了三类指标:
| 指标类型 | 具体指标 | 评估方式 |
|---|---|---|
| 检索质量 | Recall@K, MRR, NDCG | 人工标注 500 组 Query-Doc 对 |
| 生成质量 | 事实准确性、回答完整性 | 用 GPT-4 做裁判 + 人工抽检 |
| 端到端 | 用户满意度、转人工率 | A/B 测试 |
评估数据集构建:从生产日志中抽样真实用户问题,由业务专家标注"正确答案"和"应引用的文档"。
6.3 在线监控与告警
- 检索为空率:某类问题连续检索为空,说明知识库缺失
- 上下文利用率:LLM 输入中有效上下文占比,太低说明检索质量差
- 用户反馈:👍/👎 按钮,差评问题自动入池复盘
七、踩坑实录
坑 1:Embedding 模型"水土不服"
初期直接用了 M3E 做中文 Embedding,结果在专业术语上表现很差。后来用业务数据做了对比测试,发现 BGE 在垂直领域的区分度更好。Embedding 模型一定要在自己的数据上测,不要只看榜单。
坑 2:分块切断了因果关系
一份故障排查文档里有这样的结构:“如果现象 A,原因是 X;如果现象 B,原因是 Y”。按固定长度切分后,“现象 A” 和 “原因 X” 被切到了不同块里,检索时只召回了一半,模型只能瞎猜。
解法:在分块前做结构化解析,对"条件-结论"型内容做特殊处理,确保逻辑完整性。
坑 3:Prompt 里的上下文顺序影响答案
实验发现,把最相关的文档放在 Prompt 的前面比放在后面效果更好(Lost in the Middle 问题)。现在我们的策略是按重排序分数降序排列上下文。
坑 4:过度依赖 RAG,放弃模型能力
RAG 解决的是知识时效性和准确性问题,不是万能的。对于推理、创意类任务,不应该强行用 RAG 约束。我们在系统里做了意图路由:事实性问题走 RAG,开放性问题直接走模型。
八、写在最后
RAG 不是银弹,但它是当前工程化落地中最务实的大模型幻觉缓解方案。它的本质不是"让模型更聪明",而是"给模型配一副靠谱的眼镜"——让它在回答之前,先看清事实。
如果你正在做 RAG 落地,建议按这个优先级推进:
- 先把数据层做扎实(解析、分块、清洗)
- 再优化检索质量(混合检索 + 重排序)
- 最后打磨生成体验(Prompt 工程 + 引用溯源)
hallucination 无法 100% 消除,但一个好的 RAG 系统可以把"胡说八道的概率"从 30% 降到 3% 以下——这在生产环境里,就是能用和不能用的区别。
本文基于实际项目经验整理,如有不同实践思路,欢迎评论区交流。
更多推荐


所有评论(0)