Day 09 / 共 20 天 · 第 2 周 索引/嵌入/存储

向量存储 VectorStore(检索心脏)

向量库是 RAG 检索的引擎。今天精读 SimpleVectorStore:向量怎么存、"给一个问题向量、找最相似的 k 个 Node"怎么算——这就是 Day 06 那张"向量空间"图在代码里的实现。

📍 你在整条链的位置
③ 嵌 ④ 建索引 向量库(存 + 检索) ⑤ 检索器 D11
L01

向量库只干两件事

💡 add(存)+ query(找最近邻) vector_stores/types.pyBasePydanticVectorStoreadd(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:174add(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_embedding ① 过滤 按 metadata/id 缩小候选如"只在2024年文档" ② 算相似度 对每个候选算 cosineDay6 的余弦相似度 ③ 取最高 排序取 top_k默认 k=2 → 返回 VectorStoreQueryResult:node_ids + 相似度分数 [ (Node2, 0.89), (Node7, 0.81), (Node3, 0.74) ] ← 后面包成 NodeWithScore(D2)
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:295get_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)。看它们各适合什么场景。
← Day 08 Day 10 · 其他索引 →