一个看似合理的问题
Gemini 1.5 Pro 支持 100 万 token 上下文,Claude 3.5 支持 20 万 token,GPT-4 Turbo 12.8 万 token。一部小说大约 15 万字,约 20 万 token,直接塞进去就能问。有人问:RAG 还有必要吗?
这个问题值得认真回答,因为它背后藏着一个真实的决策:给一个生产系统,我应该用 RAG 还是长上下文?
先把数字摆出来
大语言模型的上下文窗口(2024--2025):
| 模型 | 上下文窗口 | 约合文本量 |
|---|---|---|
| Gemini 1.5 Pro | 1,000,000 tokens | ~750,000 词,约 1500 页 |
| Claude 3.5 Sonnet | 200,000 tokens | ~150,000 词,约 300 页 |
| GPT-4 Turbo | 128,000 tokens | ~96,000 词,约 190 页 |
| GPT-4o | 128,000 tokens | ~96,000 词,约 190 页 |
看起来很多。但一个企业知识库有多少内容?
- 中等规模公司的内部文档:数千篇,数百万字
- 大型代码库:数万个文件,十亿 token+
- 新闻/研究数据库:数百万篇文章
所有这些都超出了任何模型的上下文窗口。这是长上下文能力的物理上限。
长上下文的实际代价
"窗口大"不等于"免费"。每次请求都要处理所有 token,代价是真实的。
代价一:钱
按 2024 年末的价格粗估(输入 token):
| 模型 | 每百万 token 价格 | 100 万 token 一次请求 |
|---|---|---|
| Gemini 1.5 Pro | $1.25 | $1.25 |
| Claude 3.5 Sonnet | $3.00 | $3.00 |
| GPT-4 Turbo | $10.00 | $10.00 |
对比 RAG 的成本:
- 检索阶段:只调用 Embedding API(< $0.001)
- 生成阶段:只发送 2,000--5,000 token 的检索结果 + 问题(< $0.05)
同样的问题,RAG 的成本可以比长上下文低 20--200 倍。
如果一天有 1,000 个用户查询企业知识库:
- 长上下文(1M token):约 $1,250/天
- RAG(3K token 上下文):约 $3--15/天
代价二:延迟
处理更多 token = 更慢的响应。首 token 延迟(TTFT)随输入长度线性增长:
100K token 输入 → TTFT ~2--5 秒
1M token 输入 → TTFT ~15--30 秒(视模型和基础设施)
对话类应用 30 秒才开始输出,用户体验基本无法接受。
代价三:中间丢失问题
2023 年 Stanford 的研究 "Lost in the Middle"(Liu et al.)发现:当相关信息放在长上下文的中间时,LLM 的召回表现显著下降。信息在开头或结尾时表现最好,在中间时表现最差。
erlang
位置 vs 召回率(近似趋势):
开头(0-10%) ████████████████ 高
中间(40-60%) ██████ 低
结尾(90-100%) ████████████ 较高
这意味着你把 100 篇文档全塞进去,模型不一定能找到放在 50 号位置的那篇。
RAG 的实际代价
RAG 不是没有代价的。
代价一:检索不完美
向量检索是近似匹配,会出错:
- 漏检(False Negative):相关文档没被召回。用户问了一个问题,但对应的段落语义上和问题距离较远,被排在了 top-k 之外。
- 误检(False Positive):无关文档被召回。LLM 拿到噪声上下文,可能产生混淆或幻觉。
这是 RAG 系列前几篇一直在解决的问题:混合检索、Rerank、HyDE......本质上都是在修补检索的不完美。
代价二:分块破坏上下文
分块(Chunking)把文档切碎,相关信息可能分散在不同 chunk。一篇 10 页的研究报告,结论依赖第 3 页的假设,但被切成了两个 chunk,检索时只拿到了结论那块,LLM 缺少背景信息。
代价三:系统复杂度
RAG 是一个完整的工程系统:向量库 + Embedding 模型 + 检索链路 + 更新机制 + 评估框架。相比"直接把文档发给 LLM",它的维护成本更高。
五个维度的对比
| 维度 | 长上下文 | RAG |
|---|---|---|
| 文档量上限 | ~10--100 篇(受窗口和成本限制) | 无上限(向量库可扩展) |
| 成本 | 高(所有 token 每次都计费) | 低(只发相关片段) |
| 延迟 | 高(大输入慢) | 低(小输入快) |
| 召回完整性 | 完美(全部内容都在) | 不完整(依赖检索质量) |
| 知识更新 | 需要重新发送所有内容 | 只更新变化的文档 |
| 工程复杂度 | 低(直接调用 API) | 高(需要维护检索链路) |
| 单文档理解 | 强(跨全文的推理) | 弱(受分块影响) |
没有哪一方全赢。
决策框架:用哪个?
四个维度定位你的场景:
维度一:文档量
markdown
< 50 篇,总计 < 100K token → 考虑长上下文
50--1000 篇 → 评估成本后决定
> 1000 篇,或总量 > 1M token → RAG
维度二:更新频率
静态内容(月级更新以上) → 长上下文可接受
动态内容(日级/小时级更新) → RAG(增量索引成本低)
实时数据 → RAG(或直接 API 集成)
维度三:查询次数
yaml
一次性分析(研究、报告生成) → 长上下文
低频查询(< 100 次/天) → 两者都可以
高频查询(> 1000 次/天) → RAG(成本差异会累积到不可忽视)
维度四:延迟要求
交互式问答(< 3 秒响应) → RAG
报告生成、离线分析 → 长上下文可接受
综合判断
场景匹配表:
用例 文档量 更新 查询 建议
────────────────────────────────────────────────────────
法律合同审查(单份) 小 无 一次 长上下文
企业知识库问答 大 频繁 高频 RAG
PDF 财务报告分析 中 无 一次 长上下文
产品文档问答系统 大 中频 高频 RAG
代码库理解 极大 频繁 高频 RAG
会议纪要摘要(单次) 小 无 一次 长上下文
混合策略:两者兼用
长上下文和 RAG 并不互斥,有时候最好的选择是组合:
策略一:RAG 选文档,长上下文读全文
python
# 第一步:用 RAG 找到最相关的 3 篇文档
relevant_docs = retriever.invoke(query) # top-3 文档
# 第二步:把完整文档(而不是 chunk)发给 LLM
full_docs = [load_full_doc(doc.metadata["source"]) for doc in relevant_docs]
full_context = "\n\n".join([doc.page_content for doc in full_docs])
# 第三步:LLM 基于完整文档回答
answer = llm.invoke(f"基于以下文档回答:{full_context}\n\n问题:{query}")
适用场景:文档数量大(不能全发),但每篇文档内部有复杂的跨段推理需求。
策略二:粗粒度 RAG + 大块上下文
传统 RAG 的 chunk 大小是 512--1024 tokens。现在窗口大了,可以用 3000--10000 token 的大块,保留更多上下文,同时仍然做检索过滤。
python
# 分块时用大块(保留更多上下文)
splitter = RecursiveCharacterTextSplitter(
chunk_size=4000, # 传统 512 → 现在可以用 4000
chunk_overlap=400,
)
# 检索时 top-k 相应减小
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 3 × 4000 = 12,000 tokens,足够精准,比传统 RAG 上下文丰富得多
策略三:摘要缓存 + 精准检索
对大型文档库,先用 LLM 生成每篇文档的结构化摘要,存入向量库;检索时先找摘要,再按需拉取原文对应段落。
python
# 预处理:生成摘要(一次性)
for doc in all_documents:
summary = llm.invoke(f"总结这篇文档的核心要点(200字以内):{doc.page_content}")
# 把摘要作为检索单元
summary_doc = Document(page_content=summary, metadata={
"source": doc.metadata["source"],
"original": doc.page_content,
})
summary_vectorstore.add_documents([summary_doc])
# 查询时:检索摘要,返回原文段落
def query_with_summary(question):
summaries = summary_vectorstore.similarity_search(question, k=5)
# 从原文中精确提取相关段落
relevant_chunks = [
extract_relevant_passage(s.metadata["original"], question)
for s in summaries
]
return llm.invoke(build_prompt(question, relevant_chunks))
现实中的选择
大窗口模型的出现确实改变了一些决策:
以前需要 RAG,现在可以不用的场景:
- 50 页以内的文档理解(直接塞进去,更简单)
- 一次性的文档分析任务(不值得搭 RAG 系统)
- 原型验证阶段(快速验证想法,不需要生产级 RAG)
仍然需要 RAG 的场景(大多数生产系统):
- 知识库 > 1000 篇文档
- 需要实时/频繁更新
- 高并发,成本敏感
- 需要引用溯源(RAG 天然知道答案来自哪篇文档)
大窗口模型让"简单场景不用 RAG"变得合理了。但它没有让 RAG 过时------它只是让 RAG 的适用场景变得更清晰:当文档量、更新频率、或成本让"全量上下文"不可行时,RAG 是无可替代的。
小结
| 长上下文 | RAG | |
|---|---|---|
| 核心优势 | 完整上下文,跨文档推理 | 可扩展,成本低,实时更新 |
| 核心局限 | 成本高,延迟大,文档量有上限 | 检索不完美,工程复杂 |
| 最适合 | 小量文档的一次性深度分析 | 大规模生产系统 |
| 趋势 | 窗口继续变大,成本继续降低 | 检索质量继续提升 |
两者不是竞争关系,而是互补的工具箱。理解各自的代价,选对了工具,才是工程判断力。