一、理解 RAG:从概念到实践

50 阅读14分钟

理解 RAG:从概念到实践

本文用 WWH 原则(What / Why / How)系统讲解 RAG。读完你将理解 RAG 的本质、为什么现代 AI 产品离不开它、以及如何自己实现一个。


一、What — RAG 是什么?

1.1 一句话定义

RAG = Retrieval-Augmented Generation(检索增强生成)。

它是一种让 AI 先"查资料"再"回答问题"的技术架构——在 LLM 生成回答之前,先从外部知识库中检索相关信息作为参考。

1.2 类比理解

把 LLM 想象成一个参加开卷考试的学生

场景对应 RAG 概念
学生的大脑和语言能力LLM(语言大模型)
考场提供的参考书你的文档(知识库)
遇到题目先翻书找相关章节检索(Retrieval)
翻到的章节检索到的 Chunk(文档片段)
基于翻到的内容写答案生成(Generation)
答不出来就老实说不知道System Prompt 约束

如果这个学生不能翻书(纯 LLM)——那他只能凭记忆答题,记忆截止在训练日期,不知道的内容就瞎编。

1.3 数据流

┌──────────── 离线准备(索引)────────────┐
│                                        │
│  文档 → 解析 → 切分 → 向量化 → 向量库  │
│                                        │
└────────────────────────────────────────┘
                  ↓
┌──────────── 在线查询(回答)────────────┐
│                                        │
│  问题 → 向量化 → 向量库搜索相似段落     │
│    → 拼接 Prompt → LLM 生成 → 回答     │
│                                        │
└────────────────────────────────────────┘

二、Why — 为什么要用 RAG?

2.1 LLM 的三大先天缺陷

缺陷具体表现RAG 如何解决
知识截止LLM 训练数据有截止日期,不知道训练后发生的事情文档实时上传,知识随时更新
幻觉没有信息来源时,LLM 倾向于编造看似合理但错误的内容给 LLM 提供参考资料,约束它"只能基于资料回答"
私有知识盲区LLM 从未见过你公司的内部文档、代码库、合同把私有文档向量化后存入本地知识库

2.2 为什么不能直接把文档全塞给 LLM?

三个原因:

  1. 上下文窗口有限:即使是最新的百万 token 上下文模型,塞一本 500 页的书也会超出限制,而且推理极慢、成本极高
  2. 信噪比低:整本书 99% 的内容和你的问题无关,LLM 容易被无关信息干扰
  3. 成本指数增长:上下文越长,推理计算量越大,API 费用越高

RAG 的做法是先筛选、再阅读——只把最相关的几个段落喂给 LLM。

2.3 RAG vs Fine-tuning vs 长上下文

方式适合什么不适合什么
RAG知识频繁变化、需要溯源、数据安全需深度理解文档整体结构的任务
Fine-tuning让 LLM 学会特定风格/格式/术语保证知识的准确性(微调学模式,不学事实)
长上下文单篇文档的完整理解多文档、高并发、成本敏感

三者不互斥,实际产品中常组合使用:RAG 检索 + 长上下文理解 + Fine-tuning 定制风格。


三、How — 怎么实现?

3.1 核心实现步骤(六步)

步骤一:文档解析
  把 PDF/Word/Markdown 等格式的文档提取为纯文本
  ↓
步骤二:文本切分 (Chunking)
  把长文本切成小块(每块 ~500 字),块之间保留少量重叠
  ↓
步骤三:向量化 (Embedding)
  每块文字通过 Embedding 模型转成一组数字(如 384 维向量)
  ↓
步骤四:向量存储
  把所有向量存入向量数据库(如 LanceDB),建索引
  ↓
步骤五:检索 (Retrieval)
  用户问题也转成向量 → 在向量库中找最相似的 Top-K 个文档块
  ↓
步骤六:生成 (Generation)
  文档块 + 问题 → 拼成 Prompt → 发给 LLM → 生成回答

3.2 流程图

用户上传文档                    用户提问
    │                              │
    ▼                              ▼
