Skip to content

企业级 RAG 知识库平台架构选型与踩坑全记录 ​

去年接了一个企业内部知识库项目——把公司十几年的文档、wiki、工单系统整合成一个「问一句就能答」的 AI 知识库。听起来简单,做下来踩了一身坑。这篇完整复盘整个架构选型过程和落地中遇到的真实问题。

需求拆解:先搞清楚在做什么 ​

很多 RAG 项目失败,不是技术不行,是需求没搞清。开干之前先明确三件事:

  1. 知识源是什么:PDF、Word、Confluence、工单系统、代码仓库?格式杂不杂?
  2. 用户是谁:内部员工还是外部客户?并发量多大?容忍多长延迟?
  3. 准确率底线:是「大概对就行」还是「必须精确到引用原文段落」?

我们这个项目的情况:

维度实际情况
知识源Confluence wiki + PDF 文档 + 工单系统,约 5 万篇
用户内部员工,峰值并发约 50 路
延迟要求首 token < 3s,完整回答 < 15s
准确率必须引用原文,不能瞎编

需求清楚了,才能做靠谱的架构选型。

全链路架构设计 ​

先看整体架构,再逐层说选型思路:

┌─────────────────────────────────────────────────────────────────────┐
│                         用户交互层                                   │
│                    Web UI / IM 机器人 / API                          │
└──────────────────────────┬──────────────────────────────────────────┘
                           │ 用户提问
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         服务编排层                                   │
│  ┌──────────┐  ┌───────────┐  ┌──────────┐  ┌──────────────────┐  │
│  │ Query    │→ │ 意图识别   │→ │ Query    │→ │ Rerank 重排序    │  │
│  │ 预处理    │  │ & 路由    │  │ 改写扩展  │  │ (Cross-Encoder) │  │
│  └──────────┘  └───────────┘  └──────────┘  └────────┬─────────┘  │
└───────────────────────────────────────────────────────┼─────────────┘
                                                        │ 召回上下文
                    ┌───────────────────────────────────┼───────────┐
                    │             检索层                 │           │
                    │  ┌──────────┐    ┌──────────────┐ │           │
                    │  │ 向量检索  │    │ 关键词检索    │ │           │
                    │  │ (Top-K)  │    │ (BM25)       │ │           │
                    │  └────┬─────┘    └──────┬───────┘ │           │
                    │       └───────┬────────┘         │           │
                    │          混合召回融合             │           │
                    └────────────────┬─────────────────┘           │
                                       │ Top-N 文档块               │
                                       ▼                            │
┌─────────────────────────────────────────────────────────────────────┐
│                         生成层                                       │
│  ┌──────────┐  ┌───────────┐  ┌──────────┐  ┌──────────────────┐  │
│  │ Prompt   │→ │ 上下文    │→ │ LLM 推理  │→ │ 答案后处理        │  │
│  │ 组装     │  │ 截断压缩   │  │ (流式)   │  │ + 引用标注        │  │
│  └──────────┘  └───────────┘  └──────────┘  └──────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
                                       │
                    ┌──────────────────┼──────────────────────┐
                    │             数据处理层                   │
                    │  ┌────────┐  ┌────────┐  ┌───────────┐  │
                    │  │ 文档   │→ │ Chunk  │→ │ Embedding │  │
                    │  │ 解析   │  │ 切分   │  │ 向量化    │  │
                    │  └────────┘  └────────┘  └─────┬─────┘  │
                    └──────────────────────────────┬──────────┘
                                                   │ 向量写入
                                                   ▼
                              ┌────────────────────────────────┐
                              │       向量数据库 + 元数据存储    │
                              │   (Milvus / Qdrant / pgvector)  │
                              └────────────────────────────────┘

这个架构不是一开始就这样的,是踩了好几个坑迭代出来的。下面按层说选型思路。

数据处理层:文档解析是隐形大头 ​

很多人以为 RAG 的难点在检索和生成,实际上文档解析才是最耗精力的环节。

文档解析选型 ​

方案优点缺点适用场景
PyMuPDF快、轻量表格解析差纯文本 PDF
Unstructured支持格式多速度慢、依赖重格式杂的场景
MarkerPDF 转 Markdown 质量高仅 PDF学术论文、报告
自研解析器完全可控开发成本高特殊格式需求

我们最终用 PyMuPDF + 自研表格提取的组合:常规文本走 PyMuPDF,遇到表格用 OpenCV 做行列检测再结构化。Confluence 和工单系统走 API 直接拿结构化数据,不解析文档。

Chunk 切分策略 ​

这是 RAG 效果的决定性因素之一。切太大检索不准,切太小语义断裂。

实践中的切分策略:

python
# 递归切分:先按章节,再按段落,最后按长度兜底
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,          # 每块 512 token
    chunk_overlap=64,        # 块间重叠 64 token,保证语义连贯
    separators=["\n## ", "\n### ", "\n\n", "\n", "。", ";", " "],
    length_function=lambda x: len(x)  # 中文按字符数
)

关键经验:

  • 重叠窗口必须有:64 token 的重叠能解决 80% 的语义断裂问题
  • 按标题层级优先切:不要上来就按长度硬切,先用 Markdown 标题分段
  • 表格不要切:整张表作为一个 chunk,切了就废了
  • chunk_size 没有银弹:512 是个起点,具体看你的文档特征调

检索层:混合召回是刚需 ​

纯向量检索在专业领域会漏掉关键词精确匹配的场景。比如搜「ISO 27001」,向量可能召回一堆信息安全的内容,但用户要的就是这个标准本身。

