Day 24 / 共 68 天 · 阶段 4 RAG 检索增强
向量数据库
昨天(Day 23)你会把文本变成向量、用余弦相似度找最像的。但你也发现了问题:只有 3 段文档时逐个比对还行,几百万段呢?今天学 向量数据库——专门用来存海量向量、毫秒级找出最相似的几条的基础设施。你会搞懂 FAISS / Milvus / Qdrant / pgvector 各自的定位,并亲手建一个库增删查。学完 RAG 就有了"能用"的存储层;明天(Day 25)把检索这一步完整串起来。
📍 你在阶段 4(RAG 检索增强 D21-28)的位置
D22 chunking→
D23 embedding→
D24 向量数据库→
D25 检索→
D26 rerank
💡 用一套类比兜住今天(今天全程沿用「一座超级图书馆」的世界观)
昨天你学会给每本书(文本块)贴上"意思坐标"标签(embedding)。向量数据库 = 一座专为这些坐标建的超级图书馆:建库/插入 = 把贴好标签的书上架;索引 = 图书馆的智能导览系统(不用逐本翻,直接带你到那片书架);检索 = 报出你要的"意思",管理员秒速找回最贴近的几本;元数据过滤 = "只在 2024 年、法律类里找"。今天你学会"建馆、上架、借书"。
L01
为什么普通做法不够用
🤔 痛点昨天你把向量存在一个 Python 列表里,查询时用
for 循环和每一条算相似度。3 条没问题,可公司知识库切出 200 万个 chunk 时,每次提问都要算 200 万次余弦——慢到用户等不起,内存也扛不住。💡 本质"逐个比对"叫暴力搜索,数据一多就崩。向量数据库就是为"海量向量的快速相似查找"专门造的工具——就像书多了必须建图书馆和索引,不能全堆地上一本本翻。
📝 举个例子:慢在哪
200 万个 1536 维向量,暴力搜一次要做约 200 万次向量运算,可能几秒甚至几十秒;而且这堆向量全塞进内存会占好几个 GB。向量数据库能把同样的查询压到几毫秒~几十毫秒,还能持久化到磁盘、支持增删改。差距是"能不能上线"级别的。
L02
向量数据库是干嘛的
🤔 痛点我已经有 MySQL 这种普通数据库了,为什么不能拿它存向量、查相似?非得学个新东西?
💡 本质普通数据库擅长精确匹配("找 id=5 的行""找名字叫张三的"),但不擅长"找和这个向量最接近的 10 个"。向量数据库专门优化了后者:存向量、建专用索引、按相似度快速召回 top-k。这是两类不同的活。
图注:入库(把书上架)通常一次性做好;检索(借书)是每次用户提问都发生的高频操作。
👶 存的不只是向量入库时通常一起存三样:向量(用来算相似)、原始文本(检索命中后要把这段原文喂给模型)、元数据(来源、时间、分类等,用于过滤)。三者绑在一起,检索到向量就能顺藤摸到对应原文。
L03
近似检索 ANN:快 = 允许一点点不完美
🤔 痛点它凭什么能从几百万条里毫秒找出最像的?难道不用挨个比就能知道哪个最近?
💡 本质秘诀是 ANN(近似最近邻,Approximate Nearest Neighbor):不追求"绝对精确的最像",而是用聪明的索引结构大概率快速找到很像的那些。就像找书不必翻遍全馆——导览系统先带你到对的那片书架,再在小范围里挑。用极小的精度损失,换几百上千倍的速度。
👶 小白:"近似"?那会不会漏掉真正最相关的那条,害我答错?
👨🏫 老师:理论上有极小概率,但实践中调好参数后准确率非常高,几乎不影响效果,而速度提升是决定性的——没有 ANN,大规模 RAG 根本跑不动。而且后面还有"兜底":召回时多取一些(比如 top-20)、再用更精细的 rerank(Day26)精排出最终 top-5,进一步把真正相关的顶上来。所以你尽管放心用"近似",它是工程上的最优解。
👶 名词混脸熟你会看到 HNSW、IVF 这类索引名字——它们是 ANN 的不同实现算法。现在完全不用懂原理,知道"它们是让检索变快的索引结构、建库时选一种"即可。绝大多数库都有默认值,新手直接用默认。
L04
四大选手:FAISS / Milvus / Qdrant / pgvector
🤔 痛点一搜"向量数据库"冒出来十几个名字,看得眼花。它们到底有啥区别?我该用哪个?
💡 本质它们定位不同,按"你的规模和场景"选。记住一句话:小项目先用轻量的,规模上来了再上专业的。
| 名字 | 一句话定位 | 适合谁 |
|---|---|---|
| FAISS | 一个"向量检索库"(不是服务),本地内存里超快,Facebook 开源 | 学习、单机原型、离线批量检索 |
| Qdrant | 轻量好上手的向量数据库(独立服务),带元数据过滤 | 中小项目、想要一个真·数据库又不重 |
| Milvus | 面向海量、可分布式扩展的重型向量数据库 | 亿级向量、大规模生产 |
| pgvector | 给 PostgreSQL 加向量能力的插件 | 已在用 Postgres、不想多引入新系统 |
📝 举个例子:怎么选不纠结
• 就想学 RAG / 做个 demo → 用 FAISS 或 Chroma(另一个超易上手的),几行代码本地跑。
• 做个中小型线上应用 → Qdrant,好部署、功能够。
• 公司已经重度用 PostgreSQL → pgvector,复用现有数据库最省心。
• 数据到了亿级、要高可用 → Milvus。
转行阶段:把 FAISS/Chroma 玩熟就够写进简历了,别一上来啃 Milvus。
• 做个中小型线上应用 → Qdrant,好部署、功能够。
• 公司已经重度用 PostgreSQL → pgvector,复用现有数据库最省心。
• 数据到了亿级、要高可用 → Milvus。
转行阶段:把 FAISS/Chroma 玩熟就够写进简历了,别一上来啃 Milvus。
L05
动手:建库 / 插入 / 查询
🤔 痛点说了这么多,一个向量库用起来到底几步?会不会很复杂?
💡 本质核心就三个动作:建集合(建库)→ 把"向量+文本+元数据"插进去 → 拿查询向量找 top-k。下面用最易上手的
chromadb 演示,别的库大同小异。# pip install chromadb (一个零配置、本地就能跑的向量库,最适合入门)
import chromadb
client = chromadb.Client() # 起一个本地向量库
col = client.create_collection("kb") # ① 建一个"集合"(相当于一张表/一个书架)
# ② 插入:一次给若干条,每条含 文档原文 / id / 元数据
# (这里让 Chroma 自动帮我们算 embedding;也可传入自己 D23 算好的向量)
col.add(
documents=["账户提现流程:登录进入我的钱包点提现",
"开具发票:在订单页申请电子发票",
"配送时效:普通快递 3-5 天送达"],
ids=["d1", "d2", "d3"],
metadatas=[{"cat": "账户"}, {"cat": "订单"}, {"cat": "物流"}],
)
# ③ 查询:给一句自然语言问题,要回最相似的 top-2
res = col.query(query_texts=["怎么把钱取出来"], n_results=2)
print(res["documents"]) # → [['账户提现流程……', '开具发票……']],第一条正是想要的
print(res["distances"]) # 距离越小越相似
👶 用自己的向量也行上面让 Chroma 自动算 embedding 图省事;真实项目常用 Day23 里选定的 embedding 模型自己算好,再用
col.add(embeddings=[...], documents=[...], ...) 传进去——这样能保证"建库和查询用同一个模型"(回忆 Day23 的坑)。删除/更新则用 col.delete(ids=[...]) 等,增删查一应俱全。L06
元数据过滤 & 实战注意
🤔 痛点用户问"2024 年的报销政策",你只想在"2024 + 财务类"的文档里找,别把 2019 年的翻出来——纯靠向量相似度做不到精确的"限定范围",怎么办?
💡 本质元数据过滤:入库时给每条打上标签(年份、分类、来源等),查询时加一个
where 条件,让库先按标签圈定范围、再在范围内做相似检索。相似度负责"意思像",元数据负责"符合硬条件",两者配合才精准。# 查询时加 where 过滤:只在 cat=账户 的文档里做语义检索
res = col.query(
query_texts=["怎么把钱取出来"],
n_results=2,
where={"cat": "账户"}, # 元数据过滤:先圈定"账户"类,再比相似度
)
print(res["documents"]) # 只会在"账户"类里找,物流/订单类被排除在外
📝 举个例子:实战三条注意
① 持久化:demo 里数据在内存,重启就没了;上线要用带本地/远程存储的模式,别每次重建库(重算 embedding 又慢又费钱)。
② 增量更新:文档新增/修改时,只对变动的部分重新 embedding 并 upsert,别每次全量重建。
③ top-k 取多少:太少可能漏、太多引入噪声还挤爆上下文(回忆 Day18)。常见先取 top-10~20 交给 rerank(Day26)精排。
② 增量更新:文档新增/修改时,只对变动的部分重新 embedding 并 upsert,别每次全量重建。
③ top-k 取多少:太少可能漏、太多引入噪声还挤爆上下文(回忆 Day18)。常见先取 top-10~20 交给 rerank(Day26)精排。
👶 你已经拼齐 RAG 的一半回顾一下:D21 全景 → D22 切块 → D23 变向量 → D24 存进向量库(今天)。存储这块通了,明天 D25 就把"用户问题进来 → 查库 → 拿回相关块"这条检索链路完整走一遍,你的第一个能答问的知识库就成形了。
L07
今日小结 + 动手 10 分钟
🧠 今天你应该能回答
- 为什么不能用 Python 列表 + for 循环硬扛海量向量检索?
- 向量数据库和普通数据库(如 MySQL)擅长的事有什么不同?
- ANN(近似最近邻)是什么?为什么"近似"反而是好事?
- FAISS / Qdrant / Milvus / pgvector 各适合什么场景?新手先用哪个?
- 元数据过滤解决什么问题?它和相似度检索怎么配合?
✋ 动手 10 分钟:建一个能语义问答的小知识库
在 Day11 的环境里 pip install chromadb,新建 vecdb_demo.py:
import chromadb
client = chromadb.Client()
col = client.create_collection("faq")
# 上架一批 FAQ(现实中来自 D22 切好、D23 向量化的 chunk)
col.add(
documents=[
"退款政策:签收后 7 天内可无理由申请退款",
"提现说明:余额可在钱包页申请提现,1-2 个工作日到账",
"发票开具:订单完成后在详情页申请电子发票",
"配送范围:目前仅支持中国大陆地区配送",
],
ids=["a", "b", "c", "d"],
metadatas=[{"cat":"售后"},{"cat":"账户"},{"cat":"账户"},{"cat":"物流"}],
)
# 试几个"字面不同、意思相同"的问题,看它能否找对
for q in ["怎么把钱拿回来", "我不想要了能退吗", "能寄到香港吗"]:
r = col.query(query_texts=[q], n_results=1)
print(f"问:{q}\n 最相关 → {r['documents'][0][0]}\n")
# 进阶:加元数据过滤,只在"账户"类里找
r = col.query(query_texts=["取钱"], n_results=2, where={"cat": "账户"})
print("只在账户类里找:", r["documents"][0])
观察它如何把"把钱拿回来"匹配到"提现说明"、"不想要了能退吗"匹配到"退款政策"——字面全不同,意思却对上了。
明日预告 · Day 25:存储层搭好了,明天把 检索这一步彻底讲透并端到端串起来:用户 query 也要先 embedding、用余弦相似度算分、取 top-k、再叠加元数据过滤,最后把召回的相关块拼进上下文(回忆 Day18)交给大模型作答。这一讲之后,一个"基于你自己文档的问答助手"雏形就真正跑通了。