# RAG 是什么?让 LLM 看着你的资料回答问题 > 本文是 RAG(Retrieval-Augmented Generation,检索增强生成)的科普介绍, > 不依赖具体源码。完整技术细节见 > [../rag-overview.md](../rag-overview.md)。 如果你看过 [Function Call 101](./function-call-101.md) 和 [MCP 101](./mcp-101.md),你已经知道 LLM 怎么 "用工具" 了。但还有一类 问题工具解决不了:**模型不知道你公司的数据**。 你问 ChatGPT "我们公司的报销额度是多少",它怎么可能知道? 报销政策没在它的训练数据里。 RAG 是目前最常用的解法。这篇讲它是什么、怎么工作、为什么需要专门搞一个 概念叫 "向量化"。 --- ## 从一个具体问题开始 假设你给公司做一个 AI 助手,员工问: > "我们公司的报销额度是多少?" 你把这句话丢给 LLM,得到: > "我无法获取你们公司的具体政策。建议查询员工手册或联系 HR..." 废话。员工知道员工手册里有,但 200 页谁愿意翻?他们就是想让 AI 直接 告诉他们。 问题在于:**LLM 训练时根本没见过你公司的员工手册**。它的"知识"全部 封存在训练权重里,新数据进不去。 你有几个选择: ### 选项 A:把整本手册塞进 system prompt ``` System: 你是公司 AI 助手。以下是员工手册: [200 页 PDF 文字...] User: 我们公司的报销额度是多少? ``` 理论可行,但: - 200 页可能 50 万 token,每次对话都付这个钱,贵 - 模型有"大海捞针"问题——内容太多,中间部分容易被忽略 - 手册更新了得重新塞,而且系统提示太长会挤占其他重要指令的空间 ### 选项 B:Fine-tuning(微调) 把员工手册做成训练数据,微调一个版本的模型,让它"记住"这些政策。 可行,但: - 训练贵,得 GPU 集群 - 政策一改要重训 - 微调可能让模型遗忘其他能力(灾难性遗忘) - 你需要一支 ML 团队 ### 选项 C:让模型自己搜 加一个 search 工具,让模型自己决定要不要查、查什么、读哪条结果。 可行(其实就是 [Function Call](./function-call-101.md) 的应用),但: - 多了几轮往返,慢 - 模型可能错过该查的时机 - 还是需要一个搜索引擎能搜到内部文档 ### 选项 D:RAG **在用户问问题之前,先去公司文档库里找相关段落,把段落和问题一起 塞给模型**,让模型基于段落回答。 ``` User 输入: "我们公司的报销额度是多少?" ↓ 在用户看不到的后台: ↓ [1] 检索:在公司文档库找最相关的几段 ↓ → 找到:"差旅报销单笔上限 2000 元,需主管审批..." ↓ [2] 增强:把检索结果拼进 prompt ↓ Prompt: "根据以下文档回答: ↓ 差旅报销单笔上限 2000 元,需主管审批... ↓ 问题:我们公司的报销额度是多少?" ↓ [3] 生成:LLM 基于资料回答 模型输出: "公司差旅报销单笔上限是 2000 元,需要主管审批。" ``` R + A + G。模型不"记住"任何东西,每次回答之前临时拿资料看一眼。 --- ## 为什么这是个好主意 对比一下: | 方案 | 知识更新 | 成本 | 可解释 | 实施难度 | | ---------------- | ----------------- | ----------------- | --------------------- | --------------------- | | 全塞 prompt | 每次都要重塞 | 每次对话都贵 | 不行(模型从一堆里挑) | 简单 | | Fine-tuning | 改了要重训 | 训练超贵,推理便宜 | 不行(知识进了权重) | 难 | | 让模型自己搜 | 实时(搜的是新数据) | 多轮往返 | 能("我搜了 X 得到 Y") | 中 | | **RAG** | 实时(查的是当前库) | 检索 + 一次推理 | 能("根据 X 文档") | 中 | RAG 几乎在所有维度上都不错。**它最大的优势是"知识跟模型解耦"**—— 你想换底层模型?随便换,检索逻辑没动;你想更新政策?改一下文档, 重新索引,搞定。 不用训练,不用 ML 团队,不用调几小时超参。 --- ## 三个步骤拆开看 ### [1] 检索:怎么找到 "相关" 的段落 最朴素的想法:**关键词搜**。 用户问 "退货",我在文档库 grep "退货",拿到所有包含"退货"的段落。 问题是: - 用户问 "退货" 但文档里只写 "退款政策" → 漏了 - 用户问 "我不想要这东西了" → 字面不含 "退货",漏了 - 用户问 "返货" → 也漏了 人类问问题是**语义的**,搜索却只能匹配**字面的**。 RAG 的核心创新就在这里:**用向量化把 "字面" 变成 "语义"**。 ### 向量化:把文字变成一组数字 `text-embedding-3-small`(OpenAI 的 embedding 模型)接收一段文字, 吐出 1536 个浮点数: ``` "退货" → [0.21, -0.13, 0.88, 0.45, ..., 0.07] "退款" → [0.23, -0.10, 0.85, 0.42, ..., 0.05] ← 数字很接近 "购物车" → [0.91, 0.40, -0.20, 0.10, ..., -0.30] ← 数字差很远 ``` 这 1536 个数字不是随便的——它们是 "语义坐标"。**意思相近的词, 数字也接近**。 为什么?embedding 模型预训练时的目标就是 "让意思相近的句子输出 向量接近,意思不同的句子向量远"。你不需要懂它的训练原理,只需要把 它当成一个黑盒:**输入文本,输出"语义指纹"**。 有了这个,检索就变成一件优雅的事: 1. 离线时:把文档库里每段文字都向量化,把(向量,原文)成对存进 向量数据库(Pinecone、Qdrant、LanceDB 之类) 2. 用户问问题时:把问题也向量化 3. 在数据库里找跟 "问题向量" 最接近的几个 "文档向量" 4. 把对应的**原文**拼进 prompt(注意是原文,不是向量) "接近"用 **余弦相似度** 算——把向量当 1536 维空间里的方向,方向越 一致,夹角越小,余弦值越接近 1。 ### 一个常见误解:向量和 prompt 很多人初学时会问:**"向量化的结果是什么?是不是把向量塞进 prompt?"** **不是。** LLM 看不懂向量,它只懂自然语言。向量只在"检索"这一步用 来匹配,匹配完之后用的是**原文**。 数据库里其实是两份数据并存: ``` ┌─────────────────────────────────────────────────────────────┐ │ id │ vector │ text │ │ #001 │ [0.21,-0.13,0.88,..]│ "退款政策:商品 7 天内可..." │ │ #002 │ [0.91, 0.40,-0.20,..]│ "购物车使用指南..." │ │ #003 │ [-0.55,0.70,-0.10,..]│ "隐私条款..." │ └─────────────────────────────────────────────────────────────┘ ↑ ↑ 用来 "找" 用来 "还原" ``` 类比:**向量像图书索引卡上的"主题分类号",原文像书的内容**。 读者(LLM)读的是书,索引卡只是帮你找到该读哪本书。 最终发给 LLM 的 prompt 长这样,**从头到尾全是自然语言**: ``` System: You are a helpful assistant. Answer based on the provided context. Context: --- [Source: policies/refund.md] 退款政策:商品 7 天内可申请退货退款,需保持原包装完好,联系客服 400-xxx 办理。 --- Question: 怎么退货? ``` 向量没出现,因为它的使命在检索那一步就完成了。 ### [2] 增强:把检索结果塞进 prompt 这一步简单,就是把找到的几段原文按某种格式拼进 prompt。常见格式: ``` 请基于以下资料回答用户问题。如果资料里没有相关信息,直接说"我不知道"。 [资料 1] {{ chunk_1 }} [资料 2] {{ chunk_2 }} [资料 3] {{ chunk_3 }} 用户问题:{{ user_query }} ``` "如果资料里没有相关信息,直接说我不知道" 这一句很重要—— 没它的话模型容易在检索失败时强行编一个答案出来。 ### [3] 生成:让 LLM 回答 普通 LLM 调用,但 prompt 里多了"参考资料"。模型在生成时会优先用 prompt 里给的内容,不太敢瞎编(因为提示明确告诉它"基于资料")。 这一步没什么特别的——RAG 的"魔法"全在前两步。 --- ## 实际工程里的坑 讲到这里你可能觉得 RAG 很简单——"切块 + 向量化 + cosine + 拼 prompt" 四步搞定。**它的概念确实简单,工程却充满坑**。 ### 坑 1:切块策略 文档不能整篇喂进去——一篇 50 页的政策手册一个向量,语义信息全被 平均掉了。要切块,但怎么切? - 按固定长度切?会切断句子,信息丢 - 按段落切?长度不均,有的几行有的几百行 - 按章节切?太大,匹配不精确 实际常用做法:**滑动窗口 + 重叠**——每 500 字一块,相邻块重叠 100 字, 避免边界丢上下文。更高级的做语义切块,用 embedding 相似度判断切点。 ### 坑 2:检索失败比幻觉更糟 如果检索没拉到相关文档,模型就只能瞎编了。它可能写出一个**听起来 专业、引用煞有介事但完全错误**的答案。 防御措施: - prompt 里明确写 "如果资料里没有就说不知道" - 设相似度阈值——top-1 的 cosine 低于 0.5 就直接拒绝回答 - 召回后做 reranker 二次验证 ### 坑 3:embedding ≠ 相关性 embedding 模型抓的是 "语义相似",不一定是 "对你这个问题有答案"。 例子:用户问 **"明天天气怎么样?"**,文档里有: - A. "天气预报通常由气象台发布..." - B. "明天我们公司放假,大家不用上班..." embedding 可能把 A 排在前面(都讨论天气),但 A 其实没回答用户的问题; B 字面不相关,但其实是用户该看的(他可能想问 "明天用不用上班")。 实战常见的加强: - **混合检索**:embedding(语义)+ BM25(关键词)双路召回合并 - **Reranker**:先拉 50 个,用专门的重排模型(cohere-rerank、bge-reranker) 精排到 top 5 - **HyDE**:让 LLM 先 "想象" 一个理想答案,用想象答案去检索——比直接 用问题更接近文档表述 ### 坑 4:评估超难 模型说错了,根因是什么? - 检索没拉到正确文档? - 拉到了但模型没用? - 用了但理解错了? 需要分阶段评估: - 检索准确率(top-k 里有没有正确答案) - 端到端准确率(最终回答对不对) - 引用一致性(回答有没有 hallucinate,真的在检索结果里吗) 很多生产 RAG 系统在这上面投入的精力不亚于 RAG 本身的实现。 --- ## RAG 跟 MCP 是什么关系 [MCP 101](./mcp-101.md) 讲过 MCP 是 AI 应用怎么接入外部工具的协议。 那 RAG 跟 MCP 是什么关系? **很多 MCP server 内部就是 RAG**。比如 `mcp-server-notion` 接到 query 后,在 Notion 里做向量检索,返回相关页面——这是个标准 RAG 系统,只是 包装成了 MCP 接口。 但 RAG 也可以**不通过 MCP**——直接在 agent 框架里写死,或者每次 LLM 请求前手工拼 prompt。**MCP 是分发机制,RAG 是内部实现**,两者不冲突。 更广义看,RAG 是 "模型不知道怎么办" 这个问题的多种解法之一: ``` 模型不知道怎么办? ├─ 缺事实/知识(报销额度、产品手册) → RAG(查文档库,把原文拼进 prompt) ├─ 缺实时数据(今天天气、最新 PR) → tool use(让模型自己调 API) └─ 缺通用能力(根本不会写代码) → fine-tuning(改模型本身) ``` 下一篇会讲第四类情况——**模型有工具但不知道什么时候用哪个**—— 这又是另一种 "知识" 的缺失,需要另一种解法。 --- ## 总结 RAG 的核心思路一句话:**不让模型"凭记忆"回答,让它"看着资料"回答**。 ``` [索引阶段] 文档 → 切块 → 向量化 → 存数据库 [查询阶段] 问题 → 向量化 → 找近邻 → 拼原文 → LLM 生成 ``` 它在四个维度上比其他方案漂亮: - **知识可实时更新**——改文档重新索引就行,不用重训模型 - **可解释**——回答可以标注来源 - **便宜**——只需要一次嵌入服务调用 + 一次 LLM 推理 - **可控**——你完全决定文档库的内容和检索策略 如果你做的 AI 应用要回答 "基于我们的数据" 的问题,RAG 是默认应该考虑 的方案。它不一定是最完美的,但它便宜、好懂、可演化。 --- ## 想深入了解? - 完整技术细节:[../rag-overview.md](../rag-overview.md)——包括向量数据库 选型、常见混合检索 / rerank / HyDE 策略、向量化背后的数学 - 系列前文:[function-call-101.md](./function-call-101.md)、 [mcp-101.md](./mcp-101.md) - 系列下一篇:[skills-101.md](./skills-101.md)——RAG 解决了 "知识" 的问题,Skill 解决 "什么时候用哪个工具" 的问题,会借用 RAG 类似的 思路但走出一个有意思的变体