┌─────────┐                  ┌─────────┐
│ 解析文本 │                  │ 向量化   │
│pdf-parse│                  │384维向量 │
│mammoth  │                  └────┬────┘
│marked   │                       │
└────┬────┘                  ┌────▼────┐
     │                       │ 向量检索 │
     ▼                       │ Top-5   │
┌─────────┐                  │ 相似度   │
│ 文本切分 │                  │ ≥ 0.5   │
│512token │                  └────┬────┘
│64overlap│                       │
└────┬────┘                  ┌────▼────┐
     │                       │Prompt拼接│
     ▼                       │system+  │
┌─────────┐                  │ctx+问题 │
│ 向量化   │                  └────┬────┘
│384维    │                       │
└────┬────┘                  ┌────▼────┐
     │                       │ LLM生成 │
     ▼                       │llama.cpp│
┌─────────┐                  │流式输出 │
│LanceDB  │                  └────┬────┘
│向量存储  │                       │
└─────────┘                  ┌────▼────┐
                             │前端渲染  │
                             │回答+来源 │
                             └─────────┘

3.3 详细实现指引

你想做的事参考文档
20 分钟搭一个能用的 RAG 系统二、从零搭建本地 RAG 知识库
逐行写出完整代码 + 深入理解设计原理三、手把手教你从零写一个本地 RAG
理解 Demo 到生产级的升级路径三、手把手教程 §16(技术选型 + 架构图 + 评估体系)
面试 RAG 相关岗位四RAG 模拟面试问答(27 个问答)

四、核心概念速查

4.0 Token(词元)

一句话:Token 是 LLM 和 Embedding 模型处理文本的最小单位

一个中文字 ≈ 1~2个 token,一个英文单词 ≈ 1~2 个 token。当你看到"模型最大支持 512 token"时,意思是它一次只能"读懂"约 300~500 个中文字。

这就是为什么长文档必须切成小块——不是不想一次处理整本书,而是模型的"阅读窗口"有限。


4.1 向量 (Vector)

一句话:向量是一串有序数字,用来表示一段文字在"语义空间"中的位置。

举例:all-MiniLM-L6-v2 模型把任意文本转成一个 384 维向量——即一个包含 384 个浮点数的数组,比如 [0.12, -0.34, 0.78, ...]

为什么有用:含义相近的文字,在向量空间中距离也近。

"苹果手机" → [0.8, 0.3, 0.1, ...]
"iPhone"  → [0.7, 0.4, 0.1, ...]    ← 距离很近
"香蕉"    → [0.1, -0.5, 0.9, ...]   ← 距离很远

4.2 维度 (Dimension)

一句话:向量有多少个数字,就是多少维。

维度模型示例特点
384 维all-MiniLM-L6-v2轻量,够用
768 维BGE-base-zh-v1.5平衡
1024 维BGE-large-zh-v1.5更精准,但更大更慢
1536 维OpenAI text-embedding-3-smallAPI 调用,不开源

维度越高,语义信息越多,但检索速度越慢、存储越大。384 维对大多数场景已经够用。

4.3 Embedding 模型(向量大模型)

一句话:把文字转成向量的模型。

和 LLM(语言大模型)是两回事:

Embedding 模型LLM(语言大模型)
输入一段文字一段文字
输出一串数字(向量)一段文字(回答)
目的衡量"文字有多像"生成自然语言
大小小(80MB~2GB)大(0.5B~数百B 参数)
例子all-MiniLM-L6-v2, BGE-large-zhQwen2.5, GPT-4, DeepSeek-V3

在 RAG 中的分工:Embedding 模型负责"找到相关段落",LLM 负责"基于段落写回答"。

4.4 语言大模型 (LLM)

一句话:能理解和生成人类语言的 AI 模型。

在 RAG 中的角色:阅读检索到的文档片段,基于它们生成自然语言回答。

为什么小模型(0.5B)和大模型(7B+)差距很大?

  • 0.5B:能理解简单指令,但容易忽略 Prompt 约束、输出不稳定、中文能力有限。适合 Demo 演示流程
  • 7B+:指令遵循能力强、幻觉率明显降低、中文理解和生成质量高。适合生产环境
  • 72B+:推理能力强、能处理复杂多跳问题、但硬件要求高

