RAG 与向量数据库 · A 直觉
0 / 7 章 ← 首页
贯穿案例 · 一家公司的客服知识库 序幕 · 十篇文档的原型 第二幕 · 答得不准,调质量 第三幕 · 一千万篇,扛规模 第四幕 · 上线,选型与实战 第五幕 · 活下去,更新与治理
01

RAG 在解决什么 —— 把闭卷考试改成开卷

先立一个贯穿五页的场景:你要给公司做客服机器人,手里只有一份内部手册。直接问大模型,它会一本正经地编;让它先翻手册再回答,答案就有了出处。这个「先翻书」的动作,就是 RAG。

RAG 全称 Retrieval-Augmented Generation(检索增强生成),出自 Lewis 等人 2020 年的论文。名字里三个词对应三步:检索出相关资料,把资料增强进提示词,让模型生成答案。模型本身不变,变的是它每次答题时桌上摊开的资料。

痛点 01知识会过期

模型的知识停在训练截止日。上周改的退款政策,它不可能知道。

痛点 02私有数据进不去

你公司的手册、工单、合同,从来没出现在训练数据里,也不该出现。

痛点 03答案没出处

模型编错了你也看不出来。带上检索到的原文,答案才能被核对。

LAB 1.1 · 闭卷 vs 开卷同一个问题,两种答法

闭卷 · 只靠模型记忆

开卷 · 先检索再回答

闭卷的答案是示意:模型会给出一个「听起来合理」的通用说法。开卷的答案来自本页 10 篇示例文档,真实检索的结果。
2026 年的定位变化RAG 仍是让模型用上外部知识的主流做法,但它不再是「每次问答前固定执行的一步」。越来越多的系统把检索做成 Agent 可以按需调用的工具,由模型自己决定查不查、查几次、换什么词再查。这个变化在 D 页展开,A、B、C 三页先把检索本身讲透。
✦ 本章通关条件
✓
能用一句话讲清 RAG 的三步,以及它解决的三个问题
查看参考答案
大白话

模型是个记忆力很好但不看最新资料的学生。RAG 就是考试时允许他翻书:先去书架找到相关的几页,摊在桌上,再作答。

专业版

检索(从外部语料召回相关片段)→ 增强(把片段拼进 prompt)→ 生成(模型基于上下文作答)。它解决知识时效、私有数据接入、答案可溯源三件事;模型权重不变。

02

最小流水线 —— 五步走完一次问答

RAG 听起来玄,拆开只有五步。先别管用什么模型、什么数据库,看一遍数据在每一步长什么样。

前三步是离线的(建库,做一次):把手册切成块,给每块算出向量,存进库。后两步是在线的(每次提问都跑):把问题也变成向量,找最近的几块,拼进提示词交给模型。

LAB 2.1 · 流水线步进器点步骤,或用下方按钮逐步走
这里的「向量」是 2D 玩具版(下一章解释)。真实系统里每块文字对应 768~4096 个浮点数,其余流程一模一样。

把它写成代码,只要 25 行

minimal_rag.py(伪代码骨架,embed / llm 换成任意厂商)
import numpy as np

# —— 离线:建库 ——
chunks = split(open("手册.md").read())          # 1 切块
vectors = np.array([embed(c) for c in chunks])   # 2 嵌入:文字 → 向量
vectors /= np.linalg.norm(vectors, axis=1, keepdims=True)  # 单位化,点积 = 余弦
# 3 入库:这里直接放内存;规模大了换向量数据库(C、D 页)

# —— 在线:每次提问 ——
def answer(question, k=3):
    q = embed(question); q /= np.linalg.norm(q)
    top = np.argsort(-(vectors @ q))[:k]                # 4 检索:最相似的 k 块
    context = "\n".join(f"[{i}] {chunks[i]}" for i in top)
    prompt = f"只根据【资料】回答,没有就说不知道,并标注编号。\n【资料】\n{context}\n【问题】{question}"
    return llm(prompt)                                # 5 拼装 + 生成

这 25 行就是一个能跑的 RAG。后面三页做的事,都是在替换其中某一行:把第 1 行换成更聪明的切法(B 页),把第 4 行换成更准的检索(B 页),把 `vectors @ q` 换成撑得住一千万条的索引(C 页)。

