Day 12 / 共 20 天 · 第 3 周 检索与查询
查询引擎 QueryEngine(端到端问答)
检索器只"找内容",查询引擎把"找 + 精排 + 生成答案"串成端到端问答。今天精读 RetrieverQueryEngine——就是 Day 01 第四行 query_engine.query(...) 的真相。
📍 你在整条链的位置
⑤ 检索 D11→
QueryEngine 组装端到端→
合成 D13→
后处理 D14
L01
QueryEngine 定位
🤔 检索器只返回一堆相关 Node,用户要的是一句答案
检索器给你 5 个相关段落,但用户想要"根据《财务制度》第3条,报销上限3000元"这样一句话。中间那步"把段落变成答案"谁来做?
💡 QueryEngine = 检索器 + 合成器的组装
query_engine/retriever_query_engine.py:25 的 RetrieverQueryEngine——在检索之上加"用 LLM 把 Node 合成答案",是"问答"的完整封装。输入问题字符串,输出 Response(答案 + 引用的源 Node)。Day 07 index.as_query_engine() 造的就是它。L02
三个可插拔零件
retriever_query_engine.py:39-52 构造:
def __init__(self, retriever, response_synthesizer=None, node_postprocessors=None, ...):
self._retriever = retriever # ① 检索器(Day11):找
self._node_postprocessors = node_postprocessors or [] # ② 后处理(Day14):精排/过滤
self._response_synthesizer = response_synthesizer or get_response_synthesizer(...) # ③ 合成器(Day13):生成
💡 三个零件 = 第 3 周三天
retriever(找,D11)+ node_postprocessors(筛/重排,D14)+ response_synthesizer(合成,D13)。QueryEngine 是它们的"组装厂"——把三个零件拼成能问答的整体。
L03
_query:核心就两步
retriever_query_engine.py:202-210:
def _query(self, query_bundle) -> RESPONSE_TYPE:
nodes = self.retrieve(query_bundle) # ① 检索(含后处理)
response = self._response_synthesizer.synthesize(query=query_bundle, nodes=nodes) # ② 合成
return response
整个 RAG 的"检索增强生成"就浓缩在这两行:retrieve(找)+ synthesize(生成)。
RAG 的心脏就这两行
Retrieval(检索)+ Generation(生成)——RAG 三个字母的含义,就在
_query 这两行里。简洁得惊人,因为复杂性都封在 retriever 和 synthesizer 里。Day 01 的 query() 最终跑的就是这里。L04
retrieve + 后处理
retriever_query_engine.py:160-166:
def retrieve(self, query_bundle) -> List[NodeWithScore]:
nodes = self._retriever.retrieve(query_bundle) # 检索器找(Day11)
return self._apply_node_postprocessors(nodes, query_bundle=query_bundle) # 后处理(Day14)
读法:QueryEngine 的 retrieve = 检索 + 依次应用后处理器。后处理(Day14)对检索结果重排、过滤、去噪——"只留 score>0.7 的""用 rerank 重新排序"。处理完才交给合成器。
L05
synthesize:交给合成器
retriever_query_engine.py:178-188:委托给 response_synthesizer.synthesize(query, nodes)(Day 13 精读)。
💡 合成 = 把相关 Node + 问题喂 LLM 生成答案
但片段太多塞不下上下文怎么办?要综合多个片段呢?——这些由 Synthesizer 的不同模式(refine/tree_summarize,Day 13)处理。QueryEngine 只管"把 Node 交给 Synthesizer",怎么合成是 Day 13 的事。
L06
Response:可溯源(RAG 的卖点)
📝 query 返回的 Response 长这样
response = query_engine.query("报销上限?")
print(response) # → "根据《财务制度》第3条,单次报销上限为 3000 元。"
print(response.source_nodes) # → [Node2(财务制度.pdf, p3, score=0.89), ...]
# ↑ 答案的依据,能点进原文核实source_nodes = 可溯源
Response 不只给答案,还带
source_nodes——"这答案是基于这几个片段生成的"。用户能看到依据、点进原文核实。这是 RAG 相对纯 LLM 的重要优势(能追责、能验证),也是 Day 01/02 埋的"溯源"伏笔的兑现。L07
各种查询引擎
- RetrieverQueryEngine:本课主角(通用 RAG)。
- RouterQueryEngine:多个引擎,LLM 选一个(Day 10 提过的路由)。
- SubQuestionQueryEngine:把复杂问题拆成子问题,分别查再汇总。
- SQLQueryEngine:把问题转成 SQL 查数据库。
📝 SubQuestion 处理复杂问题
问"
对比 A 和 B 的报销政策"→ 拆成"A 的报销政策?"+"B 的报销政策?"→ 分别 RAG 查 → 汇总对比。单次检索答不了的复杂问题,靠拆解。读法:它们都是 BaseQueryEngine(统一
query 接口),能互换、能嵌套。第 4 周会看到 Agent 把 QueryEngine 当"工具"用——RAG 成为智能体的一种能力。L08
今日小结 + 动手
🧠 今天你应该能回答
- QueryEngine 和 Retriever 的区别?
- 三个零件?
_query的两步(RAG 的心脏)? - Response 的 source_nodes 怎么实现"可溯源"?
- RouterQueryEngine / SubQuestion 各解决什么?
✋ 动手
cd /Users/bitmart/work/codes/github/llama_index/llama-index-core/llama_index/core
sed -n '39,60p' query_engine/retriever_query_engine.py
sed -n '160,210p' query_engine/retriever_query_engine.py
ls query_engine/
明天预告 · Day 13:检索到的片段太多、塞不下 LLM 怎么办?响应合成 Synthesizer——refine(逐个精炼)、compact(压缩填满)、tree_summarize(树形汇总)。决定答案怎么"攒"出来。