RAG 的核心环节
这一页把 RAG 的两条链路展开,逐环节讲清楚「在做什么、为什么这么做、容易在哪里出错」。 读完这一页,你会对后面所有模块代码建立直觉。
一、入库链路(Ingestion)
入库链路负责把不可信的原始文件变成可信、可追溯、可检索的索引。
1. 上传与校验
- 用户在界面上传文件(Markdown / PDF / Word / Excel / PPT / HTML / 图片)。
- 系统先做输入安全校验:文件大小、扩展名、MIME 类型、内容是否为空。
- 关键点:上传文件是不可信输入,文件名不能用来拼路径(防路径穿越),扩展名不能当作内容真相(后面 Parser 会再校验真实结构)。
2. 保存原始文件
- 原始文件存入 MinIO(对象存储),对象键由系统生成(如
知识库ID/文档UUID/source.pdf),不用用户文件名。 - 先保存原文件再建记录:即使后续处理失败,也能用原文件排查或重建索引。
3. 解析(Parsing)
这是 V2 的核心能力。把各种格式统一成一种内部模型:
Markdown / PDF / DOCX / XLSX / PPTX / HTML / 图片
↓ Parser(翻译官)
ParsedDocument(统一文档模型)
↓
Block[](标题 / 正文 / 表格 / 代码 / 图片…)每个 Block 还带一个 SourceLocator(来源定位器),记录「这段内容来自哪里」:
- Markdown → 标题路径(如
["RAG", "Embedding"]) - PDF → 页码 + 坐标框(BBox)
- Excel → 工作表 + 单元格区域
- PPT → 幻灯片序号
这一层是关键设计:RAG 主流程永远不判断文件后缀,只认统一的
ParsedDocument。新增一种文件格式,只需要新增一个 Parser,主流程不动。
4. 切块(Chunking)
解析完的文档可能很长,不能整篇送去向量化,所以要切成小片段(Chunk)。
切块策略直接决定检索质量,要点是:
- 按结构边界切(标题、表格、代码块),而不是机械地按字符数切
- 每个 Chunk 保留来源信息(标题路径、页码),方便日后引用
- 控制 Chunk 大小(本项目默认 512 tokens),并留少量重叠(overlap,默认 64)避免切断语义
- 表格重复表头、代码保持围栏、正文优先按段落/句子切
5. 向量化(Embedding)
把文本 Chunk 变成向量(如 1024 维浮点数组)。向量化的意义是:语义相近的文本,向量距离也近。
关键要求:文档向量化与问题向量化必须用同一个模型,否则两者的向量不在同一个语义空间,检索没有意义。
6. 索引(Indexing)
把「文本 + 向量 + 来源信息」写入向量数据库(本项目用 Milvus),并保持「主键稳定、可重复写入」,保证重试不会产生重复数据。
二、问答链路(Query)
1. 问题向量化
用同一个 Embedding 模型把用户问题转成向量。
2. 检索(Retrieval)
在向量库里找出与问题向量最相似的 K 个 Chunk,同时限定知识库范围(不能跨库召回)。
3. 构建上下文(Context Building)
把召回的 Chunk 按相似度排序,每个标上 [来源 1] [来源 2]…,在字符预算内拼成一段知识上下文。
上下文预算(本项目默认 12000 字符)决定 LLM 每次最多「看」多少证据。超出的低排名 Chunk 会被丢弃。
4. 生成(Generation)
把「知识上下文 + 用户问题」一起发给 LLM,要求它只依据上下文回答,不能编造,并输出时标注 [来源 N]。
5. 引用(Citation)
系统在返回答案的同时返回结构化引用(文档名、章节、页码),让用户能回到原文验证。
三、两条链路如何衔接
入库链路(做一次)
上传 → 校验 → 存MinIO → 解析 → 切块 → 向量化 → 索引
│
▼
PostgreSQL(事实) + Milvus(向量)
▲
问答链路(做多次) │
用户提问 → 向量化 → 检索 ←────────────────────────────┘
→ 拼上下文 → LLM → 答案 + 引用核心要点:入库产生「事实数据 + 派生索引」,问答只消费它们。
- PostgreSQL 保存事实(文档状态、Chunk、Asset 元数据、会话与回答证据)
- MinIO 保存原始文件和抽取图片 Asset
- Milvus 保存可重建的派生索引(丢了可以用事实数据重新生成)
四、两条容易出错的地方
| 易错点 | 正确做法 |
|---|---|
| 用字符数切块,导致语义被切断 | 用 Token 预算 + 结构边界 |
| 文档向量和问题向量用了不同模型 | 全程共用同一个 Embedder |
| 把「未处理完的文档」放进检索结果 | 只有状态 READY 的文档才参与检索 |
| 把外部服务当作永不出错 | 设计超时、有界重试、失败状态 |
这些原则正是 UltimateRAG 代码里反复出现的注释主题。