RAG(检索增强生成)流程方案

是什么

RAG 检索增强生成(Retrieval Augmented Generation),是当下最火热的LLM应用方案和打开方式了

通过自有垂域数据库检索相关信息,然后合并成为提示模板,给大模型润色生成回答,绝大多数AI平台的“知识库”功能都应用的方案

为什么

每当将大模型应用于实际业务场景时发现,通用的基础大模型基本无法满足实际业务需求,主要有以下几方面原因:

  • 知识的局限性:大模型自身的知识完全源于训练数据,而现有的主流大模型(deepseek、gpt、通义千问…)的训练集基本都是构建于网络公开的数据,对于一些实时性的、非公开的或私域的数据是没有。
  • 幻觉问题:所有的深度学习模型的底层原理都是基于数学概率,模型输出实质上是一系列数值运算,大模型也不例外,所以它经常会一本正经地胡说八道,尤其是在大模型自身不具备某一方面的知识或不擅长的任务场景。
  • 数据安全性:对于企业来说,数据安全至关重要,没有企业愿意承担数据泄露的风险,尤其是大公司,没有人将私域数据上传第三方平台进行训练会推理。这也导致完全依赖通用大模型自身能力的应用方案不得不在数据安全和效果方面进行取舍。

所以我们需要一个方案来实现私有数据与大语言模型的串联,达到AI知识库平台的完美整合,RAG就是其中一个方案

怎么做

完整的RAG应用流程主要有:

  • 文档预处理(索引):数据提取——>文本分割——>向量化(embedding)——>数据入库
  • 检索与增强(查询):用户提问——>数据检索——>生成文本块(相关度最高)
  • 生成回答:用户问题 + 检索文本块——>注入Prompt——>LLM生成答案

处理流程上传文件 → 分块 → 向量化 → 存入向量库 以及 用户提问 → 向量化 → 检索相关块 → 拼接Prompt → 发送给大模型

使用 Embedding 模型生成向量

用户向量生成

1
2
3
4
5
6
7
8
9
用户问题

Embedding 模型

[0.0123, -0.2345, 0.8765, ...]

Query Vector

vector_client.query_points()

把用户输入的自然语言文本,通过 Embedding 模型转换成一个固定维度的浮点数数组,然后将这个数组作为 Query Vector 去 向量数据库搜索。

文档向量生成

1
2
3
4
5
6
7
文档:

Embedding 模型

[0.1, 0.2, 0.3, ...]

Vector OSS

文档通过转化成文字内容,调用 Embedding 模型,也可以向量化存入向量数据库

关键问题1:使用同一个 Embedding 模型

这是非常重要的一点,文档向量用户向量 必须使用相同 Embedding 模型

文档入库时使用的文档是 Embedding 模型 A,用户查询时也必须使用 Embedding 模型 A

这样保证输入输出的维度相同

关键问题2:向量数据库如何存储大文档

有一下三种场景:

  • 场景一:标准做法(只查向量数据库)

做法:你在将文档分块存入向量数据库时,不仅存入了向量(Vector),还把该文本块的具体内容(Text/Content) 作为元数据(Payload)一并存入了数据库。
查询逻辑:用户提问 -> 向量检索 -> 向量数据库直接返回最相似的Top-K个结果,这些结果里直接自带文本块内容
结论:拿到文本块内容后,直接拼接到Prompt里发给大模型即可。完全不需要再查原始数据库,这是最高效的方式。

  • 场景二:仅存指针(必须查原始数据库)

做法:为了节省向量数据库的存储空间,你只存了向量和一个document_idchunk_id指针,没有存具体的文本内容。
查询逻辑:向量数据库返回ID -> 系统拿着这个ID去原始数据库(比如对象存储OSS、关系型数据库)查询对应的完整文本块。
结论:这种情况下,必须再查一次原始数据库,否则你只拿到了坐标,拿不到文字。

  • 场景三:父文档检索(需要查原始数据库)

这是RAG进阶玩法。你在向量数据库里存的是细粒度的小块(Child Chunk),但你希望大模型读取的上下文是大块的原文(Parent Chunk)
查询逻辑:向量数据库匹配到相关性高的小块,返回的Payload里包含parent_id -> 系统拿着这个ID去原始数据库或专门的缓存中,查询该父块对应的一大段完整原文。

结论在绝大多数标准RAG实践中,你只需要查询向量数据库,不需要再查一次原始数据库(如MySQL、OSS或MongoDB)来获取文档内容。

查原始数据库目的是为了用小块的命中,换回大块的原文,提供更丰富的上下文。为了追求最佳性能和最低延迟,强烈建议采用场景一(将文本块存入Payload)。向量数据库本身就是为这种高频读取代而设计的,多一次数据库往返查询(Network I/O)会显著增加系统延迟,而且如果原始文档过大(比如一整本书),你根本无法一次性把整本书都查出来喂给模型,所以查原始文档(全量)在RAG中几乎永远是错误的选择,你永远只需要查相关的“块”

关键问题3:文档较大时的较优入库步骤

当文件特别大时,比如一本几十万字的教科书

用户查询的时候不可能将全部内容丢给LLM进行处理,因为根本塞不进上下文窗口,强行塞入会严重变慢且困惑度飙升

平台也绝不会将整篇文章塞入向量数据库,因为数据库中metadata不支持存入过大的数据且 Embedding 模型的上下文窗口也不支持这么多的内容