混合召回架构 ​

用户 Query
    │
    ├──→ 向量检索 (Embedding 相似度)  → Top 20 结果
    │                                    ↓
    ├──→ 关键词检索 (BM25)            → Top 20 结果
    │                                    ↓
    └───────────── 混合融合 (RRF 算法) ──→ Top 10 候选
                                         ↓
                                    Rerank 重排序
                                    (Cross-Encoder)
                                         ↓
                                    Top 3-5 最终上下文

RRF(Reciprocal Rank Fusion)融合公式简单但有效:

python
def rrf_fusion(vector_results, keyword_results, k=60):
    """RRF 融合:排名越靠前得分越高"""
    scores = {}
    for rank, doc in enumerate(vector_results):
        scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank)
    for rank, doc in enumerate(keyword_results):
        scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank)
    return sorted(scores.items(), key=lambda x: -x[1])

Rerank:花小钱办大事 ​

向量检索用的是双塔模型(Bi-Encoder),速度快但精度一般。Rerank 用 Cross-Encoder 做精排,把候选集从 10 个精排到 3-5 个,准确率提升非常明显。

推荐 bge-reranker-v2-m3,中英双语,推理速度可接受。这一步是整个 RAG 链路里性价比最高的优化。

生成层:Prompt 工程决定回答质量 ​

检索到了好的上下文,生成阶段如果 Prompt 写得烂,照样翻车。

核心 Prompt 结构 ​

text
你是一个企业知识库助手。请严格基于以下检索到的上下文回答问题。

规则:
1. 只使用上下文中的信息回答,不要编造
2. 如果上下文没有相关信息,明确回答"根据现有知识库无法回答"
3. 回答时标注引用来源,格式:[来源:文档名]
4. 保持专业、简洁

上下文:
---
{retrieved_context}
---

用户问题:{user_question}

几个关键经验:

  • 明确告诉模型「不知道就说不知道」:这一句话能干掉 60% 的幻觉
  • 要求标注引用来源:既提升可信度,也方便用户验证
  • 上下文按相关度排序后拼接:最相关的放最前面,LLM 对开头内容更敏感
  • 控制上下文长度:不要一股脑塞 10 万 token,3-5 个 chunk 够了,太多反而干扰

六个真实踩坑记录 ​

坑 1:Embedding 模型选错,召回率惨不忍睹 ​

最初用了 text-embedding-ada-002(OpenAI),中文文档召回率只有 40% 左右。换成 bge-large-zh-v1.5 后直接飙到 78%。

教训:中文场景必须用中文优化的 Embedding 模型。推荐 BGE 系列或 m3e-base。

坑 2:Chunk 切分没考虑表格,表格数据全废 ​

一份产品参数表被切成了 6 个 chunk,检索时只召回了其中 2 个,答案张冠李戴。

教训:表格作为独立 chunk,不参与长度切分。在 chunk 元数据里标记 type: table。

坑 3:只做向量检索,精确术语全漏 ​

用户搜「RFC 7231」,向量检索召回一堆 HTTP 协议介绍,但没有一篇提到 RFC 7231 本身。

教训:混合召回(向量 + BM25)是刚需,不是可选项。

坑 4:上下文太长,LLM 开始「断章取义」 ​

为了追求全面,一次塞了 15 个 chunk(约 8000 token)给 LLM。结果模型只关注了前 3 个和最后 2 个,中间的全被忽略。

教训:上下文不是越多越好。3-5 个高质量 chunk 远胜 15 个平庸 chunk。这就是 Rerank 存在的意义。

坑 5:没有做 Query 改写,多轮对话全靠蒙 ​

用户第一问「公司的报销流程是什么」,第二问「出差标准多少」。系统拿「出差标准多少」去检索,完全找不到上下文。

教训:多轮对话必须做 Query 改写,把指代消解后再检索。

python
def rewrite_query(chat_history, current_query, llm):
    """用 LLM 做多轮对话的 Query 改写"""
    prompt = f"""根据对话历史,将用户最新问题改写为可独立检索的完整问题。
    对话历史:{chat_history}
    最新问题:{current_query}
    改写后的问题:"""
    return llm(prompt)

坑 6:增量更新没做好,知识库越用越旧 ​

最初全量重建索引,每次更新要跑 4 小时。文档改了但知识库没更新,用户问到的都是旧信息。

教训:必须做增量索引。给每个文档算 hash,变更的才重新切分和 Embedding。

效果评估体系 ​

上线前必须有量化评估,不能靠「感觉还行」就发版。

我们用了两个指标:

  1. 召回准确率:人工标注 200 个问题的标准答案段落,看检索结果是否命中
  2. 回答满意度:上线后让用户打分(1-5 星),统计平均分

最终线上数据:

指标上线初期优化三个月后
召回准确率62%85%
回答满意度3.2 / 54.3 / 5
首 token 延迟4.2s2.1s
幻觉率(抽检)12%3%

复盘总结 ​

RAG 不是「调个 API + 接个向量库」就完事的项目。它的难点在于:工程化链路长,每一环都有坑,任何一环掉链子整体效果就崩。

如果重新做一遍,我会把精力分配调整为:

  • 文档解析与 Chunk 策略:40%(最被低估的环节)
  • 检索与 Rerank:25%
  • Prompt 工程与生成调优:20%
  • 架构与工程化:10%
  • 评估体系:5%

最后一句:RAG 项目没有银弹,所有选型都要基于你自己的数据特征来定。先跑通最小闭环,再逐环优化。

📖本文阅读--次|📊全站访问--次|👥访客--人