✦ 本章通关条件
✓
能区分哪三步是离线、哪两步是在线,并指出「向量数据库」替换的是哪一行
查看参考答案
大白话

建图书馆是一次性的:切书、给每页编目、上架。读者来借书是每次都发生的:问题变成目录号、找最近的几页、交给模型。向量数据库替换的是「上架和找书」那一段,数据少的时候一个数组就够了。

专业版

离线:切块、嵌入、入库。在线:查询嵌入、近邻检索、提示词拼装与生成。向量数据库替换的是「存向量 + 近邻检索」,即 vectors @ q 与 argsort 这一步。

03

向量是什么 —— 用方向表示意思

「嵌入」把一段文字变成一串数字,规则只有一条:意思相近,方向相近。检索就变成了几何问题:找和问题夹角最小的那几个点。

想象一张只有两个维度的地图。「无线网络连接失败」和「wifi 断了」被放在地图的同一个角落,「申请退款」远远躺在另一边。真实模型的地图有上千个维度,人眼看不了,但「近就是像」这个规则完全一样。

衡量「近」最常用的是余弦相似度:只看两个向量的夹角,不看长度。夹角 0° 余弦为 1,垂直为 0,反向为 −1。下面这个圆盘里,每篇文档是圆周上的一个点,你输入的问题是一根箭头,扇形范围内的点就是被检索到的。

LAB 3.1 · 余弦圆盘玩具词向量:每个词在圆上有个角度,文本向量 = 命中词的向量之和
灰色扇形 = 余弦 > 0.6 的区域(半角 ≈ 53°)。试试输入 E1042:它不在词表里,向量看不见它。这是向量检索的真实弱点,下一章专门讲。

三种「距离」,其实是一回事

度量看什么什么时候用
余弦相似度夹角,忽略长度默认选择,文本嵌入几乎都用它
点积 (IP)夹角 × 两个长度向量已单位化时和余弦完全等价,计算更快
欧氏距离 (L2)两点的直线距离单位向量上,‖a−b‖² = 2 − 2·cos,排序和余弦一致

实操结论:嵌入模型的输出多数已经单位化,选余弦或点积都行,关键是建索引和查询时用同一种度量,选错了不会报错,只会悄悄变差。

✦ 本章通关条件
✓
能解释余弦相似度看的是什么,以及为什么单位向量上余弦、点积、L2 排序一致
查看参考答案
大白话

余弦只问「你们朝向是不是一致」,不管谁更长。所有向量都被压成一样长之后,朝向越一致,直线距离也越近,三种量法给出的名次就一样了。

专业版

cos = a·b / (‖a‖‖b‖)。当 ‖a‖=‖b‖=1,a·b = cos,且 ‖a−b‖² = 2−2cos,三者对相同候选集的排序单调一致。

04

向量会翻车 —— 为什么还需要关键词

既然向量懂「意思」,是不是可以把传统的关键词搜索扔了?看一场对撞:同一批文档、同一个问题,分别交给 BM25 和向量,再看合并后的结果。

BM25 是关键词检索的标准算法:一个词在这篇文档里出现得越多、在整个库里越稀有,这篇文档的分就越高。它只认字面,所以「上不了网」搜不到「无线网络连接失败」。向量认意思,所以能搜到;但它对错误码、型号、人名这类「字面就是全部意义」的词很迟钝,因为这些词没有「语义邻居」。

LAB 4.1 · BM25 × 向量 × 混合点预设,或自己输入
混合列用的是 RRF(倒数排名融合):每路按名次打分 1/(k+名次),两路都靠前的文档得分最高。公式和调参在 B 页第 3 章。

什么时候谁赢

查询类型BM25向量例子
同义改写 / 口语漏准「上不了网」→「无线网络连接失败」
精确标识符准漏或乱错误码、SKU、函数名、人名
专业缩写准看模型领域术语,通用嵌入模型没见过
长句描述意图弱准「用户反馈付款后一直转圈」

所以生产系统的默认配置是两路都跑,再融合。这不是折中,是互补:每一路覆盖对方的盲区。真实的嵌入模型对 E1042 这类词也会给出一个模糊向量,只是没法保证精确命中,本页的玩具模型把这个弱点放大了,方便你看清它。

✦ 本章通关条件
✓
能各举一个只有 BM25 能搜到、只有向量能搜到的查询,并说明混合检索为什么是默认选择
查看参考答案
大白话

