Agent Engineering 课程阅读
首页/课程一 · RAG 手写/向量检索:从相似到 Top-K

在 GitHub 查看原文

本页目录

Lesson 03 — 向量检索:从相似到 Top-K

本课目标:把第 1 课里 Chroma 帮你做的"检索"这一步拆开。你会先手写一个最朴素的检索,再对比 Chroma 是怎么做的,搞懂 ANN、Top-K、metadata 过滤、距离度量这些核心概念。

上一课你学会了"两个向量算相似度"。这一课的问题是:有 1 万个文档向量,怎么快速找出和问题最相似的那几个?


1. 检索的本质:算距离 + 排序

不管用什么工具,检索的内核都极其朴素:

1. 把问题也变成向量 query_vec
2. 遍历所有文档向量,算 query_vec 和每个的相似度
3. 按相似度从高到低排序
4. 取最前面的 K 个(Top-K)

这就是暴力检索(brute force)。代码不到 10 行,你可以自己写一个。

那为什么还要向量库?

暴力检索的问题在规模

文档数 暴力检索每次查询
100 条 几毫秒,毫无压力
1 万条 几十毫秒,还能接受
100 万条 几秒,用户等不及
1 亿条 直接卡死

每次查询都要遍历全部算一遍,数据量一大就扛不住。于是有了 ANN(Approximate Nearest Neighbor,近似最近邻)算法——它不追求 100% 找到最精确的答案,而是用很小的精度损失换取巨大的速度提升

💡 类比:在一本没有目录的字典里查字,你要一页页翻(暴力);如果字典有拼音索引,你直接翻到对应位置(ANN)——可能差几页,但快了几百倍。

ANN 怎么做到的(了解即可)

主流思路有几类: - HNSW(Chroma 默认):把向量组织成多层"图",查询时从顶层快速跳跃逼近,层层细化。 - IVF:先把向量空间聚类成若干"桶",查询时只搜索问题所在的几个桶。 - PQ(乘积量化):把长向量压缩成短编码,用更少内存算近似距离。

这些算法内部很复杂,但你不需要自己实现——向量库(Chroma、FAISS、Milvus 等)已经帮你封装好了。你只要知道:向量库 = 把"算距离+排序"这件事做得又快又能持久化存储


2. Top-K:取几个?大有大的问题

检索出来一堆按相似度排好序的文档,取前 K 个喂给大模型。这个 K 怎么选?

K 太小 K 太大
可能漏掉相关文档 混入大量无关内容(噪声)
答案信息不全 干扰大模型,可能答错
消耗更多 token(更贵、更慢)

经验值:通常 K 在 3 ~ 5 之间起步,再根据效果调整。关键不是"越多越好",而是"找到的都得相关"——这正是下一课(chunking)和第 6 课(rerank)要解决的。

🎯 认知:检索质量 = 召回率(该找的有没有找到)× 精确率(找到的有没有掺水)。Top-K 大能提升召回但拉低精确。


3. metadata 过滤:先筛后检

光靠向量相似度有时不够。比如你只想查"2024 年的报销制度",但向量检索可能捞出 2022 年的旧制度。

向量库支持给每条文档打元数据(metadata)——就是一组键值对标签:

collection.add(
    documents=["餐饮报销上限80元"],
    embeddings=[...],
    metadatas=[{"category": "报销", "year": 2024}],  # ← 标签
    ids=["doc_0"],
)

查询时可以加过滤条件:

collection.query(
    query_embeddings=[...],
    n_results=3,
    where={"category": "报销"},  # ← 只在报销类里检索
)

这就是 "先按标签筛选,再在子集里做向量检索"。生产环境几乎必用——能大幅提升精确度、还能控制权限(比如只查自己部门的文档)。


4. 距离度量:余弦 / 欧氏 / 点积

向量库算"相似度"时,可以选不同的度量方式。Chroma 支持三种:

度量 算什么 Chroma 里叫 适用
余弦相似度 方向夹角 cosine RAG 最常用,忽略向量长度只看语义方向
欧氏距离 两点直线距离 l2 关心绝对位置时,数值越小越相似
点积 不归一化的余弦 ip (inner product) 向量已归一化时等价于余弦

为什么 RAG 默认用余弦? 因为不同文本 embedding 出来的向量长度可能不同(长句向量"模长"可能更大),但我们只关心"语义方向是否一致",不关心长度。余弦正好把长度归一化掉。

⚠️ 注意一个反直觉点:欧氏距离和点积是"越小越相似"或"越大越相似"方向相反。Chroma 里 l2 返回的 distances 越小越相似,而 cosine 也是返回距离(1 - 余弦),越小越相似。看结果时别搞反。


5. 本课代码会做什么

code.py 会做四件事,让你把检索彻底搞懂:

① 暴力检索 vs Chroma 检索

用同一批文档和同一个问题,分别用"手写遍历"和"Chroma"检索,对比结果和速度。你会发现小数据量下结果几乎一样,但 Chroma 更省事。

② Top-K 实验

调整 K 值(1/3/5),看检索回来的内容怎么变化,体会"K 大了会混入噪声"。

③ metadata 过滤

给文档打上 category 标签,演示"只在指定类别里检索"。

④ 距离度量对比

分别用 cosine 和 l2 检索同一个问题,看排序结果有没有差异。


6. 跑起来

python lessons/03_retrieval/code.py

终端会打印每一步的对比结果。重点观察: - 暴力检索和 Chroma 的结果是否一致(验证它们做的是同一件事) - K 变大时,排后面的文档是不是明显没那么相关 - metadata 过滤后,结果是不是全都是指定类别


下一课 Lesson 04 — 文档切块 会讲:文档怎么切才合理,切得好检索才准。