Day 13 / 共 20 天 · 第 3 周 检索与查询
响应合成 Synthesizer(流水线第 ⑥ 步)
检索到的片段可能很多、可能塞不下 LLM——怎么把它们"攒"成一个连贯答案?这就是响应合成。今天讲三种合成模式,它们决定答案质量、调 LLM 次数(成本)和速度。
📍 你在整条链的位置
⑤ 检索→
后处理 D14→
⑥ 合成答案 Synthesizer→
✅ Response
L01
合成的难题
🤔 检索回来 10 个片段,一次塞不进提示词怎么办?
top_k=10、每块 1000 token = 上万 token,加上下文限制——不能无脑全塞。而且要"综合"多个片段得出连贯答案,不是简单拼接。
💡 Synthesizer = 用不同策略把 N 个片段"攒"成一个答案
有的逐个精炼、有的压缩填满、有的树形汇总。合成模式直接影响:答案质量、LLM 调用次数(钱)、延迟。选对模式很重要。
L02
四种主要模式
response_synthesizers/type.py:4-51 的 ResponseMode,每个模式一个文件:
| 模式 | 怎么攒 | LLM 调用 |
|---|---|---|
| REFINE | 逐个片段精炼答案 | N 次(多) |
| COMPACT(默认) | 压缩填满上下文再精炼 | 较少 |
| TREE_SUMMARIZE | 树形两两汇总 | 并行、适合大量 |
| SIMPLE_SUMMARIZE | 全拼一起(塞不下就截断) | 1 次(可能丢信息) |
读法:
factory.py 的 get_response_synthesizer 按 mode 造对应合成器(Day 12 默认 COMPACT)。下面拆最重要的三个。L03
refine:滚雪球式精炼
response_synthesizers/refine.py:
📝 refine 过程(假设 3 个片段)
1. 片段1 + 问题 → LLM →
2. 片段2 +
3. 片段3 +
每个片段调一次 LLM,答案层层完善。
初版答案 A12. 片段2 +
A1 + 问题 → LLM →"在 A1 基础上结合片段2 精炼"→ A23. 片段3 +
A2 → LLM → A3(最终)每个片段调一次 LLM,答案层层完善。
优点全、缺点慢
每个片段都被完整看到(不因塞不下被截断),答案精细。但 N 个片段 = N 次 LLM 调用(慢、贵)。适合片段多、要精确综合的场景。
L04
compact:refine 的省钱版(默认)
response_synthesizers/compact_and_refine.py:先把多个片段"塞满"一个提示词(尽量填满上下文),塞不下的再走 refine。
📝 10 个片段,compact vs refine
• refine:10 个片段 = 10 次 LLM 调用
• compact:若每次能塞 3 个 → 打包成 3+3+4 → 只 3 次调用(省 7 次)
• compact:若每次能塞 3 个 → 打包成 3+3+4 → 只 3 次调用(省 7 次)
💡 默认模式,平衡"信息完整"和"少调 LLM"
先"打包"能塞进一次的片段(少调 LLM),塞不下的剩余再 refine。大多数场景用它就好。
L05
tree_summarize:树形汇总
response_synthesizers/tree_summarize.py:片段两两/成组汇总成中间摘要,再汇总,层层向上到最终答案。
tree_summarize:片段分组汇总 → 中间摘要再汇总 → 最终答案。每层可并行,适合大量片段。
适合"总结大量内容"
问"总结整份文档"用它(层级归并、每层并行、比 refine 快)。缺点:中间汇总可能丢细节。问"报销的具体条款"用 compact(要精确细节)。
L06
PromptHelper:自动装箱
💡 算"一次能塞多少片段"
indices/prompt_helper.py 的 PromptHelper:根据 LLM 上下文窗口、留给答案的空间,算出每次能装多少 Node 文本、要分几批。读法:compact 的"尽量塞满"、refine 的"每次一个"都靠它计算边界。它防止"塞爆上下文"报错——自动分批。不同 LLM 上下文大小不同(GPT-4o 128k vs 老模型 4k),它动态适配。这是 RAG 工程化的关键细节。
L07
流式与结构化
💡 streaming=True + 结构化输出
流式:答案边生成边返回(打字机效果,改善体验)。结构化:
output_cls 让 LLM 返回 Pydantic 对象而非纯文本(如 {"answer":..., "confidence":...})。读法:流式让用户"立刻看到答案在打字"(像 ChatGPT);结构化让 RAG 结果能接入下游程序。都是生产 RAG 的体验/集成能力。
L08
今日小结 + 动手
🧠 今天你应该能回答
- 为什么需要"合成"而不是简单拼接片段?
- refine / compact / tree_summarize 各怎么工作、各适合什么?
- 为什么 compact 是默认(省 LLM 调用)?
- PromptHelper 解决什么"装箱"问题?
✋ 动手
cd /Users/bitmart/work/codes/github/llama_index/llama-index-core/llama_index/core
sed -n '1,55p' response_synthesizers/type.py
ls response_synthesizers/
grep -n "def get_response_synthesizer\|ResponseMode" response_synthesizers/factory.py | head
明天预告 · Day 14:检索到的 Node 在合成前先"精修"——后处理(重排/过滤/上下文窗口)+ 提示词模板。rerank 是提升 RAG 准确率最有效的技巧之一。