推理引擎 vs 模型文件——为什么 LLM 是两个东西?

很多初学者以为"下载一个大模型"就是一个可双击的 .exe 文件。实际上,跑一个本地 LLM 需要两个独立的东西配合:

推理引擎模型文件
是什么可执行程序(如 llama-server权重数据文件(如 .gguf
装了什么计算逻辑——怎么跑 Transformer知识——数十亿个训练出来的参数
类比游戏机游戏卡带
独立运行?不能,必须加载模型不能,只是数据

启动命令本质上是:llama-server -m qwen2.5-0.5b.gguf——让引擎把模型"吃进去",然后在 8080 端口提供 HTTP API。换模型只需换 -m 后面的文件路径,引擎不用变。

这在你的本地 RAG Demo 中有直接体现:bin/ 目录放引擎(llama-server),models/ 目录放模型文件(.gguf),generator.tsspawn 把二者连接起来。

4.5 相似度 (Similarity)

一句话:衡量两个向量有多"近"的数学指标。

方式含义取值范围怎么理解
余弦相似度两个向量方向的接近程度-1 到 11 = 完全相同方向,0 = 垂直(无关),-1 = 相反
欧氏距离空间中两点直线距离0 到 ∞0 = 完全相同,越大越不相似

RAG 中最常用余弦相似度。实践中通常设一个阈值(如 0.5),低于这个值的检索结果直接丢弃。

如果所有结果都低于阈值怎么办? 这说明知识库里没有相关内容。此时不应勉强让 LLM "编一个答案"——正确做法是直接告诉用户"未找到相关文档段落,请尝试上传更多相关文件或换个问题"。

4.6 Chunk(文档片段)

一句话:把长文档切成的小块,每块是检索的最小单位。

为什么切分?

  • Embedding 模型一次只能处理有限长度的文本
  • 小块检索更精准——"关于绩效考核的规定"只应该命中相关段落,而不是整本员工手册

切分关键参数

  • Chunk Size:每块多大(中文建议 300-500 字)
  • Chunk Overlap:相邻块重叠多少(防止关键信息被切在边界上)

4.7 向量数据库 (Vector Database)

一句话:专门存储和搜索向量的数据库。

传统数据库向量数据库
WHERE name = '张三'(精确匹配)"找和这段话最相似的 5 个段落"(近似匹配)
B-Tree 索引HNSW / IVF 索引
返回精确结果返回近似结果(可调精度)

常用选择:

  • Demo/个人项目:LanceDB(嵌入式,零配置)
  • 生产/团队:Milvus / Qdrant(分布式,支持混合检索)

4.8 Prompt 模板

一句话:告诉 LLM"你是谁、用什么资料、怎么回答"的指令。

RAG Prompt 的核心要素:

1. 角色定义 → "你是一个文档问答助手"
2. 约束规则 → "仅使用下面的参考资料,不知道就说不知道"
3. 参考资料 → (检索到的文档片段)
4. 用户问题 → (用户实际输入)
5. 格式要求 → "用 Markdown 格式回答"

为什么"不要编造"是关键约束?没有这句,LLM 在检索不到信息时会用自己的训练知识补充回答——看起来合理,但可能完全错误。


五、在线 AI 产品是怎么做的?

5.1 核心结论

DeepSeek、豆包、Kimi 都不是"一个纯 LLM 在回答问题"。它们背后是 LLM + 多种增强技术的组合系统。

5.2 各家产品技术架构对比

能力DeepSeek豆包 (字节)Kimi (月之暗面)
核心 LLMDeepSeek-V3 / R1豆包大模型 (自研)Moonshot-v1 / k1.5
联网搜索✅ 手动✅ 自动判断✅ 默认开启
文档 RAG✅ 支持上传文件问答✅ Kimi+ 上传文件
长上下文128K128K200 万字
代码/工具✅ 代码解释器✅ 代码执行
推理增强✅ R1 深度思考✅ k1.5 思维链
多模态❌ 纯文本✅ 图片理解✅ 图片/文件
记忆系统✅ 多轮对话

5.3 它们的增强管线长什么样?

你输入一个问题后,实际发生的是:

你的问题
    │
    ▼
┌─────────────┐
│  意图识别    │  ← "需要联网吗?需要查文档吗?还是可以直接回答?"
└──────┬──────┘
       │
  ┌────┼────┬──────────┐
  ▼    ▼    ▼          ▼
┌──┐ ┌──┐ ┌──┐    ┌──────┐
│联 │ │文 │ │工 │    │直接   │
│网 │ │档 │ │具 │    │回答   │
│搜 │ │RA │ │调 │    │(常识)  │
│索 │ │G  │ │用 │    │       │
└─┬┘ └─┬┘ └─┬┘    └───┬───┘
  │    │    │          │
  └────┼────┼──────────┘
       │    │
       ▼    ▼
  ┌─────────────────┐
  │   上下文拼装      │  ← 搜到的网页 + 文档片段 + 工具结果 + 历史对话
  └────────┬────────┘
           ▼
  ┌─────────────────┐
  │   语言大模型      │  ← 阅读上下文,生成回答
  └────────┬────────┘
           ▼
  ┌─────────────────┐
  │   格式化 + 引用   │  ← 标注来源、渲染 Markdown
  └─────────────────┘

5.4 谁在"计算"答案?

这个问题非常关键:当你在 DeepSeek 里输入一个问题时,是 LLM 自己判断需要联网搜索,还是另一个程序在指挥它?

答案是:另一个程序在指挥 LLM,LLM 只负责"生成文字"。

你的问题: "今天杭州天气怎么样?"
        │
        ▼
┌─────────────────────────┐
│  编排程序(Orchestrator)│  ← 这不是 LLM,是工程师写的调度代码
│                         │
│  "这个问题涉及时效信息,   │
│   需要联网搜索"           │
└───────────┬─────────────┘
            │
      ┌─────┼─────┐
      ▼           ▼
┌──────────┐ ┌──────────┐
│ 搜索引擎  │ │ 直接给LLM │
│ 抓取天气  │ │(无需搜索  │
│ 相关网页  │ │  的问题)  │
└────┬─────┘ └─────┬────┘
     │             │
     ▼             │
┌──────────┐       │
│提取天气数据│      │
│作为上下文  │      │
└────┬─────┘       │
     │             │
     └──────┬──────┘
            ▼
┌─────────────────────────┐
│      语言大模型 (LLM)     │
│                         │
│  收到:                  │
│  "用户问天气,这是搜索到   │
│   的信息:[网页内容]"      │
│                         │
│  输出:                  │
│  "今天杭州晴转多云,       │
│   气温 18-25°C..."       │
└─────────────────────────┘

关键认知:

角色谁在做能力边界
编排程序(Orchestrator)工程师写的代码判断意图、调用搜索、组装上下文。不会生成文字
向量模型(Embedding)专门的小模型(如 all-MiniLM-L6-v2)只做一件事:把文字转成向量。不会生成文字
语言大模型(LLM)DeepSeek-V3 / Qwen / GPT只做一件事:收到上下文 + 问题 → 生成回答。不会搜索、不会判断意图

用餐厅类比:

顾客(你)点菜
    │
    ▼
服务员(编排程序) —— 判断"这个菜厨房能做吗?需要去隔壁菜市场买菜吗?"
    │
    ├── 去菜市场(搜索引擎/向量库)拿食材
    │        │
    │        ▼
    │   采购员(向量模型) —— 在菜市场找最匹配的食材
    │
    └── 把食材 + 订单交给厨师
            │
            ▼
        厨师(LLM) —— 只负责烹饪。不会买菜,不会接待顾客

回到你的场景

"我问 DeepSeek 一个简单问题,是 LLM 自动计算最佳匹配结果,还是由另一个程序完成?"

另一个程序完成检索和路由,LLM 只负责最后一步——看着参考资料写回答。

这和你搭建的本地 RAG Demo 完全一致:chat.ts(编排程序)→ retriever.ts(检索)→ generator.ts(调用 LLM)→ 返回。你写的 chat.ts 就是那个"服务员"。

5.5 那"1+1 等于几"这种问题呢?

这个问题触及了关键:不是所有问题都需要走检索流程。

你问 "1+1 等于几?"                  你问 "公司去年的营收是多少?"
        │                                      │
        ▼                                      ▼
┌───────────────┐                      ┌───────────────┐
│   编排程序     │                      │   编排程序     │
│               │                      │               │
│ 这是常识问题   │                      │ 这需要查文档   │
│ LLM 自己知道   │                      │ 或联网搜索     │
└───────┬───────┘                      └───────┬───────┘
        │                                      │
        │ 直接跳过检索                          │ 先检索再生成
        │                                      │
        ▼                                      ▼
┌───────────────┐                      ┌───────────────┐
│    LLM        │                      │  向量模型检索  │
│               │                      │  → 拿到文档    │
│ 回答: 2       │                      │  → LLM 生成回答│
└───────────────┘                      └───────────────┘

三种问题的处理路径完全不同:

问题类型举例编排程序怎么做LLM 拿到什么
常识/知识类"1+1 等于几?"、"中国的首都是?"直接发给 LLM只有问题本身
时效类"今天天气怎么样?"、"最新的 iPhone 是哪款?"先联网搜索,结果作为上下文问题 + 搜索结果
文档/私有知识类"我们公司去年的营收是多少?"先从向量库检索,文档片段作为上下文问题 + 文档片段

关键认知:即使是"1+1"这种问题,LLM 也不是在"计算",而是在"回忆"

LLM 本质上是一个文字接龙机器——它在训练数据中见过无数次"1+1=2"这个模式,所以能"猜"出下一个 token 大概率是"2"。它没有内置计算器,也不会真正进行数学运算。

这就是为什么:

  • 简单的 1+1 → LLM 能答对(训练数据里见过太多次)
  • 复杂的 17 位数乘法 → LLM 经常答错(没见过这个具体算式,只能硬猜)
  • 需要精确计算的场景 → 应该让编排程序调用一个真正的计算器,把结果传给 LLM(这就是"工具调用")

总结:编排程序是大脑,LLM 是嘴巴。

场景编排程序(决策者)LLM(执行者)
"1+1=?"判断:不需要检索,直接回答凭训练记忆输出"2"
"今天天气?"判断:需要联网 → 调搜索引擎 → 把结果拼进 Prompt基于天气数据生成自然语言回答
"公司财报分析?"判断:需要查文档 → 调向量库检索 → 把 Chunk 拼进 Prompt基于文档片段生成分析
"12345 × 67890 = ?"判断:需要精确计算 → 调计算器 API → 把结果拼进 Prompt基于计算结果生成回答

5.6 关键洞察

① 联网搜索 = 互联网版的 RAG

你问"今天天气怎么样"→ 搜索引擎抓取网页 → 提取相关片段 → 喂给 LLM → 生成回答。这和本地文档 RAG 原理完全一样,区别只是"知识源"从本地 PDF 换成了互联网网页。

② 长上下文 ≠ RAG,两者互补

Kimi 的 200 万字上下文是把整本书塞进 LLM,让它通读。好处是不丢信息,代价是慢和贵。实际产品中常混合:RAG 快速筛出候选段落 → 长上下文精细理解。

③ Embedding 模型和 LLM 是两回事,但配合工作

谁是做什么在你的电脑上
Embedding 模型 (all-MiniLM-L6-v2)把文字转成向量,做搜索✅ 本地 CPU 跑
LLM (Qwen2.5-0.5B)生成回答✅ 本地 CPU 跑
DeepSeek 的 EmbeddingDeepSeek 用自研 embedding 做搜索❌ 云端
DeepSeek 的 LLMDeepSeek-V3 生成回答❌ 云端

本地 RAG 的价值在于:你把"搜索"和"回答"都搬到自己电脑上,数据不离开你的硬盘。


六、延伸阅读

你想了解去看
从零安装和使用 RAG 系统二、从零搭建本地 RAG 知识库
逐行写出 RAG 的全部代码三、手把手教你从零写一个本地 RAG
Demo 怎么升级到生产级三、手把手教程 §16 (技术选型 + 架构图 + 评估体系)
面试 RAG 岗位四、RAG 模拟面试问答