BM25 是查字典,字对得上才找得到;向量是问懂行的同事,意思对得上就能找到。查错误码要靠字典,描述症状要靠同事,所以两个都问,再综合。

专业版

只有 BM25:E1042(精确标识符)。只有向量:上不了网怎么办(词表无交集的同义改写)。两路的失败模式正交,融合后召回覆盖更广;RRF 只依赖名次、不依赖分数量纲,因此无需校准两种分数。

05

要不要 RAG —— 与长上下文、微调、grep 的取舍

「上下文窗口都 100 万 token 了,还建什么检索?」——这一章不算成本账,只回答一个更基础的问题:不用 RAG,还有哪几条路,每条路走到哪里会断。

不给模型外挂检索,主要有三条路:长上下文把资料整份塞进提示词;微调把知识烧进权重;grep + Agent 不建任何索引,把搜索做成工具、让模型自己翻文件。前两条讲清边界就行;第三条是编程 Agent 已经在生产环境走通的路,本章重点拆它:一条朴素的关键词搜索,是怎么被做高效的。

路 01长上下文 · 整份塞进去

零基建,还天然适合全局推理(找条款矛盾、通篇总结)。适合语料小、查询不频繁的场景。断在:语料超窗口装不下;塞得越满注意力越摊薄,细节召回会掉;查询一多,每次全量输入最贵。

路 02微调 · 烧进权重

它回答「怎么说」,不回答「说什么」:改语气、输出格式、领域解题套路很灵。拿它装事实,则内容一变就得重训、答案没有出处、还可能学成「自信地背错」。定位是检索的搭档,不是替代。

路 03grep + Agent · 模型自己翻

不建索引,把 grep / glob / read 打磨成工具,模型多轮搜索直到找到。代码、日志、配置这类词汇确定的内容上是编程 Agent 的默认做法。断在:口语化语料要靠模型多轮换词,轮次和延迟都上去了。

grep + Agent 具体怎么做:看一个搜索循环

这条路的日常实践者是编程 Agent:Claude Code、Codex CLI 天天在几百万行的仓库里定位代码,靠的不是向量库,是关键词搜索加多轮循环。整套做法只围绕一条约束展开:工具的每行输出都会进入模型上下文——所以一切都是「少返回、返回准、搜不到就换词再来」。

