# RAG 与向量化 本文是 RAG(Retrieval-Augmented Generation)概念笔记,重点回答两个 常被混淆的问题:**向量化的结果是什么**、**拼进 prompt 的到底是 向量还是原文**。 不像本目录其他笔记基于 OpenClaw 源码,这一篇是通用概念整理。 ## RAG 是什么 一句话:**让 LLM 在回答前先去外部知识库里捞一段相关资料,把资料 拼进 prompt,再让模型基于资料回答。** 它解决 LLM 的三个硬伤: 1. **知识截止** — 训练完就定死了,新事件不知道 2. **不知道私有数据** — 公司文档、产品手册、内部 wiki 它没见过 3. **会编(幻觉)** — 不知道的事会一本正经胡说 RAG 的思路是:别让模型 "凭记忆答",让它 "看着资料答"。 ## 跟其他方案的对比 | 方案 | 优势 | 劣势 | | ------------------------------- | ----------------------------------- | ------------------------------------ | | **RAG** | 知识可实时更新,可解释,便宜 | 检索质量决定上限,prompt 会变长 | | **Fine-tuning** | 模型"内化"知识,不用每次塞资料 | 训练贵,数据更新要重训,容易遗忘 | | **超长 context(塞全部文档)** | 不用检索,看全部信息 | 贵,"大海捞针"问题 | | **Tool use / MCP**(让模型自己调 search) | 灵活,按需查 | 多一轮往返,延迟高,不一定查到 | 实际系统经常组合:RAG 提供基础知识,fine-tuning 调风格,tool use 处理结构化查询。 ## 最小工作流 ``` 用户问:"我们公司的报销额度是多少?" ↓ [1] 检索(Retrieval) 把问题向量化,在公司文档向量库里找最相关的几段 → 拿到: "差旅报销单笔上限 2000 元,需要主管审批..." ↓ [2] 增强(Augmentation) 把检索结果拼进 prompt: "根据以下公司文档回答问题: 差旅报销单笔上限 2000 元,需要主管审批... 问题: 我们公司的报销额度是多少?" ↓ [3] 生成(Generation) LLM 基于 prompt 里给的资料生成回答 → "公司差旅报销单笔上限是 2000 元,需要主管审批。" ``` ## 典型架构 **离线索引阶段**(只做一次,数据更新时跑): ``` 文档 → 切块(chunking) → embedding → 向量数据库 ``` **在线检索阶段**(每次用户问问题时): ``` 用户问题 → embedding → 向量数据库找 top-k 相似块 → 拼 prompt → LLM ``` 常见 top-k = 3 ~ 10。常用向量数据库:Pinecone、Weaviate、Qdrant、 LanceDB、pgvector(Postgres 扩展)。 ## 向量化:这是什么、为什么需要 ### 为什么需要向量化 用户问 **"怎么退货?"**,文档库里有: ``` A. "退款政策:商品 7 天内可申请退货退款..." B. "如何使用购物车..." C. "我们的隐私条款..." ``` 朴素的关键词匹配有问题: - 用户问 "返货" 或 "怎么把货退回去" 时关键词对不上 - 文档里写 "退款政策",没出现 "退货" 原词 - 用户问 "我不想要这个东西了" 时,字面完全不含 "退货" **关键词匹配是字面的,问问题是语义的**。需要一种方法,让计算机 能算 "这两段文字意思有多接近",哪怕字面完全不同。 ### 向量是什么 向量就是 **一组数字**。比如: ``` "退货" → [0.21, -0.13, 0.88, 0.45, ..., 0.07] ← 1536 个数 ``` 这 1536 个数字不是随便定的,它们是某种 "语义坐标"。 **语义相近的词,向量在数学上也接近**: ``` "退货" → [0.21, -0.13, 0.88, ...] "退款" → [0.23, -0.10, 0.85, ...] ← 跟 "退货" 很接近 "返货" → [0.22, -0.12, 0.87, ...] ← 也接近 "购物车" → [0.91, 0.40, -0.20, ...] ← 离 "退货" 远 "隐私" → [-0.55, 0.70, -0.10, ...] ← 完全不同方向 ``` 把它们想象成 1536 维空间里的点——意思相近的词,在这个空间里挨得近。 ### "近" 怎么算 用 **余弦相似度(cosine similarity)**: ``` 两向量方向越一致 → 越相似(值接近 1) 两向量方向越垂直 → 越无关(值接近 0) 两向量方向越相反 → 越相反(值接近 -1) ``` RAG 实际就是 "把用户问题向量和每段文档向量两两算 cosine,排序, 取 top-k"。 ### 谁来 "算出" 这些向量 预训练好的 **embedding 模型**。常见: - OpenAI 的 `text-embedding-3-small` / `text-embedding-3-large` - 开源 `bge-large` / `gte-large` / `m3e` - 中文向量化常用 `bge-large-zh` / `m3e-base` - 多语言 `bge-m3` / `multilingual-e5` 训练目标大致是 "意思相近的句子输出向量接近,意思不同的句子输出向量 远"。用的时候只需要: ```python embedding = embed_model("退货") # → [0.21, -0.13, 0.88, ..., 0.07] (1536 个数) ``` 做 RAG 时把它当**黑盒**:输入文本,输出向量,语义近的向量接近。 ## 向量化的结果是什么、拼进 prompt 的是什么 **这是 RAG 最容易混淆的点。** 答案:**向量化的结果是向量(一组数字),但拼到 prompt 里的是原文, 不是向量。向量只用来"找",找到之后用的是原文。** ### 数据流 ``` [索引阶段] 原文 向量(用来查) 存储 ┌──────────────────┐ │ 块 #001: │ │ "退款政策:商品 │ ─embedder→ [0.21,-0.13,..] ─┐ │ 7 天内可申请退货"│ │ └──────────────────┘ │ ┌──────────────────┐ │ 存进数据库: │ 块 #002: │ │ 每条记录有两份 │ "购物车使用指南" │ ─embedder→ [0.91, 0.40,..] ─┤ 数据并存: └──────────────────┘ │ ① 向量(索引用) ┌──────────────────┐ │ ② 原文(召回用) │ 块 #003: │ │ │ "隐私条款..." │ ─embedder→ [-0.55,0.70,..] ─┘ └──────────────────┘ 向量数据库里实际存的: ┌─────────────────────────────────────────────────────────────┐ │ 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,..]│ "隐私条款..." │ └─────────────────────────────────────────────────────────────┘ ↑ ↑ 用来 "找" 用来 "还原" ``` ``` [查询阶段] 用户问题 ↓ "怎么退货?" ↓ ┌─embedder→ [0.20,-0.14,0.86,...] ← 问题也变成向量 │ │ 跟数据库里所有向量算 cosine 相似度 │ │ Top-1:#001 相似度 0.89 │ ↓ │ 根据 id #001 取回原文: │ "退款政策:商品 7 天内可申请退货退款..." │ ↓ └→ 把【原文】拼进 prompt: "根据以下文档回答: 退款政策:商品 7 天内可申请退货退款... 问题:怎么退货?" ↓ 发给 LLM ``` ### 关键澄清 **1. 向量只用来检索,不参与 prompt** LLM 完全看不到向量。它跟 embedding 模型是两个不同的模型: - embedding 模型:**文本 → 向量** - LLM(GPT / Claude):**文本 → 文本** 把向量塞进 LLM 的 prompt 没意义——LLM 不知道 `[0.21, -0.13, ...]` 是什么,它只懂自然语言 token。 **2. 向量数据库同时存向量和原文** 每条记录通常长这样: ```json { "id": "doc-001-chunk-3", "vector": [0.21, -0.13, 0.88, ...], // 索引结构,算相似度用 "text": "退款政策:商品 7 天内可申请退货退款...", // 原文,召回后用 "metadata": { "source": "policies/refund.md", "chunk_index": 3, "updated_at": "2026-05-12" } } ``` - `vector` 字段:用 HNSW 之类的索引结构,给 "找最近邻" 用 - `text` 字段:召回后真正塞进 prompt 的内容 - `metadata`:过滤条件(按日期、按部门筛选) **3. 向量是有损的压缩** `text-embedding-3-small` 输出 1536 维向量,1536 个浮点数 ≈ 6 KB, 跟几百字原文容量差不多——但**你没法从向量还原出原文**。 向量是 "这段文字的语义指纹",指纹用来匹配,看真实内容要回到原文。 数据库必须把原文也存下来。 ### 类比 可以把这套机制类比图书馆: - **向量** ≈ 图书索引卡的 "主题分类号",用来快速定位 - **原文** ≈ 书的内容,真正要读的东西 读者(LLM)读的是书,不是索引卡。索引卡只是帮你找到该读哪本书。 ### 完整 prompt 长什么样 实际发给 LLM 的请求大概(简化): ``` System: You are a helpful assistant. Answer based on the provided context. Context: --- [Source: policies/refund.md] 退款政策:商品 7 天内可申请退货退款,需保持原包装完好,联系客服 400-xxx 办理。 --- [Source: faq/shipping.md] 订单状态查询:登录后在"我的订单"查看,异常订单 24 小时内会有客服回访。 --- Question: 怎么退货? ``` 从头到尾全是自然语言文本。**向量已经在检索阶段完成了它的使命, 不会出现在最终 prompt 里。** ## 向量的几个有意思的性质 ### 1. 不止短词,任意长度文本 embedding 模型能把一个 **短词** 变成向量,也能把一 **整段几百字** 变成同样维度的向量。所以 "问题向量" 和 "文档向量" 可以直接比较。 ### 2. 跨语言也能用 好的多语言 embedding(`bge-m3`、`multilingual-e5`)让 "退货" 和 "return goods" 的向量接近。RAG 可以做跨语言检索:中文问题查英文文档。 ### 3. 多模态 CLIP 这类模型把 **图片和文字** 映射到同一个向量空间。可以用文字 "穿红裙子的女孩" 检索图片库——文字向量和图片向量直接算 cosine。 ### 4. 维度高就是为了 "细分" 为什么要 1536 维而不是 10 维?因为人类语言的语义维度太多——词性、 情感、领域、时态、正式程度...每一维捕捉一点点信息。低维度容易把 "无关的事压扁到同一处"。 ## RAG 的几个常见坑 ### 1. 检索失败比幻觉更糟 如果检索没拉到相关文档,模型只能瞎编。要么加 "如果资料里没有就说 不知道",要么在 retrieval 失败时直接拒绝回答。 ### 2. 切块策略影响巨大 按固定长度切会切断句子,按段落切又长度不均。常见做法: - **语义切块**:用嵌入相似度决定切点 - **重叠切块**:相邻块有 10% ~ 20% 重叠,避免边界丢上下文 - **保留层级**:把 "标题 + 段落" 一起塞进块里,保留文档结构 ### 3. embedding ≠ 相关性 embedding 抓的是 "语义相似",不一定是 "对这个问题有答案"。 例子:用户问 "明天天气怎么样",文档里有: - A. "天气预报通常由气象台发布..." - B. "明天我们公司放假..." embedding 可能把 A 排在前面(都讨论天气),但 A 没回答用户的问题; B 字面不相关但其实是用户该看的(他可能想问 "明天用不用上班")。 工程上常见加强: - **混合检索**:embedding(语义)+ BM25(关键词)双路召回合并 - **Rerank**:第一轮拉 50 个,用专门的 reranker 模型 (`cohere-rerank`、`bge-reranker`)精排到 top 5 - **HyDE**:让 LLM "假想" 一个答案,用假答案去检索——比直接用 问题更接近文档表述 ### 4. 评估很难 模型说错了,是检索没拉到?还是拉到了模型没用?还是用了但理解错了? 分阶段评估: - 检索准确率(召回的 top-k 里有没有正确答案) - 端到端准确率(最终回答对不对) - 引用一致性(回答有没有 hallucinate,引用的内容真的在检索结果里吗) ## RAG 跟 MCP 的关系 可以把 RAG 视作 **"一类常见的 MCP server"**——很多 MCP server 干的 事就是 "接到 query → 内部跑 RAG → 返回相关内容": - `mcp-server-notion`:用户问问题,server 在 Notion 里搜相关页面返回 - `mcp-server-confluence`:同上,在内部 wiki 上跑 RAG - 公司自建 MCP server:把产品文档喂进 LanceDB,模型问到产品时查它 但 RAG 也可以 **不通过 MCP**——直接在 agent 框架里写死 (LlamaIndex、LangChain 那些),或者在每次 LLM 请求前直接拼 prompt。 MCP 只是把 RAG 能力 "包装成可复用工具" 的一种方式。 详见 [mcp-overview.md](./mcp-overview.md)。 ## 总结 ``` LLM 不知道答案? ├─ 是公开知识但过时了 → RAG(查最新资料)或 web search tool ├─ 是你的私有数据 → RAG(查内部文档库) ├─ 是结构化数据(SQL/API) → tool use(直接查) ├─ 是需要算的(数学/代码) → tool use(让模型调 code interpreter) └─ 是需要模型"内化"的风格/技能 → fine-tuning ``` RAG 是最常用的一格——便宜、可解释、知识能实时更新,适合 "知识问答" 和 "基于文档回答" 这类任务。 **心智模型一句话**:向量是 "导航地图"(找到正确目的地),原文是 "目的地内容"(交给 LLM 读)。embedding 模型把文本变成向量是为了 搜索;搜索完成后,LLM 看到的永远是原文。