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

理解 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 数据流

markdown 复制代码
┌──────────── 离线准备(索引)────────────┐
│                                        │
│  文档 → 解析 → 切分 → 向量化 → 向量库  │
│                                        │
└────────────────────────────────────────┘
                  ↓
┌──────────── 在线查询(回答)────────────┐
│                                        │
│  问题 → 向量化 → 向量库搜索相似段落     │
│    → 拼接 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 核心实现步骤(六步)

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

3.2 流程图

css 复制代码
用户上传文档                    用户提问
    │                              │
    ▼                              ▼
┌─────────┐                  ┌─────────┐
│ 解析文本 │                  │ 向量化   │
│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, ...]

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

arduino 复制代码
"苹果手机" → [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-small API 调用,不开源

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

4.3 Embedding 模型(向量大模型)

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

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

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

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

4.4 语言大模型 (LLM)

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

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

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

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

4.5 相似度 (Similarity)

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

方式 含义 取值范围 怎么理解
余弦相似度 两个向量方向的接近程度 -1 到 1 1 = 完全相同方向,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 的核心要素:

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

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


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

5.1 核心结论

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

5.2 各家产品技术架构对比

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

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

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

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

5.4 谁在"计算"答案?

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

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

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

关键认知:

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

用餐厅类比:

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

回到你的场景

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

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

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

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

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

arduino 复制代码
你问 "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 的 Embedding DeepSeek 用自研 embedding 做搜索 ❌ 云端
DeepSeek 的 LLM DeepSeek-V3 生成回答 ❌ 云端

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


六、延伸阅读

你想了解 去看
从零安装和使用 RAG 系统 二、从零搭建本地 RAG 知识库
逐行写出 RAG 的全部代码 三、手把手教你从零写一个本地 RAG
Demo 怎么升级到生产级 三、手把手教程 §16 (技术选型 + 架构图 + 评估体系)
面试 RAG 岗位 四、RAG 模拟面试问答
相关推荐
lichenyang4535 小时前
鸿蒙聊天 Demo 练习 04:聊天历史本地缓存,实现消息记录持久化
前端
AINative软件工程5 小时前
不做 A/B 测试的 prompt 优化都是在赌运气:生产级 LLM A/B 实验完整方案
llm
名字都不重要何况昵称5 小时前
canvas 元素拾取
前端·canvas
从文处安5 小时前
「前端何去何从」React Router:让单页应用有多页的体验
前端·react.js
Lkstar5 小时前
Vue Router 进阶:导航守卫、动态路由与懒加载,源码级理解
前端
ricardo19735 小时前
# Tree Shaking 深度解析:为什么你的代码没被摇掉?
前端·面试
前端流一5 小时前
踩坑实录:Vite打包AntD5报错 rc-picker/es/generate/dayjs 模块找不到
前端
_按键伤人_5 小时前
三、手把手教你从零写一个本地 RAG
前端·llm·ai编程