Day 11 / 共 20 天 · 第 3 周 检索与查询
检索器 Retriever(流水线第 ⑤ 步)
前两周建好了库(离线阶段完)。第 3 周进入"在线查询"——用户提问后发生什么。第一步就是检索器:给一个问题,找出最相关的几个 Node。
📍 你在整条链的位置(离线建库已完成 ✅,进入在线查询)
离线建库 ✅→
⑤ 检索 Retriever→
后处理 D14→
⑥ 合成答案 D13
L01
进入在线查询
💡 在线查询链:找 → 精排 → 生成
问题 →(Retriever 检索,今天)→(Postprocessor 精排,Day14)→(Synthesizer 合成答案,Day13),QueryEngine(Day12)把它们串起来。
🤔 检索器只干一件事
用户问"公司的退款政策是什么?"——检索器负责从知识库找出最相关的几个 Node("退款条款"那几段)。它只"找",不生成答案(生成是后面 Synthesizer 的事)。返回
List[NodeWithScore](Node + 相关度,Day 02)。检索质量 = 答案质量的天花板
找错了内容,后面 LLM 再强也答不对(它只能基于给它的内容答)。所以检索是 RAG 最关键的环节之一。
L02
BaseRetriever:模板方法
base/base_retriever.py:34:class BaseRetriever(PromptMixin, ...)。对外 retrieve(query)(:192),子类实现 _retrieve(query_bundle)(:263,抽象方法)。
💡 "模板方法":基类定流程,子类填核心
基类
retrieve 定义骨架(转换查询、埋点、递归处理),子类只填 _retrieve(真正检索)。VectorIndexRetriever 的 _retrieve = "嵌入查询 + 向量库 query"(L04);BM25Retriever 的 _retrieve = 关键词匹配。骨架共享、核心各异。L03
retrieve 骨架都干了啥
base/base_retriever.py:192-227:
def retrieve(self, str_or_query_bundle) -> List[NodeWithScore]:
dispatcher.event(RetrievalStartEvent(...)) # 埋点(Day19 可观测)
if isinstance(str_or_query_bundle, str):
query_bundle = QueryBundle(str_or_query_bundle) # 字符串 → QueryBundle(L5)
else: query_bundle = str_or_query_bundle
with self.callback_manager.event(CBEventType.RETRIEVE, ...):
nodes = self._retrieve(query_bundle) # ★ 子类的真正检索
nodes = self._handle_recursive_retrieval(query_bundle, nodes) # 递归(L6)
dispatcher.event(RetrievalEndEvent(..., nodes=nodes))
return nodes
读法:基类包办了"包装查询、发事件(追踪)、递归展开"这些通用逻辑,子类只写那一句
_retrieve。通用逻辑写一次,所有检索器共享。还有异步版 aretrieve。L04
向量检索器 = 前两周的总装
VectorIndexRetriever._retrieve(简化):
向量检索一步步串起:嵌入查询(D6) → 向量库找 top_k(D9) → docstore 取文本(D8) → 包成 NodeWithScore(D2)。
这就是"语义检索"的完整落地
前两周学的零件(嵌入/向量库/docstore/NodeWithScore)在这里全串起来了。Day 07 的
index.as_retriever(similarity_top_k=5) 造的就是它。L05
QueryBundle:查询不只是字符串
schema.py:1449 的 QueryBundle:封装一次查询——query_str(原问题)、embedding(查询向量)、custom_embedding_strs(用于嵌入的替代文本)。
📝 高级技巧 HyDE(假设文档嵌入)
问题很短、和文档风格不同,检索不准。HyDE 技巧:先让 LLM 根据问题"编一个假想答案",用假想答案的向量去检索(假想答案更像真文档,检索更准)。
这时
这时
query_str=原问题、custom_embedding_strs=假想答案。QueryBundle 让这类查询变换成为可能——不只是"把问题原样嵌入"。L06
递归检索:检索到"引用"就跟进
💡 命中 IndexNode(指向别处的节点)就递归进去
_handle_recursive_retrieval:如果检索到的是 IndexNode(Day 02,指向另一个索引/检索器),就"递归"继续检索它指向的目标。读法:这支撑 Day 10 的"两级检索"——DocumentSummaryIndex:先检索到"摘要 IndexNode",递归进对应文档内继续检索。让复杂的分层检索能自动展开,你无感。
L07
各种检索器:策略是调优重头
- VectorIndexRetriever:向量检索(最常用)。
- BM25Retriever:关键词检索(integration)。
- QueryFusionRetriever:融合多个检索器(向量+关键词=混合检索)。
- AutoMergingRetriever:命中小块自动合并成父块(配 Day 4 的 Hierarchical 切块)。
混合检索通常最稳
纯向量检索有时不够——混合检索(QueryFusion 融合向量+BM25)兼顾"语义"和"精确词",通常更稳。它们都是 BaseRetriever,接口统一、可组合。"检索什么内容"比"用什么 LLM"更影响 RAG 效果——检索策略值得多实验。
L08
今日小结 + 动手
🧠 今天你应该能回答
- 检索器的职责?返回什么(NodeWithScore)?为什么它是质量天花板?
retrieve(模板)和_retrieve(子类)怎么分工?- 向量检索器怎么把前两周的零件串起来?
- QueryBundle 为什么不只是字符串(HyDE)?递归检索干什么?
✋ 动手
cd /Users/bitmart/work/codes/github/llama_index/llama-index-core/llama_index/core
sed -n '192,265p' base/base_retriever.py
ls indices/vector_store/retrievers/
明天预告 · Day 12:检索器只"找",QueryEngine 把"找 + 精排 + 生成答案"串成端到端问答——这就是 Day 01 第四行
query() 的真相。