而应该设计成一个可拆分、可并行、异步处理的文档处理流水线

推荐的整体流程:

文档处理 → 文本切块 → 向量化 → 向量数据库入库

每一步都应该是独立的,而且都需要分批处理

第一步:文档解析与文档拆分

对于 Markdown 文档,最好保留一级标题、二级标题、正文等文章架构,不要单纯按照纯文本来按字数拆分,这样容易破坏语义

推荐的切分方式:

1
2
3
4
5
6
7
8
9
10
11
文档
├── 第1章
│ ├── 1.1 系统概述
│ │ ├── Chunk 1
│ │ ├── Chunk 2
│ │ └── Chunk 3

└── 第2章
├── 2.1 数据库设计
│ ├── Chunk 4
│ └── Chunk 5
  • 按标题层级(如 H2/H3)、段落边界或固定 token 数对 Markdown 进行拆分
  • 推荐 500~800 token/块,兼顾检索精度和上下文完整性
  • 每个 chunk 保留一定的重叠(overlap,如 50~100 token),避免信息被截断
  • 可用 Python 库如 tiktoken 按 token 切分,或按 Markdown 标题自然分段

对于 CSV 表格文档,需要尽量保持语意的完整,所以推荐一条 Point 就对应一行记录

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"id": 1,
"vector": [...],
"payload": {
"name": "扬子鳄",
"alias": "鼍",
"scientific_name": "Alligator sinensis",
"english_name": "Chinese alligator",

"kingdom": "动物界",
"phylum": "脊索动物门",
"class": "爬行纲",
"order": "鳄形目",
"family": "鼍科",
"genus": "鼍属",

"national_protection": "Ⅰ",
"iucn": "CR",
"rcb": "CR",
"china_endemic": true,
"cites": "Ⅰ",

"distribution_type": "S",
"author": "Fauvel, 1879"
}
}

同时需要 Embedding 的 metadata 将所有表头和内容的描述拼接起来如下:

1
2
3
{
"content": "物种名称:扬子鳄\n中文异名:鼍\n拉丁学名:Alligator sinensis\n..."
}

对于文档中为空的字段,尽量直接忽略,不要塞进 Embedding

第二步:向量化与向量入库

Embedding 和 向量入库 都需要批量执行,例如阿里云的 OSS Vectors SDK

生产环境需要采用异步任务,异步的进行文档的切分、向量化、向量入库

耗时估算

耗时主要由 Embedding 生成 + 向量写入 两部分决定,以10 万字的 Markdown 文档(约 300 KB) 为例粗估:

假设每块约 500 token(约 300 中文字),10 万字 ≈ 400 个 chunk,下面分环节估算(以 text-embedding-v4 + PutVectors 为例):

  1. Embedding 生成耗时(百炼 API)

    text-embedding-v4 单次调用延迟通常 50~200ms(取决于输入长度和网络)。

    QPS 约 100~200

    并发路数 串行总调用时间 实际总耗时
    1(纯串行) 400 × 100ms ≈ 40s ~40s
    5(CLI 默认) 400 ÷ 5 ≈ 80 批 | 80 * 100ms = 8s ~8s
    10 400 ÷ 10 ≈ 40 批 | 40 * 100ms = 4s ~5s
  2. 向量写入 OSS Vectors 耗时

    PutVectors 接口:

    • 单批最大 500 条
    • QPS 上限 1000

    400 个 chunk 只需 1 批 即可写完,单次请求耗时 ~200ms~1s。

  3. 总耗时汇总

    环节 估算
    本地分块预处理 < 1s(纯文本切分,毫秒级)
    Embedding 生成(5并发) ~7s
    向量写入(1 批) ~0.5s
    总计 ~8~10 秒

对比不同规模

如果是超大规模数据(千万级),文档建议采用多索引表架构——按业务维度拆分成多个 Index,并发检索后再合并结果,可显著降低单次检索延迟。

文档规模 chunk 数 Embedding(5 并发) 写入批次数 总耗时
1 万字 ~40 ~1s 1批 ~2s
10 万字 ~400 ~8s 1批 ~8~10s
100 万字 ~4000 ~80s 8批 ~85~90s
1000 万字 ~40000 ~13min 80批 ~14~16min

总结

RAG(检索增强生成) = 检索技术 + LLM 提示

向 LLM 提问一个问题,RAG 从各种数据源检索相关的信息,并将检索到的信息和答案注入到提示词模板中,LLM 最后给出答案

AI平台的“知识库”功能的 RAG模型调用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
┌────────────────────────┐
│ 用户上传文档 │
└───────────┬────────────┘

┌────────────────────────┐
│ OSS / 文件存储 │
│ 保存原始文档 │
└───────────┬────────────┘

┌────────────────────────┐
│ 文档任务表 │
│ PENDING │
└───────────┬────────────┘

┌────────────────────────┐
│ 异步任务队列 │
└───────────┬────────────┘

┌────────────────────────┐
│ Document Parser │
└───────────┬────────────┘

┌────────────────────────┐
│ Chunk Processor │
└───────────┬────────────┘

┌────────────────────────┐
│ Embedding Worker │
│ 批量 + 并发 │
└───────────┬────────────┘

┌────────────────────────┐
│ OSS Vertor │
│ Batch Upsert │
└────────────────────────┘