RAG 系统架构详解
flowchart LR
subgraph Offline[离线索引]
D1[原始文档] --> D2[解析 / 清洗]
D2 --> D3[Chunking]
D3 --> D4[Embedding]
D4 --> D5[向量库 / 索引]
end
subgraph Online[在线查询]
Q1[用户问题] --> Q2[Query Processing]
Q2 --> Q3[Retrieval]
Q3 --> Q4[Rerank 可选]
Q4 --> Q5[Context Assembly]
Q5 --> Q6[LLM Generation]
Q6 --> Q7[Response]
end
D5 --> Q3
理解了 RAG 是什么,下一步就不是再背定义,而是搞清楚:一个完整的 RAG 系统到底怎么运转。在工程里,RAG 从来不是“调一次向量检索 + 调一次 LLM”这么简单,而是一条包含离线索引和在线查询的完整流水线。LlamaIndex 的文档长期把 RAG 拆成 indexing、retrieval、query engine 等模块;LangChain 的 RAG 教程也明确展示了“索引一条线、查询一条线”的结构。(docs.llamaindex.ai) (docs.langchain.com)
这篇文章会讲什么
上一篇讲的是“为什么需要 RAG”,这一篇讲的是“RAG 系统是怎样跑起来的”。这两者差别很大。知道 RAG 的定义,并不等于知道:
- 文档什么时候被切分、向量化、入库
- 用户提问时,哪些步骤在线执行,哪些步骤离线完成
- 为什么有的系统只做一次检索,有的还要做 rerank
- 为什么 query 处理和文档索引要分成两条主线
- 为什么很多 RAG 项目失败,不是因为模型不够强,而是流水线里某一段设计错了
所以这篇文章的目标,是把 RAG 拆成一张“系统总图”,帮你建立两个视角:
- 离线视角:文档是如何被整理成可检索知识库的
- 在线视角:用户问题是如何一步步变成最终回答的
理解这张图以后,你后面再看 chunking、hybrid search、rerank、agentic RAG,就不会觉得它们是零散技巧,而会知道它们在整条流水线里各自解决什么问题。
延伸阅读
- Lewis et al. 2020 的原始 RAG 论文,为“检索 + 生成”这条路线提供了研究起点。(arxiv.org)
- LangChain 官方 RAG 教程展示了一个非常典型的模块化 RAG pipeline。(docs.langchain.com)
- LlamaIndex 文档长期围绕 indexing、retriever、query engine 来组织 RAG 架构。(docs.llamaindex.ai)
- 2023 年的 RAG Survey 把现代 RAG 范式总结为 Naive RAG、Advanced RAG、Modular RAG 三类,这个分类在今天仍然很有用。(arxiv.org)
先说结论:RAG 其实是两条流水线
很多人第一次做 RAG,会把系统想成先看定义:
用户问题 → 向量检索 → LLM 回答
这当然没有错,但太粗了。真正的 RAG 系统至少有两条主线:
1. 索引流水线(离线)
负责把原始文档变成“可检索知识”。
2. 查询流水线(在线)
负责把用户问题变成“带参考资料的回答”。
这两条线看起来独立,实际上强耦合。 因为在线查询质量,很大程度上取决于离线索引质量。你在线调 top-k、换 rerank、改 prompt,很多时候是在“救”一个本来就没建好的索引。LlamaIndex 文档里把 indexing 和 query engine 明确拆开,就是这个原因。(docs.llamaindex.ai)
所以理解 RAG 架构时,最重要的一句话是:
RAG 不是一个请求时临时发生的动作,而是离线准备 + 在线检索 + 在线生成的组合系统。
一、完整 RAG 流水线
先给出一条足够完整、又适合工程理解的主链路:
Query
→ Query Processing
→ Retrieval
→ Reranking(可选)
→ Context Assembly
→ LLM Generation
→ Response
如果把索引链路也加进去,可以画成两条并行但耦合的流程:
离线索引:
Documents
→ Parsing / Cleaning
→ Chunking
→ Embedding
→ Vector Store / Index
在线查询:
User Query
→ Query Processing
→ Query Embedding / Search
→ Retrieval
→ Reranking
→ Prompt Assembly
→ LLM
→ Response
LangChain 的官方教程本身就是这个思路:一边是 indexing chain,一边是 retrieval-and-generation chain。(docs.langchain.com)
二、索引流水线(离线):先把文档变成“可被查到的知识”
先看定义:索引流水线的任务不是“保存文档”,而是把原始资料转成适合检索的知识单元。离线建得好,在线回答才有上限。
1. Document Ingestion:文档接入
RAG 的起点通常不是 query,而是文档。原始资料可能来自:
- Word / PPT / Excel
- Markdown / HTML
- Wiki / Notion / Confluence
- 数据库记录
- 内部 API 返回内容
这一阶段最重要的工作,是把原始格式解析成尽量干净、结构尽量保留的文本。LlamaIndex 也把 document parsing 放在 indexing 模块的前面,因为这是所有后续步骤的前提。(docs.llamaindex.ai)
这里容易被低估的一点是: 文档解析质量会直接决定后续 RAG 效果。
例如:
- 页眉页脚没清掉
- 表格结构丢失
- 标题层级被打乱
- PDF 解析后句子顺序错乱
这些问题一旦进入索引,后面再强的 embedding 和 rerank 都只能尽量补救。
2. Cleaning / Normalization:清洗与规范化
文档接入后,通常还要做一定清洗:
- 去掉噪声字符
- 去掉重复段落
- 标准化空格和换行
- 统一标题和段落边界
- 识别文档来源、章节、时间等 metadata
这一步不是为了“文本更好看”,而是为了后续两件事:
- 切分更稳定
- 检索结果更可解释
很多失败的 RAG 系统,问题不在检索算法,而在于输入给索引的原文已经脏了。
3. Chunking:把长文切成可检索单元
这是索引流水线里最关键的一步之一。
RAG 很少直接对整篇文档建索引,而是要切成 chunk。原因很简单:
- 整篇太长,粒度太粗
- 相关信息通常只在某个局部段落
- 模型最终也只适合消费小块上下文,而不是整本手册
所以 chunking 的目标不是“机械切段”,而是:
把文档切成既能被检索到、又保留足够语义完整性的知识单元。
这一点非常重要,后面专门一章会展开讲。
4. Embedding:把 chunk 变成向量
chunk 切完之后,通常要送入 embedding 模型,得到向量表示。 这个向量的作用是:让系统后续能根据 query 的语义相似性去找这些 chunk。
这里最关键的工程原则是:
索引时用什么 embedding 模型,查询时通常也必须用同一套 embedding 空间。
否则 query 和 document 的向量空间不一致,相似度就没有意义。这个原则在现代向量检索和 RAG 系统里几乎是基本前提。(docs.langchain.com)
5. Storage / Index:把向量和 metadata 存进可检索系统
最后,chunk 向量和 metadata 会进入:
- 向量数据库
- 稀疏索引系统
- 混合检索系统
- 或者更广义的检索后端
这里存的不只是 vector,通常还包括:
- chunk 原文
- 文档标题
- 来源 URL / 文件名
- 章节位置
- 创建时间 / 更新时间
- 租户 ID / 权限范围
- 文档类型
这些 metadata 对后续 filtering、引用展示和多租户隔离都非常重要。
索引流水线的本质
你可以把离线索引理解成:
先把“人能读的资料”,转成“机器能找的知识”。
这一层不直接回答问题,但它决定了:
- 你能不能找到相关资料
- 你找到的是不是足够细的片段
- 结果能不能带来源
- 后续检索能不能做过滤和扩展
所以索引流水线看似不“智能”,其实它决定了整个 RAG 系统的上限。
三、查询流水线(在线):用户问题是怎样变成最终回答的
先看定义:在线查询的核心,不是“拿问题去搜一下”,而是把原始问题逐步变成模型真正能用的上下文,再让模型输出可读答案。
1. Query Processing:先处理用户问题,不一定直接搜
用户输入的 query 往往不是最适合检索的形式。
例如:
- 口语化:
咋装 Python - 含糊:
这个接口限流多少 - 错别字:
苹国 - 省略上下文:
那这个能退吗
所以很多 RAG 系统在真正检索前,会先做一层 query processing。常见做法包括:
- 拼写纠正
- query rewrite
- query expansion
- 意图识别
- 上下文补全
这一步不是所有系统都必须做,但在复杂场景里很常见。LangChain 和 LlamaIndex 都支持在 retrieval 前插入更多 query handling 逻辑。(docs.langchain.com) (docs.llamaindex.ai)
为什么 query processing 很重要
因为用户提问是“为人类而说”的,不一定是“为检索系统而写”的。 query processing 的本质,是在做一次语义重写:
把用户的自然提问,转成更适合召回知识的检索表达。
2. Retrieval:先广泛找回候选
检索阶段的目标,不是马上找出“最终最优答案片段”,而是先尽量把可能相关的候选找回来。 这一点特别重要,因为很多系统在这里的优化目标是 召回率,不是精度。
常见做法包括:
- Dense retrieval(向量检索)
- Sparse retrieval(BM25 等关键词检索)
- Hybrid retrieval(两者结合)
- metadata filtering
- 多路召回后合并
Pinecone 的 RAG 资料也一直强调,retrieval 的任务首先是把可能相关的上下文快速召回,而不是一步完成所有判断。(pinecone.io)
一个非常重要的工程直觉
检索阶段更像“粗筛”,不是“终审”。
它回答的问题是:
哪些片段有可能相关?
而不是:
哪几个片段一定最值得给模型看?
后者通常要交给 rerank。
3. Reranking:把“召回”变成“更准的前几条”
初检常常会把相关内容找回来,但排序未必最好。 这时很多生产级 RAG 系统会加一层 rerank,也就是对 query 和候选文档做更精细的相关性判断,再从候选里挑出真正最值得给模型看的几条。
这一步常见的实现是 cross-encoder 或专门 rerank 模型。 它的典型作用是:
- 去掉噪声 chunk
- 提升前几条结果质量
- 缩小最终进入 prompt 的上下文范围
所以可以把它理解成:
检索负责“找回来”,Rerank 负责“排前面”。
这也是现代 RAG 系统从 naive 走向 advanced 的一个典型标志。RAG Survey 把包含 rerank、query reformulation 等增强机制的系统归入更高级的 RAG 范式。(arxiv.org)
4. Prompt Assembly:把资料“喂给模型”的方式同样重要
很多人做 RAG 时,只关注“检索到了什么”,却低估了另一个关键问题:
怎么把这些资料组织给模型看?
这就是 augmentation,也可以理解为 prompt assembly。
一个最基本的模板通常长这样:
你是一个助手。请基于以下参考资料回答用户问题。
如果资料中没有答案,请明确说明。
参考资料:
[文档1]
[文档2]
...
用户问题:
{query}
但生产里,Prompt Assembly 往往还会处理更多细节:
- 是否附带文档来源
- 是否要求引用
- 是否要求模型只基于资料回答
- 多个片段之间如何排序
- 是否按来源分组
- 是否截断或摘要过长片段
Prompt Assembly 的质量,会显著影响模型是否真正利用检索结果,而不是“看了一眼又开始自由发挥”。
5. Generation:模型基于检索内容生成答案
最后一步才是 LLM 真正生成回答。
这一步看起来最“显眼”,但它其实是建立在前面所有环节之上的:
- query 没处理好,检索就可能偏
- 检索没召回来,模型就无料可用
- rerank 不准,真正关键片段可能没进 prompt
- prompt assembly 混乱,模型可能抓不住重点
所以最终回答质量,虽然由模型来呈现,但并不只是模型层的问题。
四、把两条主线放在一起看
如果把索引和查询两条线合在一起,你会更清楚一个 RAG 系统的全貌:
┌────────────────────────────────────┐
│ 索引流水线(离线) │
│ 文档 → 清洗 → 切分 → 向量化 → 入库 │
└────────────────────────────────────┘
│
▼
┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ User │──▶│ Query │──▶│ Retrieval │──▶│ Reranking │
│ Query │ │ Processing │ │ │ │(可选) │
└──────────┘ └──────────────┘ └─────────────┘ └──────┬───────┘
│
▼
┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ Response │◀──│ LLM │◀──│ Prompt │◀──│ Selected │
│ │ │ Generation │ │ Assembly │ │ Context │
└──────────┘ └──────────────┘ └─────────────┘ └──────────────┘
这个图里有两个特别容易被忽略的事实:
1. 索引流水线决定上限
在线链路再花哨,也救不了一个糟糕的索引。
2. 在线流水线决定体验
索引再好,如果 query 处理差、rerank 差、prompt assembly 差,最终结果仍然会不稳定。
所以真正成熟的 RAG 系统,通常不是某一个环节“神级优化”,而是每个环节都足够靠谱。
五、三种常见 RAG 架构:Naive、Advanced、Modular
先看定义:从 Naive RAG 到 Modular RAG,本质上是从“检索一下就回答”逐步演化到“检索、重排、路由、工具调用都可插拔”。这种分法在近年的 RAG Survey 中已经很常见。(arxiv.org)
1. Naive RAG
最简单的形态:
Query → Retrieval → Prompt → LLM
特点是:
- 直接检索
- 不做 query rewrite
- 不做 rerank
- 不做复杂路由
- 结构简单、延迟低、适合原型
它的价值不是“效果最好”,而是让你先把闭环跑通。
2. Advanced RAG
在 naive 的基础上加入:
- query rewrite
- hybrid search
- rerank
- metadata filtering
- 更好的 prompt assembly
- 引用展示或答案约束
这类系统通常更接近生产环境,因为它已经开始认真处理召回率、精排和回答可控性问题。RAG Survey 把这一类增强检索与重排序的系统视为现代 RAG 的核心演进方向。(arxiv.org)
3. Modular RAG
再往上走,会变成“模块可插拔”的系统:
- 某些 query 走不同检索器
- 某些场景接工具调用
- 某些任务需要多跳检索
- 某些问题先分类再进入特定 query engine
- 某些回答需要结构化输出或验证
这时 RAG 已经不只是“一个 pipeline”,而更像一套可编排的知识访问架构。LangGraph、LlamaIndex query engine/router 之类能力,通常都在往这个方向走。(docs.langchain.com) (docs.llamaindex.ai)
六、关键设计决策:哪些参数真的会影响系统质量
1. Chunk 大小
| 选项 | 影响 |
|---|---|
| 太小 | 容易丢上下文,检索碎片化 |
| 太大 | 容易引入噪声,相关性变模糊 |
| 合理大小 | 兼顾语义完整性和检索粒度 |
chunk 大小没有一刀切答案,它取决于文档类型、领域语言密度、后续模型上下文预算。后面专门一章会详细讲。
2. Top-k
| 选项 | 影响 |
|---|---|
| 太小 | 可能召回不足,漏掉关键证据 |
| 太大 | 噪声增加,prompt 变长,rerank 成本更高 |
| 常见起点 | 先取一个中等 k,再由 rerank 压缩到更小 n |
一个常见工程模式是:
- retrieval 阶段取较大的 top-k
- rerank 后再选更小的 top-n 送给模型
3. 是否做 Rerank
这是延迟和准确率最典型的权衡点之一。 如果你只做原型,可能先不加; 如果你要生产质量,很多场景里 rerank 都会显著提升前几条结果质量。RAG Survey 也把 rerank 视为 advanced RAG 的关键组件。(arxiv.org)
4. 检索策略:Dense / Sparse / Hybrid
不同数据集对检索方式的敏感性很高:
- 关键词非常重要的场景,sparse 往往有优势
- 语义改写很多的场景,dense 更有价值
- 企业文档里两者经常互补,hybrid 很常见
所以“默认用向量检索就行”通常只是起点,不是终点。
5. Embedding 模型
embedding 模型不只是影响“语义理解”,还会影响:
- 多语言效果
- 领域术语匹配
- chunk 间距离结构
- 向量库成本和维度
它往往是整个 RAG 系统最被低估的基础组件之一。
七、延迟与成本:RAG 为什么天然比纯聊天复杂
RAG 多出来的,不只是一个检索步骤,而是一整条前置链路。 因此一次请求的典型耗时通常由这些部分组成:
| 阶段 | 典型成本来源 | 优化方向 |
|---|---|---|
| Query Processing | 额外 LLM 调用或规则处理 | 能省则省,能缓存则缓存 |
| Embedding / Search | 检索请求和向量检索 | 优化索引、缓存热门 query |
| Rerank | Cross-encoder 或 rerank API | 减少候选量、只在必要时用 |
| Prompt Assembly | token 增长 | 精简上下文、限制 top-n |
| LLM Generation | token 输出和模型大小 | 控制回答长度、模型路由 |
所以从系统视角看,RAG 天然比“直接调模型”更贵、更慢一些。 但换来的,是知识接入、来源可追溯和回答可靠性的提升。
八、错误处理与降级:RAG 不是永远成功的
一个成熟的 RAG 系统,不能假设每次都能顺利命中相关资料。至少要考虑这些失败方式:
1. 检索无结果
可能是知识库里真没有,也可能是 query 表达方式和文档表述差太多。
可选策略:
- 明确告诉用户“未找到相关资料”
- 降级到纯 LLM 回答,但要强调不确定性
- 触发 query rewrite 后重试一次
2. 检索结果相关性差
这时模型很可能会被误导,回答看起来有依据但其实答非所问。
可选策略:
- 启用 rerank
- 加 metadata filtering
- 降低 top-k 噪声
- 对答案增加“仅基于资料回答”的约束
3. 检索服务或 embedding 服务异常
可选策略:
- 降级为 BM25 等稀疏检索
- 走纯 LLM 模式
- 返回友好错误并记录日志
一个重要的工程原则是:
RAG 不能假设任何单点永远成功,必须设计“失败时怎么活下去”。
九、一个最好记住的原则:RAG 不是“加了检索”,而是“重写了回答路径”
如果你只想记住一句话,那就是:
RAG 的本质,不是在原有 LLM 前面“多加一个搜索框”,而是把“用户问题如何变成答案”这条路径彻底改写了。
在纯 LLM 系统里,路径通常是:
问题 → 模型 → 回答
在 RAG 系统里,路径变成了:
问题 → 检索 → 选择证据 → 组织上下文 → 模型 → 回答
这意味着,RAG 的质量不再只由模型决定,而是由整条流水线共同决定。 这也是为什么 RAG 一定要用“系统架构”的视角来看,而不能只把它当成一个 prompt 技巧。
小结
一个完整的 RAG 系统包含两条主线:
- 索引流水线(离线):文档解析、清洗、切分、向量化、入库
- 查询流水线(在线):query 处理、检索、重排、上下文组装、生成回答
从 Naive RAG 到 Advanced RAG 再到 Modular RAG,本质上是让检索更智能、让上下文更可控、让整条链路更模块化。RAG Survey 对这三类范式的总结,也印证了今天工程实践中的典型分层。(arxiv.org)
如果说上一篇解决的是“为什么需要 RAG”,那么这一篇解决的就是:
一个 RAG 系统到底由哪些环节组成,以及这些环节是如何彼此依赖的。
核心概念速查
| 概念 | 一句话 |
|---|---|
| Indexing Pipeline | 离线将文档清洗、切分、向量化并存入索引的流程 |
| Query Pipeline | 在线从 Query 到 Response 的实时链路 |
| Query Processing | 在检索前对用户问题做纠错、改写、扩展或意图识别 |
| Retrieval | 从知识库中召回可能相关的候选片段 |
| Reranking | 对候选结果重新打分,选出更相关的前几条 |
| Prompt Assembly | 把检索结果组织成模型当前可见的参考上下文 |
| Naive RAG | 单次检索 + 直接生成的最基础形态 |
| Advanced RAG | 加入 rerank、hybrid、query rewrite 等增强机制 |
| Modular RAG | 模块可插拔、可路由、可多跳扩展的 RAG 系统 |