长上下文与递归 RAG
难度:⭐⭐⭐ | 高频指数:🔥🔥 | 应用岗相关度:★★
面试回答
常见问法
- 既然模型支持 128k / 1M 长上下文,RAG 是不是要被淘汰了?
- 长上下文的 Lost-in-the-Middle 是怎么回事,怎么缓解?
- 递归 RAG / Self-RAG / RAPTOR 分别在解决什么问题?
- 长文档摘要时 Map-Reduce、Refine、Tree-of-Summaries 怎么选?
- 长上下文 + RAG 怎么混合用?哪些场景必须混合?
- 你们项目里上下文打多少 token?怎么权衡成本和召回?
回答
简短答案是:长上下文没有取代 RAG,反而让 RAG 的取舍更复杂。
工程上要分两层看。第一层是物理代价——Transformer 注意力是 O(n²),KV cache 随上下文线性膨胀;一个 1M context 的请求,单次推理成本和延迟可能是 8k 请求的几十倍。线上根本不可能把所有用户的全量知识库都塞进 prompt,成本和延迟都不允许。
第二层是效果代价——也就是 Lost-in-the-Middle。模型对 prompt 首尾的注意力远高于中段,把 100 个 chunk 平铺进去时,关键证据如果落在中间位置,召回了也用不上。Needle-in-a-Haystack 测试在 8k 以内还行,到 100k 以后命中曲线就出现明显的 U 型塌陷。
所以现在主流是混合方案:RAG 做粗筛,长上下文做精读。检索把候选从十万级压到几十个 chunk,再用长上下文模型一次性消化,避免传统 RAG 上下文窗口太小只能塞 top-3 的问题。复杂问题再叠一层递归——先检索→判断证据是否够→不够就改写 query 再检索,直到模型自己说”我能答了”。
面试时要表现出的判断力是:长上下文是新工具,不是新答案——它改变了 RAG 各环节的最优参数(top-k 可以大一些、chunk 可以粗一些),但没有改变”先粗筛后精读”这个基本架构。
追问
- Sliding Window Attention、Ring Attention、Flash Attention 各自优化什么?
- Needle-in-a-Haystack 测试怎么解读,为什么不能只看平均分?
- 分层索引(父子块、摘要索引)是怎么组织的?
- 多跳推理用 Agentic RAG 还是 pipeline RAG?怎么决定?
- 递归 RAG 的终止条件怎么设计?
- prompt caching / KV cache 复用对长上下文成本影响有多大?
原理展开
1. 长上下文不是免费的:成本曲线长什么样
很多人对长上下文的第一反应是”那就都塞进去呗”,但要算账。
计算成本:注意力机制是 O(n²),prefill 阶段的 FLOPs 随上下文长度平方增长。10k → 100k 上下文,单次 prefill 算力涨 100 倍,这是物理规律,再优化也跑不掉指数。
内存成本:KV cache 是 O(n),一个 70B 模型在 100k 上下文下 KV cache 能占几十 GB 显存,直接决定单卡能并发多少请求。
金钱成本:以主流 API 为例,128k 上下文比 8k 单价没变,但你真往里塞 100k token,单次成本就是 8k 请求的 12 倍。线上一天百万次请求时,这个差距是几个数量级的开销。
延迟成本:prefill 时间线性增加,用户感知的”首字延迟”(TTFT)从几百 ms 涨到几秒,体验直接崩盘。
所以工程判断永远是:长上下文是有上限的奢侈品,不是”反正能塞就都塞”。
2. Lost-in-the-Middle:长上下文的效果天花板
斯坦福 2023 年的论文给出了一个反直觉结论:把关键信息放在 prompt 不同位置,模型回答正确率呈 U 型曲线——首尾高,中段低。
位置:[开头] [中段] [中段] [中段] [末尾]
准确: 75% 55% 45% 50% 72%
为什么?训练阶段的位置编码、attention pattern、SFT 数据分布共同决定的——模型见过更多”开头有指令、末尾有问题”的样本,对中段 token 的检索能力训练不足。
工程含义有两条:
- 塞 100 个 chunk 进 prompt,命中的不一定能被用上——召回率高 ≠ 回答准
- 关键 chunk 要排到首尾——Rerank 之后顺序很重要,最相关的放最前面或最后面,别放中间
Needle-in-a-Haystack 测试就是为了量化这个现象——往长文档里插一句”无关的关键事实”,看模型在不同位置和不同长度下能否找回。只看平均分会被骗,要看分位数和位置热力图。
3. 递归 RAG / Self-RAG:什么时候要”再来一轮”
朴素 RAG 是单次检索 → 生成。问题是:
- 用户问题模糊时,第一次检索可能拿不到关键证据
- 多跳问题(“X 公司 CTO 的前公司在哪上市的?“)需要先查 X 公司 CTO,再查那家前公司
- 复杂问题可能需要多个证据片段拼起来
递归 RAG 的核心模式:
loop:
检索(query)
模型判断: 证据够吗?
够 → 生成回答,结束
不够 → 改写 query / 拆子问题 → 继续检索
超过 max_iter → 强制收尾
Self-RAG 在这个基础上加了”反思 token”——模型在生成时主动判断”我需要再检索吗”、“这段证据可信吗”、“这段回答有依据吗”,让检索决策内化到生成过程里。
实战里要警惕:递归 RAG 的延迟是朴素 RAG 的 N 倍,每多一轮就多一次 LLM + 检索往返。简单问题用递归 RAG 是浪费,要做问题分流——能一次答的就一次答。
4. RAPTOR:树状摘要 + 多粒度检索
RAPTOR 是 2024 年提出的递归索引方案,思路是构建摘要树:
Level 0: 原始 chunks(100 字粒度)
Level 1: 对 chunks 聚类,每类生成摘要(500 字)
Level 2: 对 Level 1 摘要再聚类,再摘要(2000 字)
...
Root: 全文摘要
检索时同时查多层,粗粒度摘要回答”在哪里”,细粒度 chunk 回答”具体内容”。这对长文档问答(论文、技术手册、法律条款)特别有效——单纯切 chunk 会丢全局语义,单纯做全文摘要又丢细节。
工程上 RAPTOR 的代价是离线构建成本——每层聚类 + LLM 摘要,索引一次几十万 token,更新不友好。所以适合相对静态的语料,不适合每天大量新增的场景。
5. Map-Reduce / Refine / Tree-of-Summaries:长文档处理三种范式
不止索引,对长文档做摘要 / QA 时也有三种经典模式:
Map-Reduce:
把文档切成 N 块 → 每块独立处理(map)→ 汇总(reduce)
优点:可并行,吞吐高
缺点:每块看不到全局,跨块逻辑会丢
Refine:
从第一块开始,每次把"当前答案 + 下一块"喂给模型,迭代精修
优点:上下文连续,跨块语义保留
缺点:串行,延迟随块数线性增长
Tree-of-Summaries:
Map-Reduce 的递归版本,多层 reduce
优点:保留层次信息,处理超长文档稳定
缺点:实现复杂,调用次数最多
工程选型逻辑:
- 要快、跨块依赖弱 → Map-Reduce(如批量摘要新闻列表)
- 要细、跨块依赖强 → Refine(如小说翻译保持人物口吻)
- 超长、要分层 → Tree(如百页技术文档问答)
6. 长上下文 + RAG 混合方案
主流落地形态有三种:
方案 A:RAG 粗筛 + 长上下文精读
检索 top-50 chunk(粗筛)→ 全部塞入 128k 上下文 → 生成
适合:知识密集型 QA、需要交叉引用多个证据片段。长上下文承担了”在召回结果里精读”这一步,比传统”只塞 top-3”召回完整很多。
方案 B:长上下文容纳完整文档 + RAG 做文档级路由
向量检索定位到 top-1 文档 → 整篇塞进 128k → 生成
适合:每篇文档可独立回答的场景(产品手册、论文 QA)。比 chunk 级检索少一次拼接误差。
方案 C:分层——粗粒度长上下文 + 细粒度 RAG
长上下文承载顶层摘要(全局视野)→ RAG 检索细节 chunk → 拼接生成
适合:用户问题既需要全局判断又需要细节支撑(比如”总结这本书的核心论点并给出原文引用”)。
判断标准:候选规模能否塞进 context?塞进去能否回本?回不了本就 RAG,能回本就长上下文。
7. 长上下文的工程优化:让昂贵变可承受
线上要把长上下文用起来,必须做几件事:
- Prompt Caching:把不变的部分(system prompt、知识库 chunk)缓存 KV cache,下次只算 query。OpenAI、Anthropic、DeepSeek 都支持,命中时延迟和成本都能降 50%+
- Context 压缩:用 LLMLingua 之类的工具对召回的 chunk 做有损压缩,去掉冗余 token,能把 100k 压到 30k 而效果损失可控
- 流式拼装:边检索边喂给模型,不等召回全完成
- KV cache 复用:同一文档多次问答时复用 prefill
- 分级模型:粗筛用小模型、精排用长上下文大模型
工程上的关键认知是:长上下文不是开关,是滑动条——根据 query 复杂度动态决定上下文长度,不要一刀切。
8. 什么时候递归 RAG 反而是坑
递归 RAG 被吹得很神,但实战里要警惕:
- 没设终止条件:模型反复说”再查一下”,无限循环,token 烧光
- 判断不准:模型经常误判”我有足够证据了”,提前止损或过度检索
- 延迟爆炸:每轮 2-3 秒,三轮 10 秒,用户已经走了
- 错误放大:第一轮检索错了,改写的 query 也错,越走越偏
- 可观测性差:用户问一次,背后跑了 5 次检索 + 3 次 LLM,trace 一长串
判断标准是:问题是否真的需要多跳。能一次答的不要递归。常见做法是先用小模型对 query 做复杂度分类,简单问题走朴素 RAG,复杂问题才进递归。
9. 实战里的混合架构长什么样
我们项目里典型的处理链是这样的:
用户 query
↓
意图分类(小模型,分流)
├─ 简单事实问答 → 单次 RAG,top-5 chunk → 生成
├─ 复杂多跳推理 → 递归 RAG,最多 3 轮
└─ 长文档总结 → Map-Reduce + 长上下文精读
↓
Rerank(Cross-Encoder)
↓
Prompt 组装(最相关的放首尾,避免 Lost-in-the-Middle)
↓
长上下文 LLM(开 prompt caching)
↓
后处理(引用标注、置信度)
这套架构的精髓不在每个组件多先进,而在分流——根据 query 类型走不同的链路,避免简单问题用复杂方案,复杂问题被简单方案敷衍。
对比总结
| 方案 | 适用场景 | 成本 | 延迟 | 召回上限 |
|---|---|---|---|---|
| 长上下文直塞全量 | 候选小、追求极致召回 | 极高 | 高 | 极高 |
| 朴素 RAG | 简单事实问答 | 低 | 低 | 中 |
| RAG + 长上下文精读 | 主流知识 QA | 中 | 中 | 高 |
| 递归 RAG / Self-RAG | 多跳推理、模糊查询 | 高 | 高 | 高 |
| RAPTOR 摘要树 | 长文档分层问答 | 离线高、在线中 | 中 | 高(多粒度) |
| Map-Reduce | 批量长文档摘要 | 中 | 中(可并行) | 中 |
| Refine | 跨块连续语义 | 中 | 高(串行) | 高 |
易错点
- 以为”上下文变长 = 答得更准”——没测 Needle-in-a-Haystack,被 Lost-in-the-Middle 暗杀
- 长上下文直塞导致单次成本暴涨——线上一天百万次请求时财务部会找你
- 递归 RAG 没设终止条件——陷入死循环或反复检索同一组结果
- 把 Agentic RAG 用在简单问题上——延迟翻数倍,体验崩盘
- Map-Reduce 用在跨块依赖强的任务——每块独立处理把上下文打散了
- 忘开 prompt caching——长上下文场景不开缓存等于自杀
- 把”召回了”等同于”用上了”——top-50 里关键 chunk 在中段,模型可能根本没注意
记忆技巧
记三组锚点:
- 三条路线:长塞 / 朴素 RAG / 递归 RAG,按「问题复杂度 × 成本预算」二维选
- 两个天花板:物理(O(n²) 算力 + KV cache)+ 效果(Lost-in-the-Middle)
- 一个底层判断:上下文长不等于有效上下文;索引粒度 ≠ 召回粒度 ≠ 入 prompt 粒度
面试速答版
长上下文没取代 RAG,反而让 RAG 取舍更复杂。两个天花板逃不掉:注意力 O(n²) 决定成本和延迟随上下文平方增长,Lost-in-the-Middle 决定中段 chunk 命中也用不上。
主流是混合方案——RAG 粗筛(把候选从十万压到几十个 chunk)+ 长上下文精读(一次消化所有候选)。复杂多跳问题再叠递归 RAG / Self-RAG,但要有终止条件避免延迟爆炸。
工程上必须做的优化:prompt caching、context 压缩、query 复杂度分流。判断标准是”问题是否真的需要更长上下文 / 更多轮检索”,不要给简单问题加复杂方案。
面试加分版
如果想多讲一层,可以补:
- 长上下文改变了 RAG 各环节的最优参数:top-k 可以更大、chunk 可以更粗、rerank 的 top-n 可以放宽——但没有改变”先粗筛后精读”的基本架构
- RAPTOR / 分层索引的价值在于”多粒度”:单一粒度切 chunk 永远在”语义完整”和”检索精度”之间妥协,分层是绕过这个二选一
- Agentic RAG 真正的成本不是 token,是延迟可观测性变差——一次用户请求背后跑了几次 LLM、几次检索,trace 不清楚的话出问题根本查不到
- Cross-ref:RAG 系统设计的完整数据流参考
RAG系统设计面试题.md,本篇聚焦”长上下文出现后取舍怎么变”