解决大模型"幻觉"问题的工程化方案:外挂知识库检索增强生成实战

写在前面:大模型很强大,但"一本正经地胡说八道"这件事,在工程落地时是要命的。本文不聊理论,只谈我们在生产环境里踩过的坑和验证过的方案。


一、为什么必须做 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”,向量检索可能完全找不到。

混合检索方案

  1. 向量检索:语义相似度,Top-K=20
  2. BM25 关键词检索:字面匹配,Top-K=20
  3. 使用 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,而模型上下文有限。我们做了两层压缩:

  1. 基于相关性的 Map-Reduce:对每个文档片段,先用小模型判断"是否与问题相关",过滤掉低相关段落
  2. 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 落地,建议按这个优先级推进:

  1. 先把数据层做扎实(解析、分块、清洗)
  2. 再优化检索质量(混合检索 + 重排序)
  3. 最后打磨生成体验(Prompt 工程 + 引用溯源)

hallucination 无法 100% 消除,但一个好的 RAG 系统可以把"胡说八道的概率"从 30% 降到 3% 以下——这在生产环境里,就是能用和不能用的区别。


本文基于实际项目经验整理,如有不同实践思路,欢迎评论区交流。

更多推荐