RAG 完整流程拆解:从文档到智能回答的每一步
一、什么是 RAG?为什么它如此重要?
RAG(Retrieval-Augmented Generation,检索增强生成) 是当前大语言模型(LLM)应用落地的核心技术之一。它的本质很简单:给大模型装上一个"外脑"。
纯 LLM 存在三大痛点:幻觉(编造虚假信息)、知识冻结(训练数据有截止日期)、以及私有数据盲区(不了解企业内部文档)。RAG 通过将外部知识库与 LLM 结合,让模型在回答问题时能够"查资料",从而生成基于事实的答案。
RAG 的工作流程分为两个主要阶段:
-
索引阶段(Indexing):离线预处理文档,构建可检索的知识库
-
检索与生成阶段(Retrieval & Generation):在线响应用户查询,检索相关知识并生成答案

二、Step 1:文档解析(Document Parsing)
2.1 为什么文档解析是 RAG 的"第一道关卡"?
文档解析是整个 RAG 流程中最容易被忽视,却最关键的环节。如果这一步出了问题,后续的所有优化都将事倍功半。
原始文档格式多种多样:PDF、Word、PPT、网页、扫描件、图片等。每种格式都有其独特的挑战:
| 文档类型 | 主要挑战 |
|---|---|
| 文本提取不准确、表格行列关系混乱、多栏排版错位 | |
| 扫描件/图片 | 需要 OCR 识别,手写体、低质量扫描导致识别错误 |
| 网页 | HTML 标签噪音、导航栏/广告等无关内容混入 |
| PPT | 图文混排复杂、幻灯片顺序与逻辑结构不一致 |
| 表格 | 跨页表格断裂、表头与数据分离 |
2.2 文档解析的核心任务
文档解析的目标是将各种格式的原始文档转换为干净、结构化、可检索的文本。主要包含以下步骤:
-
格式识别:判断文档类型(PDF/HTML/DOCX 等)
-
内容提取:提取正文、标题、表格、图片说明等
-
结构还原:保留段落层级、列表关系、表格结构
-
噪音过滤:去除页眉页脚、广告、导航栏等无关内容
最佳实践:对于 PDF 文档,最可靠的做法是先将其转换为 Markdown 等结构化格式,再进行后续处理。
2.3 常用工具推荐
-
开源方案:Unstructured、Marker、PDFPlumber、PyMuPDF
-
商业方案:Firecrawl(网页)、Azure Document Intelligence(PDF/OCR)
-
OCR 引擎:Tesseract、PaddleOCR、Azure Read API

三、Step 2:分块(Chunking)
3.1 为什么要分块?
LLM 的上下文窗口是有限的,我们无法将整本书或几百页的 PDF 一次性塞进模型。同时,Embedding 模型也有输入长度限制(通常为 512 或 1024 tokens)。因此,必须将文档切分成更小的"块"(Chunk)。
但分块不是简单的"一刀切"------分块策略直接决定了 RAG 系统的检索质量 。研究表明,不同的分块策略可能导致高达 9% 的召回率差距。

