Day 09 / 共 20 天 · 第 2 周 索引/嵌入/存储
向量存储 VectorStore(检索心脏)
向量库是 RAG 检索的引擎。今天精读 SimpleVectorStore:向量怎么存、"给一个问题向量、找最相似的 k 个 Node"怎么算——这就是 Day 06 那张"向量空间"图在代码里的实现。
📍 你在整条链的位置
③ 嵌→
④ 建索引→
向量库(存 + 检索)→
⑤ 检索器 D11
L01
向量库只干两件事
💡 add(存)+ query(找最近邻)
vector_stores/types.py 的 BasePydanticVectorStore:add(nodes)——把 Node 的向量存起来;query(VectorStoreQuery)——给查询向量,返回"最相似的 k 个 Node id + 分数"。就这两件核心事(外加 delete)。"找最近邻"= Day 06 那张图的兑现
Day 06 说"意思近→向量近"。向量库的 query 就是"给一个点,找它周围最近的几个点"——找到语义最相关的 Node。SimpleVectorStore 用暴力法(挨个算),生产用 Chroma/Qdrant(HNSW 等算法加速)。
L02
SimpleVectorStore:几个字典
vector_stores/simple.py:64,数据存在 SimpleVectorStoreData(:48):
class SimpleVectorStoreData:
embedding_dict: Dict[str, List[float]] # node_id → 向量
metadata_dict: Dict[str, Any] # node_id → metadata(供过滤 L06)
text_id_to_ref_doc_id: Dict[str, str]
读法:就是几个字典——
embedding_dict(每个 Node 的向量)+ metadata_dict(元数据)。内存里的字典,简单直接,小规模够用。persist 时序列化成 JSON。L03
add:把向量塞进字典
simple.py:174 的 add(nodes):遍历 Node,把 node.embedding 存进 embedding_dict[node.id_]、metadata 存进 metadata_dict。
读法:回收 Day 07——VectorStoreIndex 的
_add_nodes_to_index 就是调这个 add。Node 必须先有 embedding(Day 06 嵌过)才能 add。专业向量库的 add 会把向量写进它的索引结构(HNSW 图),SimpleVectorStore 只是塞字典。L04
query:三步找出 top-k
simple.py:244-305:
query 三步:先按 metadata 缩小候选 → 对候选算相似度 → 取最高的 top_k。
SimpleVectorStore 是"暴力法"
它遍历所有向量挨个算相似度(N 个就算 N 次)。小数据没问题,百万级才需要专业库的近似最近邻算法(HNSW)。
VectorStoreQuery 装了查询向量、top_k、filters、mode;返回 VectorStoreQueryResult(node_ids + 分数)。L05
top_k 怎么定
get_top_k_embeddings(Day 06 提过):算相似度、排序、取最高的 similarity_top_k 个。
💡 top_k 是 RAG 调优的常见旋钮
就是 Day 07
as_retriever(similarity_top_k=5) 传的——检索返回几个 Node。取太少(如 2)可能漏掉相关内容;取太多(如 20)引入噪声、塞爆 LLM 上下文、还费钱。默认 2,实际按你的场景调(通常 3~10)。L06
metadata 过滤:先缩范围再检索
🤔 纯语义检索有时不够精准
你想"只在财务类、2024 年的文档里找报销政策"——但语义检索可能翻出其他年份的类似内容。
💡 build_metadata_filter_fn:先按结构化条件过滤
simple.py:262:按 query.filters(如 {"year": 2024, "category": "finance"})先过滤 Node(用 Day 02/03 存的 metadata),再在符合条件的子集里做语义检索。📝 混合检索的威力
→ 又准(结构化过滤)又懂语义(向量检索)。也用于权限控制(只检索该用户能看的文档)。
只检索 metadata.department=="财务" 的 Node 里,和"报销"最相似的 3 个→ 又准(结构化过滤)又懂语义(向量检索)。也用于权限控制(只检索该用户能看的文档)。
读法:注意 SimpleVectorStore 若 persist 时没存 metadata,过滤会报错(
:255-259)——所以 metadata 要一路带好(呼应 Day 02/03)。L07
MMR:别检索出一堆重复
🤔 top-k 可能返回 5 个几乎一样的段落
如果文档里"报销上限"被反复提到,纯 top-k 可能返回 5 个都在讲上限的块——浪费上下文、答案片面。
💡 MMR:相关 + 彼此不同
simple.py:295 的 get_top_k_mmr_embeddings(Maximal Marginal Relevance):选相关的,但也避免选和已选内容太像的,用 mmr_threshold 平衡"相关性"和"多样性"。让 5 个 Node 覆盖更全面
MMR 像"选水果拼盘"——不能全选苹果(重复),要苹果+橙子+葡萄(多样但都好吃)。这样 5 个 Node 覆盖不同角度,答案更全。还有 LEARNER 模式(
get_top_k_embeddings_learner,学习式检索)。这些高级模式让 RAG 结果更好。L08
今日小结 + 动手
🧠 今天你应该能回答
- 向量库的两个核心职责?"最近邻检索"是什么?
- query 的三步(过滤→算相似度→取 top-k)?
- top_k 取太少/太多分别有什么问题?
- metadata 过滤和 MMR 各解决什么问题?
✋ 动手
cd /Users/bitmart/work/codes/github/llama_index/llama-index-core/llama_index/core
sed -n '48,90p' vector_stores/simple.py
sed -n '244,305p' vector_stores/simple.py
明天预告 · Day 10(第 2 周收官):VectorStoreIndex 最常用,但不是唯一——其他索引类型:SummaryIndex(全遍历)、KeywordTable(关键词)、DocumentSummary(两级)、PropertyGraph(GraphRAG)。看它们各适合什么场景。