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:25RetrieverQueryEngine——在检索之上加"用 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
问题 retrieve 找检索器+后处理 相关 Node synthesize 合成喂 LLM 生成 ✅ 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(树形汇总)。决定答案怎么"攒"出来。
← Day 11 Day 13 · 响应合成 →