3.2 主流分块策略详解
策略一:固定大小分块(Fixed-Size Chunking)
最简单粗暴的方式:设定一个固定长度(如 1000 字符),达到上限就切分。
# 伪代码示例 chunks = [] chunk_size = 1000 for i in range(0, len(text), chunk_size): chunks.append(text[i:i+chunk_size])
优点 :实现简单、速度快、块大小可预测 缺点:完全无视语义边界,可能切断句子、段落甚至表格
适用场景:快速原型验证(MVP)、结构均匀的内容(如新闻文章)
策略二:递归字符分块(Recursive Character Splitting)⭐ 推荐默认方案
这是目前80% 的 RAG 应用的首选方案。它按照优先级尝试在"自然边界"处切分:
-
先尝试按 段落 (
\n\n)切分 -
段落太长则按 行 (
\n)切分 -
行太长则按 句子 (
.)切分 -
句子太长则按 单词(空格)切分
-
最后按 字符 切分
# LangChain 示例 from langchain_text_splitters import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=512, # 目标块大小(tokens) chunk_overlap=50, # 重叠量(约 10-20%) separators=["\n\n", "\n", ". ", " ", ""] # 优先级分隔符 ) chunks = splitter.split_documents(documents)
推荐参数:400-512 tokens 的块大小,10-20% 的重叠量(约 50-100 tokens)
优点 :保留文档结构、适应多种内容类型、性能稳定(Chroma 研究显示召回率可达 88-89%) 缺点:块大小不均匀,对批量处理略有影响
策略三:语义分块(Semantic Chunking)
语义分块不再依赖固定长度,而是根据内容主题的转换来决定切分点。具体做法是:
-
将文档按句子拆分
-
为每个句子生成 Embedding
-
计算相邻句子的相似度
-
当相似度低于某个阈值时,在此处切分
from langchain_experimental.text_splitter import SemanticChunker semantic_splitter = SemanticChunker( embeddings=embeddings, breakpoint_threshold_type="percentile", # 或 "standard_deviation" breakpoint_threshold_amount=95 ) chunks = semantic_splitter.split_text(document_text)
优点 :语义连贯性好、能检测细微的主题转换、召回率可提升 2-9% 缺点:计算成本高(每个句子都要 Embedding)、需要调参、处理速度慢
适用场景:对检索精度要求极高、预算充足、文档主题转换微妙的场景(如研究论文)
策略四:页面级分块(Page-Level Chunking)
以 PDF 页面为单位进行切分,每页作为一个 Chunk。
优点 :在 NVIDIA 2024 基准测试中取得了最高准确率(0.648)和最低方差 缺点:仅适用于有分页的文档,页面内容长度差异大
策略五:Late Chunking(延迟分块)🔥 2024-2025 新兴技术
传统分块先切分文档再 Embedding,导致每个 Chunk 失去了全局上下文。Late Chunking 颠覆了这个顺序:
-
先:将完整文档输入 Transformer,生成 token 级别的 Embedding(此时每个 token 都包含了全文上下文)
-
后:再按边界切分,对每个 Chunk 内的 token Embedding 做平均池化
这样,即使 Chunk 中出现了代词"它",其 Embedding 也能知道"它"指代的是前文提到的"柏林"。
import requests data = { "input": [ "Berlin is the capital and largest city of Germany...", "Its more than 3.85 million inhabitants make it...", ], "model": "jina-embeddings-v3", "task": "retrieval.passage", "late_chunking": True, # 关键参数 }
3.3 分块策略选择决策树

一句话总结:
-
起步:递归字符分块(512 tokens + 10-20% 重叠)
-
优化:根据文档类型和查询模式,尝试语义分块或 Late Chunking
-
高价值场景:考虑 LLM-Based 或 Agentic 分块
3.4 分块的常见陷阱
-
块太小:200 字符以下的块几乎无法承载有效语义
-
块太大:跨主题的块会稀释 Embedding 的表达能力
-
忽视文档结构:表格切断了表头、代码切断了函数体
-
丢弃元数据:文档标题、章节名、页码等元数据对检索至关重要
-
一刀切:不同类型的文档应使用不同的分块策略
四、Step 3:Embedding 向量入库
4.1 什么是 Embedding?
Embedding 是将文本转换为高维向量的过程。语义相近的文本在向量空间中的距离也更近。通过这种方式,我们可以用数学方法衡量两段文本的"相似度"。

4.2 Embedding 模型选择
| 模型 | 维度 | 特点 | 适用场景 |
|---|---|---|---|
| text-embedding-3-small (OpenAI) | 1536 | 速度快、成本低 | 通用场景、预算敏感 |
| text-embedding-3-large (OpenAI) | 3072 | 精度更高 | 对质量要求高 |
| BGE-M3 (BAAI) | 1024 | 多语言、开源 | 中文场景、私有化部署 |
| jina-embeddings-v3 (Jina AI) | 1024 | 支持 Late Chunking | 长文档、跨语言 |
| E5-mistral-7b-instruct (Microsoft) | 4096 | 指令式 Embedding | 复杂查询 |
选型建议:
-
英文内容:OpenAI
text-embedding-3-large或text-embedding-3-small -
中文内容:BGE-M3、Jina-embeddings-v3
-
私有化部署:BGE 系列、GTE 系列
4.3 向量数据库选型
向量数据库负责高效存储和检索高维向量。常见选择:
| 数据库 | 特点 | 适用场景 |
|---|---|---|
| Pinecone | 全托管、易用 | 快速上线、不想运维 |
| Milvus/Zilliz | 开源、高性能、可扩展 | 大规模数据、企业级 |
| Qdrant | 开源、Rust 编写、高性能 | 自托管、低延迟 |
| Weaviate | 开源、GraphQL 接口 | 需要复杂查询 |
| Chroma | 轻量、易集成 | 原型开发、本地测试 |
| pgvector | PostgreSQL 扩展 | 已有 PG 基础设施 |
| Redis | 内存级速度 | 需要极低延迟 |
4.4 入库流程
# 以 Pinecone + LangChain 为例 from langchain_pinecone import PineconeVectorStore from langchain_openai import OpenAIEmbeddings # 1. 初始化 Embedding 模型 embeddings = OpenAIEmbeddings(model="text-embedding-3-small") # 2. 初始化向量数据库 vector_store = PineconeVectorStore( index=pc.Index("my-index"), embedding=embeddings ) # 3. 将分块后的文档入库 vector_store.add_documents(chunks)
关键注意事项:
-
为每个 Chunk 保留元数据(文档标题、章节、页码、URL 等),便于后续过滤和溯源
-
考虑批量入库以提升效率
-
对于大规模数据,选择支持近似最近邻(ANN)索引的数据库(如 HNSW、IVF-PQ)

五、Step 4:检索(Retrieval)
5.1 检索阶段的工作流程
当用户提出问题时,检索阶段的工作如下:
-
查询向量化:将用户问题通过同样的 Embedding 模型转换为向量
-
相似度搜索:在向量数据库中查找与查询向量最相似的 Top-K 个 Chunk
-
结果返回:将检索到的 Chunk 作为"上下文"传递给 LLM

5.2 检索策略进阶
基础检索:向量相似度搜索
最基础的检索方式,通过余弦相似度或欧氏距离找到最近邻向量。
results = vector_store.similarity_search(query, k=5)
进阶检索一:混合检索(Hybrid Search)
纯向量检索有时会遗漏精确匹配的关键词(如产品型号、专有名词)。混合检索结合了两者的优势:
-
稠密检索(Dense):向量相似度,捕捉语义相关性
-
稀疏检索(Sparse):BM25/TF-IDF,捕捉关键词精确匹配
通过 RRF(Reciprocal Rank Fusion) 融合两种检索的结果:
RRF_score = Σ(1 / (k + rank_i))
进阶检索二:重排序(Reranking)
先通过向量检索召回大量候选(如 Top-100),再用更精确的重排序模型(Cross-Encoder)对候选进行精排,最终选出 Top-K。
# 伪代码 candidates = vector_store.similarity_search(query, k=100) # 粗排 reranked = cross_encoder.rerank(query, candidates, k=5) # 精排
常用重排序模型:Cohere Rerank、BGE-Reranker、Jina Reranker
进阶检索三:上下文检索(Contextual Retrieval)
由 Anthropic 提出,在 Embedding 之前为每个 Chunk 添加一个由 LLM 生成的上下文摘要,让 Chunk 知道自己"在整篇文档中的位置"。
例如,一个 Chunk 的内容是:
"它的价格在过去一年上涨了 20%。"
添加上下文后变为:
"上下文:本文讨论的是苹果公司 2025 年财报。Chunk:它的价格在过去一年上涨了 20%。"
这样可以显著减少检索失败率,但需要在索引阶段为每个 Chunk 调用一次 LLM,增加了索引成本。
进阶检索四:查询扩展与改写
-
HyDE(Hypothetical Document Embeddings):让 LLM 先根据问题生成一个"假设的理想答案",再用这个假设答案去检索
-
多查询扩展:让 LLM 将用户问题改写为多个不同表述的查询,分别检索后合并结果
-
子查询分解:将复杂问题分解为多个子问题,分别检索后综合回答
六、Step 5:LLM 生成(Generation)
6.1 生成阶段的工作流程
检索到的 Chunk 需要与原始问题一起组装成 Prompt,送入 LLM 生成最终答案。

6.2 Prompt 工程
一个典型的 RAG Prompt 模板:
你是一个专业的问答助手。请根据以下提供的参考资料回答问题。 如果参考资料中没有相关信息,请明确说明"根据提供的资料,我无法找到答案"。 不要编造任何信息。 ## 参考资料: {retrieved_chunks} ## 用户问题: {user_query} ## 回答:
关键技巧:
-
明确角色设定:让 LLM 知道它应该扮演什么角色
-
引用溯源:要求 LLM 在回答中标注信息来源(如"根据文档1...")
-
拒绝幻觉:明确指示 LLM 在信息不足时拒绝回答
-
格式要求:指定输出格式(如 Markdown、JSON、表格)
6.3 上下文窗口管理
LLM 的上下文窗口是有限的,需要合理分配:
| 内容 | 建议占比 |
|---|---|
| 系统提示(System Prompt) | 5-10% |
| 检索到的 Chunk | 60-70% |
| 对话历史 | 10-20% |
| 用户问题 | 5-10% |
注意:研究表明,当上下文长度超过约 2500 tokens 时,模型性能会出现"上下文悬崖"(Context Cliff),响应质量显著下降。
6.4 生成优化策略
-
父文档检索(Parent Document Retrieval):检索时用小的 Chunk(精度高),但将 Chunk 所属的完整段落/页面传给 LLM(上下文完整)
-
摘要 + 详细内容:先让 LLM 看摘要,再看详细 Chunk
-
多轮生成:先生成草稿,再让 LLM 自我修正
-
流式输出:提升用户体验,让用户无需等待完整响应
七、完整流程图与架构概览


八、2026 年 RAG 的前沿趋势
8.1 Agentic RAG
不再使用固定的检索流程,而是让 AI Agent 动态决定:
-
是否需要检索?
-
检索几次?
-
从哪个数据源检索?
-
如何组合多个检索结果?
8.2 Graph RAG
将文档转换为知识图谱(实体-关系-实体),结合图遍历和向量检索,解决跨文档、跨段落的复杂推理问题。
8.3 多模态 RAG
不仅处理文本,还支持图片、音频、视频的理解和检索。使用 CLIP 等多模态 Embedding 模型统一表示。
8.4 自适应 RAG
根据问题的复杂度动态选择策略:
-
简单问题 → 直接回答(无需检索)
-
中等问题 → 单次检索
-
复杂问题 → 多轮检索 + 推理链
九、总结与最佳实践 checklist
完整流程回顾
原始文档 → 文档解析 → 分块(Chunk) → Embedding → 向量入库 → 用户查询 → 检索 → 重排序 → Prompt 组装 → LLM 生成 → 回答
生产环境 Checklist
| 阶段 | 检查项 |
|---|---|
| 文档解析 | ✅ 是否处理了 PDF/表格/扫描件?<br>✅ 是否去除了页眉页脚等噪音?<br>✅ 是否保留了文档结构(标题层级)? |
| 分块 | ✅ 块大小是否在 400-512 tokens?<br>✅ 是否有 10-20% 的重叠?<br>✅ 是否根据文档类型选择了合适的策略?<br>✅ 是否保留了元数据? |
| Embedding | ✅ 模型是否支持目标语言?<br>✅ 向量维度是否与数据库匹配? |
| 检索 | ✅ 是否使用了混合检索(Dense + Sparse)?<br>✅ 是否添加了重排序(Rerank)?<br>✅ Top-K 值是否经过调优? |
| 生成 | ✅ Prompt 是否包含明确的角色和约束?<br>✅ 是否要求引用来源?<br>✅ 上下文长度是否控制在合理范围? |