如果你已经发布过简单的RAG系统,就会熟悉这种模式:将文档分块、生成嵌入、存入向量数据库,根据查询检索top-k,再把结果塞进提示词。演示时效果不错,到了生产环境却往往令人失望。
演示级RAG与生产级RAG之间差距很大。现实文档杂乱无章,现实查询含义模糊,不同查询类型的质量差异巨大,成本和延迟是真实约束,更新和版本管理也很重要。
本文介绍生产级RAG流水线是什么样的——六个阶段、每个阶段的模式,以及让有效系统与令人失望的系统拉开差距的评估规范。
流水线
生产级RAG系统包含六个逻辑阶段:
[文档]
↓
1. 摄取(解析、清理、提取元数据)
↓
2. 分块(拆分为检索单元)
↓
3. 嵌入(向量 + 存储)
↓
[查询]
↓
4. 检索(语义 + 词法 + 筛选)
↓
5. 重排序(top-k → top-N)
↓
6. 生成(LLM + 上下文)
↓
[响应]
每个阶段都有自己的质量关注点。改进任何一个阶段都会改善整体效果。
下面逐一介绍。
阶段1:摄取
输入的是垃圾,输出的也是垃圾。摄取质量决定了所有下游环节的质量上限。
你会遇到的来源类型:
- PDF(大多是最难处理的)。
- Word文档。
- HTML页面。
- Markdown。
- 电子表格。
- 幻灯片(PowerPoint、Google Slides)。
- 电子邮件。
- 代码。
- 结构化数据(CSV、JSON)。
每种类型都有自己的解析难题。
PDF解析。 PDF是演示格式,而不是数据格式,解析起来极其困难。可采用以下策略:
- 文本型PDF:
pdfplumber、pymupdf、unstructured。适用于整洁的文本。 - 扫描型PDF: 使用Tesseract、Google Cloud Vision或AWS Textract进行OCR。
- 布局感知型: 对复杂布局(表格、多栏)使用
LayoutLM、Mathpix或基于LLM的解析(GPT-4 vision)。
处理困难PDF的现代方法是:使用具备视觉能力的LLM进行OCR并构造内容结构。成本更高,但质量远胜传统OCR。
处理表格。 非结构化文本中的表格很难处理。可以:
- 转换为Markdown表格(保留结构)。
- 线性化为散文(“第1行:客户A有50个订单……”)。
- 作为具有结构化Schema的独立可检索单元。
正确选择取决于预期查询。
提取元数据。 每份文档都有重要的元数据:
- 标题、作者、日期、版本。
- 主题、类别、标签。
- 来源URL或位置。
- 权限/可见性。
在摄取时采集这些信息,之后可以在检索时用作筛选参数。
清理。 去除样板内容:
- 每页重复的页眉/页脚。
- 导航、广告、Cookie横幅。
- “目录”页。
- 空白或重复段落。
干净的语料库能产生更好的检索结果。样板内容会造成错误匹配。
质量检查。
- 解析器是否确实提取了文本?(有些PDF会返回空字符串。)
- 是否存在编码问题?
- 表格是否得到保留?
- 图像是添加了说明,还是被跳过?
在摄取流水线中内置合理性检查。在损坏的解析结果污染索引之前将其拦截。
阶段2:分块
现在你有了干净的文档,接下来要将其拆分为检索单元(文本块)。
基本权衡:
- 小文本块:检索精确(聚焦于问题),但缺少上下文。
- 大文本块:上下文更多,但精度较低(相关内容埋在其中)。
两个极端都会损害质量。最佳区间因内容类型而异。
常见策略:
固定大小并重叠。 拆分为N个token的文本块(300–800个token),相邻块重叠50–100个token。简单,适合作为基线。
句子感知分块。 在句子边界处拆分(使用nltk、spaCy或类似工具),避免从句子中间切断。
按段落分块。 每段作为一个文本块。适用于段落结构清晰的文档。
分层(小块 + 大块)。 使用两个索引:
- 小文本块(300个token)用于精确检索。
- 大文本块(1,500个token)或完整章节用于提供上下文。
检索时查找小块,向LLM提供对应的大型父块。
感知文档结构。 使用文档结构(标题、章节)指导分块。每个章节作为一个文本块,并保留层级关系。
语义分块。 使用嵌入寻找自然分界点(主题变化的位置)。成本更高,但对某些内容可以产生更好的文本块。
基于LLM摘要的分块。 将长文档摘要为多个层级的文本块(段落摘要、章节摘要、文档摘要)。LLM在摄取时一次性生成这些内容。
正确策略取决于内容。文章和Wiki:按段落或分层。代码:按函数。对话:按轮次。技术文档:感知结构。
文本块元数据。 每个文本块都应携带:
- 来源文档ID和URL。
- 章节路径(章 > 节 > 小节)。
- 页码(用于引用)。
- 此文本块上方的标题(用于提供上下文)。
- 文档级元数据(日期、作者、类型)。
这些元数据可以在最终响应中实现筛选和引用。
阶段3:嵌入
将文本块转换为向量。
嵌入模型选择。
在2026年:
- OpenAI text-embedding-3-large: 综合能力强,价格昂贵。
- Voyage Voyage-3: 竞争力强,处理技术内容时通常更好。
- Cohere embed-v4: 多语言能力强。
- BAAI bge-large: 强大的开源模型。
- Nomic embed-text: 优秀的开源模型,可免费自行托管。
- 领域专用模型: 面向代码(Voyage Code,OpenAI text-embedding-3-large对代码也有效)、法律、医疗等领域。
选择模型时,应针对你的领域进行测试。不要假设排行榜第一名就最适合你的数据。
嵌入维度。 模型提供256–3,072个维度。维度越高,质量越好,成本和存储需求也越高。对大多数用例而言,768–1,536是最佳区间。
版本管理。 嵌入模型会更新,你也可能希望切换模型。应提前规划:
- 跟踪每个向量由哪个嵌入模型版本生成。
- 迁移时重新嵌入所有内容(或采用双索引过渡)。
- 不要在同一次搜索中混用不同模型生成的向量。
成本。 嵌入数百万个文本块需要付费。每百万token的成本为€0.10–€0.50(因提供商而异)。对于大型语料库,应提前计算。
存储。 向量体积很大。1M个文本块 × 1,536个维度 × 4字节 = 6GB。应据此规划存储。
阶段4:检索
查询到达后,你需要找到相关文本块。
向量搜索(密集检索)。 对查询生成嵌入,然后寻找最近邻。它可以捕捉语义相似性,是标准做法。
关键词搜索(BM25/词法检索)。 传统文本搜索。可以捕捉精确匹配、罕见词和特定名称,通常能补充向量搜索。
混合搜索。 同时运行两者,然后融合结果。倒数排名融合(Reciprocal Rank Fusion,RRF)是标准融合方法。
def reciprocal_rank_fusion(rankings, k=60):
scores = {}
for ranking in rankings:
for rank, doc_id in enumerate(ranking):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
return sorted(scores.items(), key=lambda x: -x[1])
实践中,在大多数基准上,混合搜索都优于只使用向量或只使用关键词的搜索。默认使用混合搜索。
元数据筛选。 在评分之前按元数据筛选检索结果。“只搜索2025年及之后的文档。”“只搜索用户有权访问的文档集合。”“只搜索policy类型的文档。”
这对多租户系统至关重要:每次查询都必须限定在该租户可访问的文档范围内。
查询理解。 对查询进行预处理:
- 拼写纠正。 “engineerin” → “engineering”。
- 缩写扩展。 “MFA” → “Multi-Factor Authentication MFA”。
- 同义词/查询扩展。 生成替代表述,并分别搜索。
- 分解。 将多部分问题拆分为子查询,分别检索。
这些预处理步骤能显著改善现实查询的检索效果。
HyDE(假设文档嵌入,Hypothetical Document Embeddings)。 使用LLM生成虚构的“理想答案”,为其生成嵌入,再用该结果作为检索查询。通常比直接嵌入问题效果更好,因为虚构答案与真实答案文档更加相似。
def hyde_retrieve(query, k=20):
hypothetical = llm("写一段文字回答:" + query)
embedding = embed(hypothetical)
return vector_search(embedding, k=k)
权衡是每次查询多一次LLM调用(增加延迟和成本)。对困难查询值得,对简单查询则过度。
多向量检索。 每个文本块不只生成一个向量,而是生成多个(例如文本块本身、摘要、假设问题各一个)。会增加存储和复杂度,但对某些内容可以改善检索效果。
阶段5:重排序
完成初步检索(top-k,k=20–50)后,使用重排序器更仔细地为候选项评分。
为什么要重排序?
初步检索速度快,但不够精确。向量相似度只是相关性的粗略代理。重排序器(通常是交叉编码器模型)会同时读取查询和每个候选项,从而生成更准确的分数。
重排序器选项:
- Cohere Rerank。 行业标准。质量好,托管服务。
- Voyage Rerank。 强劲的竞争者。
- BGE Rerank-large。 开源,可免费自行托管。
- MiniLM交叉编码器。 更小、更快,但质量较低。
- 基于LLM的重排序。 使用小型LLM为查询—候选项对评分。质量最高,成本也最高。
影响。
在大多数RAG系统中,加入重排序通常可以改善最终任务质量;经常被引用的10–20%区间应视为用于规划的估算值,仍需使用自己的评估加以验证。它带来的质量提升几乎总能抵消延迟和成本。
典型设置:
- 使用混合搜索检索30–50个候选项。
- 重排序后选取前5–10个。
- 将排名靠前的结果传给LLM。
自适应重排序。
如果查询的首个初步结果具有很高的向量相似度(明确匹配),可以跳过重排序。如果多个候选项分数相近,则进行更积极的重排序。
阶段6:生成
LLM使用检索到的上下文生成答案。
提示词结构:
你是一名根据所提供上下文回答问题的助手。
上下文:
[文档1 - 标题、URL]
[文本块1的内容]
[文档2 - 标题、URL]
[文本块2的内容]
...
用户问题:{query}
指令:
- 仅根据所提供的上下文回答。
- 如果上下文不包含答案,请明确说明。不要编造信息。
- 使用 [Source N] 标记引用来源。
- 简洁但完整。
以下提示词模式很重要:
- 来源标记。 为每个上下文文本块标记来源,以便引用。
- 依据指令。 明确要求模型仅使用上下文。
- 引用要求。 强制引用,有助于捕获幻觉。
- 回退指令。 “如果上下文没有答案,请明确说明。”防止编造。
引用处理。
一种常见模式是在响应中包含来源链接,由UI将其渲染为可点击链接。
"公司的远程办公政策允许每周最多4天在家办公 [1]。
[1]: https://wiki.company.com/policies/remote-work"
这样可以验证响应。相比没有依据的答案,用户更信任有依据的回答。
上下文大小管理。
过长的上下文会降低效果。大多数模型在上下文聚焦时表现最佳(5–10个高度相关的文本块),而不是一次性塞入所有内容。上下文膨胀会导致质量下降。
如果必须加入大量上下文,应对相关性较低的项目进行摘要,而不是全文加入。
在每个阶段进行评估
无法衡量就无法优化。每个阶段都需要自己的评估。
摄取评估。 文档是否正确解析?抽样检查文档,确认关键内容得到保留。
分块评估。 文本块大小是否合适?是否保留上下文?是否在合理边界处断开?
嵌入评估。 相似概念的嵌入是否相似?在包含已知相似对和已知不同对的测试集中,余弦相似度是否符合预期?
检索评估。 给定查询时,相关文本块是否出现在top-K?常见指标为recall@K(有多少百分比的查询在前K项中至少包含一个相关文本块)。
重排序评估。 给定检索候选项,重排序器是否将最相关的内容排在首位?指标包括NDCG(归一化折损累积增益)或MRR(平均倒数排名)。
生成评估。 给定上下文和查询,LLM是否生成正确答案?指标包括忠实度(答案是否使用上下文?)、正确性(答案是否正确?)、有用性(是否回应用户意图?)。
端到端评估。 给定真实查询,系统是否生成正确答案?这是最重要的评估,取决于所有阶段。
每个阶段都需要测试集。一种常见的启动方法是:
- 构建50–100条具有已知正确答案的查询。
- 对每条查询,确定哪些文档包含答案。
- 用这些数据评估检索(能否检索到正确文档?)和生成(答案是否正确?)。
工具包括Ragas、TruLens和自定义套件。具体工具不如持续运行评估重要。
运营注意事项
以下是一些生产现实:
索引流水线。 新文档会不断到达,需要重新嵌入和更新索引,也要处理删除和更新。这条流水线会持续运行,而不是只在初次设置时执行。应将其构建为系统,而不是一次性脚本。
延迟。
典型分解如下:
- 嵌入(查询):50–200ms。
- 向量搜索:50–200ms。
- 重排序:200–500ms(取决于K)。
- 生成:1–5s(取决于上下文和模型)。
- 总计:1.5–6s。
优化方法:
- 缓存频繁查询的嵌入。
- 缓存重复查询—候选项对的重排序结果。
- 流式生成。
- 尽可能并行执行。
要实现低于2s的延迟,通常需要快速嵌入模型、快速向量数据库,以及快速重排序器;对于已缓存或简单查询,也可以跳过重排序。
成本。
每次查询的成本:
- 嵌入:约€0.0001。
- 向量搜索:约€0.0001(取决于基础设施)。
- 重排序:€0.001–0.01(取决于模型)。
- 生成:€0.005–0.05(取决于模型和上下文)。
- 总计:每次查询约€0.01–0.05。
规模扩大后,成本会迅速累积。一百万次查询 = €10K–50K。
成本优化方法:
- 缓存。
- 在质量允许时使用更便宜的模型。
- 压缩上下文(摘要,而非完整文本块)。
更新。 文档会发生变化。可采用以下模式:
- 版本管理: 每份文档都有版本;根据策略保留或删除旧版本。
- 增量更新: 对新版本重新分块和嵌入;删除旧文本块。
- 感知差异: 只重新处理发生变化的章节。
在更新频率高的场景中,这一点非常重要。
权限。 对私有数据进行RAG时,文档具有访问控制,检索必须遵守。
- 基于元数据筛选(每个文本块包含accessible-by元数据)。
- 使用租户隔离的索引实现硬隔离。
- 审计谁访问了什么。
不要依赖LLM强制执行权限。必须在检索层执行。
常见失败模式
以下是失败RAG系统中的一些常见模式:
失败1:分块不佳。 文本块在语义中间断开,表格被拆到多个块中,标题与内容分离。修复:采用更好的分块策略。
失败2:检索遗漏答案。 相关文本块存在,却没有被检索到。通常是查询与文档表达不匹配。修复:HyDE、查询扩展、更好的嵌入。
失败3:文本块正确,顺序错误。 相关文本块被检索到,但排名较低。LLM使用了排名更高但不相关的文本块。修复:重排序。
失败4:文本块正确,仍有幻觉。 文本块正确,但LLM编造了额外信息。修复:使用更严格的依据提示词、引用要求和更低的温度。
失败5:文本块正确,答案错误。 文本块中包含答案,但LLM提取了错误部分。修复:改进生成提示词,也可能需要更大的模型。
失败6:数据陈旧。 索引没有更新。修复:持续运行摄取流水线。
失败7:权限泄露。 用户看到了本不应查看的文本块。修复:在检索时强制筛选;执行审计。
失败8:成本失控。 随着语料库和流量增长,延迟和成本不断攀升。修复:缓存、模型路由,必要时改变架构。
实例:公司知识库RAG
以典型的公司知识库RAG系统为例。
语料库: 约50,000份文档(Wiki页面、策略、运行手册、会议记录、Slack话题串)。
流水线:
-
摄取:
- Notion:通过API导出。
- Slack:导出存档,并筛选相关频道。
- Google Drive:通过API导出已批准的文件夹。
- 清理:删除只有表情符号的消息和样板签名。
-
分块:
- 分层:段落级小文本块、章节级父文本块。
- 小文本块层级总计约250K个文本块。
- 元数据:source、section path、last_updated、accessible_to。
-
嵌入:
- Voyage-3嵌入,1,024个维度。
- 存储在Pinecone(托管)中。
- 每周对已更改文档重新嵌入。
-
检索:
- 混合:向量搜索 + BM25(通过Pinecone的混合搜索)。
- 对查询使用HyDE。
- 按可访问性进行元数据筛选。
- 检索前30项。
-
重排序:
- Cohere Rerank。
- 前30项 → 前8项。
-
生成:
- Claude Sonnet 5。
- 提供前8个父文本块(而非小文本块)作为上下文。
- 响应中必须引用来源。
评估:
- 100组手工编写的查询/答案对。
- 检索recall@30:约92%。
- 最终答案正确率:约85%。
- 忠实度(无幻觉):约95%。
运营:
- 每日增量摄取。
- 每周对已更改文档进行完整的重新嵌入。
- 按查询提供可观测性。
- 每月重新运行评估并监控趋势。
- 每季度审查查询日志,发现新的失败模式。
成本:
- 嵌入:约€500/月(稳定状态)。
- 向量数据库托管:约€800/月(Pinecone)。
- 每次查询的检索 + 生成:约€0.02。
- 总计:约€2,500/月 + €0.02 × 查询量。
对大多数企业而言,这些成本完全可以通过生产力收益得到合理回报。
90天RAG建设计划
如果从零开始:
第1–30天:MVP。
- 确定语料库和主要用例。
- 构建基础摄取(一个来源)。
- 基础分块(固定大小并重叠)。
- 使用标准嵌入模型。
- 只使用向量搜索(不重排序)。
- 使用简单的生成提示词。
第31–60天:质量。
- 手工构建评估集(50–100条查询)。
- 识别失败模式。
- 添加重排序。
- 添加混合搜索。
- 改进分块。
第61–90天:运营。
- 持续摄取流水线。
- 可观测性。
- 评估自动化。
- 权限/身份验证。
- 成本监控。
90天后,你得到的不只是可演示的系统,而是真正可用的系统。真实用户可以依赖它。
质量存在于每个阶段
生产级RAG是一条六阶段流水线,每个阶段都有质量关注点。让有效系统与令人失望的系统拉开差距的模式包括:
- 摄取: 全面解析、保留结构、采集元数据。
- 分块: 根据内容类型选择适当策略,通常采用分层方式。
- 嵌入: 使用高质量模型、管理版本、规划迁移。
- 检索: 默认采用混合检索、理解查询、筛选元数据。
- 重排序: 几乎总是值得。
- 生成: 提供依据、引用来源、在未知时回退。
- 评估: 在每个阶段持续进行。
以上各项都不是可选项。RAG答案质量从60%提升到90%的差距,就存在于这些细节中。
投入确实不小——生产级RAG建设需要数月,而不是数周。回报则是一个用户真正可以信任的系统。这才是重要的门槛。



