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。这是两类不同的活。
向量数据库的两件事:入库(写) & 检索(读) 文本块+向量+元数据 (来自 D22/D23) 向量数据库 存 + 建索引 最相似的 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 → 用 FAISSChroma(另一个超易上手的),几行代码本地跑。
• 做个中小型线上应用 → 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)精排。
👶 你已经拼齐 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)交给大模型作答。这一讲之后,一个"基于你自己文档的问答助手"雏形就真正跑通了。
← Day 23 · embedding 向量化 Day 25 · 检索 →