LAB 5.1 · Agent 的 grep 搜索循环三种问题,同一条循环
    命令为 ripgrep(rg)语法,语料就是本页那 10 篇文档(按主题归进 5 个文件)。真实 Agent 的循环可能十几轮,但骨架就是这几步。

    向 Harness Agent 抄作业:高效 grep 的三层功夫

    同一条 grep 命令,在终端里和在 Agent 手里是两个工具。编程 Agent(这类承载模型的框架常称作 harness)把它做高效,靠三层叠加:

    层做法为什么
    引擎GNU grep 换成 ripgrep(rg)Rust 多线程并行;默认遵守 .gitignore、跳过隐藏文件与二进制文件。GB 级仓库上比 grep 快一个量级——搜索足够便宜,「让模型多搜几轮」才划得来
    工具输出模式分三挡:files_with_matches(只回文件名)/ content(带行号的内容)/ count(数命中探规模);head_limit 截断条数;glob / type 先把范围缩到某类文件工具输出原样进入上下文窗口。原则:宁可让模型再搜一次,也别把几万行倒进窗口;行号让下一步 Read 能精确到行段,而不是整文件读入
    策略先 -l 广搜拿文件名 → 挑相关文件用 -n 拿行号 → Read 行段;搜不到就换词:口语换正式、缩写换全称,alternation 一次带上几个候选(退款|退回|refund)每一轮都是一次模型调用,轮次就是成本。同义改写这件事,向量一次嵌入就覆盖了,这里只能靠模型的常识去猜

    谁在这么做,做法各有取舍:

    Agent路线关键细节
    Claude Code内置 Grep 工具,底层 ripgrep默认 files_with_matches 只回文件名、head_limit 截断、glob / type 缩范围;使用建议就是「先广搜定位文件,再用 content 模式缩到行」
    Codex CLI不做专门工具,给沙箱 shell模型像人一样直接敲 rg,与 jq、gh 等 CLI 组合——搜索能力来自「会写命令」,而不是「有专用工具」
    Cline / Roosearch_files 工具内置 ripgrep 二进制,免去系统依赖;正则命中返回文件与行号,配合按行段读取
    Aider更激进:常驻「仓库地图」tree-sitter 抽出函数 / 类定义,按引用关系跑 PageRank 排重要性,再按约 1k token 的预算裁成一页 repo map 放进上下文

    Aider 的 repo map 值得多看一眼,因为它其实是第四种折中:把检索离线化,只是索引不是向量,是符号图。模型看一眼这页「目录」就知道该打开哪个文件,很多「东西在哪」的问题根本不用发起搜索。对应到文档库,类比做法是维护一页常驻的目录或 FAQ 索引——本质都是用一页上下文换掉一轮搜索。

    grep 路的边界它在代码、日志上好用的根本原因,是这类文本的字面就是意义:函数名、错误码没有同义改写,关键词即语义(第 4 章的对撞实验可以重看一遍)。换成口语化的自然语言语料,Agent 的换词轮次会明显变多;多轮交互的延迟也天然高于单次向量检索。另外,「编程 Agent 普遍不用向量库」的说法部分出自对泄露源码的博客解读,当作一种已被验证的做法即可,不是定论。
    ✦ 本章通关条件
    ✓
    能说出不用 RAG 的三条路各自适合什么,并讲清 grep + Agent 的循环为什么要「先广后窄、搜不到就换词」
    查看参考答案
    大白话

    资料小就整本塞给模型;想改的是说话方式就微调;资料是代码、日志,就让模型自己拿关键词翻——先看哪些文件对得上,再进文件找行,找不到就换个词再翻,翻到了才带着出处回答。

    专业版

    长上下文适合小语料、低频查询与全局推理,受限于窗口大小、注意力稀释与全量计费;微调改变的是行为分布,不适合承载易变事实,也无从溯源。grep + Agent 以 ripgrep 为引擎,靠输出模式(files_with_matches / content / count + head_limit)控制上下文开销,策略上先广后窄、换词重试,适合词汇确定的语料,代价是轮次与延迟;Aider 式 repo map 用符号图把检索离线化,是向量之外的另一种索引。

    06

    终极测验 · 5 题验收

    答对 4 题以上,才算把 RAG 的直觉带走。

    ✦ 本章通关条件
    ✓
    终极测验 5 题答对 ≥ 4(答完自动勾选)
    07

    参考资料 · References

    本页观点与数据的出处。博客类来源标为「观点」,数字请按自己的数据重新量。

    原理出处

    • Lewis et al., Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks(2020,RAG 原论文)· arxiv.org/abs/2005.11401

    2026 年的资料

    • RAG Techniques Compared: A Practical Guide in 2026(RAG 仍是主流,模式分化)· blog.starmorph.com
    • Engineering the RAG Stack: Architecture and Trust Frameworks(2026-01 综述)· arxiv.org/pdf/2601.05264
    • RAG vs. Long Context 2026: What the Data Really Says(成本与延迟估算,博客观点)· dev.to
    • Is RAG Dead? Long Context, Grep, and the End of the Mandatory Vector DB(2026-04,博客观点,含对 Claude Code 实现的解读)· akitaonrails.com

    grep + Agent 的实现

    • Claude Code 内置工具梳理:Grep / Glob 的输出模式与参数(第三方整理)· blog.thepete.net
    • aider · Building a better repository map with tree-sitter(repo map:符号抽取 + PageRank + token 预算)· aider.chat/docs/repomap.html
    • BurntSushi/ripgrep(rg:多线程并行、遵守 .gitignore、跳过二进制与隐藏文件)· github.com/BurntSushi/ripgrep
    • OpenAI Codex CLI(沙箱 shell 中直接使用 rg 的终端 Agent)· github.com/openai/codex

    本页的玩具模型

    BM25 使用标准公式(k1=1.2,b=0.75),分词为 ASCII 单词 + 中文二元组。「向量」是词向量求和的教学模型,不代表任何真实嵌入模型的行为;公式与数据见同目录 lib.js。

    下一页 · B 检索质量
    怎么切块不会切断答案 · 嵌入模型怎么选 · RRF 融合调参 · 重排 · 用指标判断「到底准不准」
    →