系列目标 :30 天从 LangChain 入门到企业级部署
今日任务:理解长文档挑战 → 掌握三种 Chain 策略 → 实现"100 页 PDF 自动摘要"!
📚 一、为什么需要长文档处理?
Qwen 7B 的上下文窗口约 32K tokens(≈2万汉字),看似很大,但:
- 一份技术白皮书 ≈ 50K tokens
- 公司年度报告 ≈ 100K+ tokens
- 法律合同/用户协议 ≈ 超长且关键
直接塞入 Prompt?
- ❌ Token 超限 → 报错
- ❌ 截断开头/结尾 → 丢失关键信息
解决方案:
✅ 分而治之 ------ 将长文档分块,用不同策略聚合结果!
💡 今天,我们就实测 LangChain 三大长文档 Chain:MapReduce、Refine、Map-Rerank!
🧱 二、核心策略对比
表格
| 策略 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| MapReduce | 分块独立处理 → 合并结果 | 并行快、简单 | 合并可能丢失细节 | 摘要、关键词提取 |
| Refine | 逐块迭代优化答案 | 保留细节、连贯 | 串行慢、token 多 | 精细问答、总结 |
| Map-Rerank | 分块打分 → 取最高分 | 快速定位关键段 | 只返回单片段 | 事实核查、精准检索 |
✅ 所有策略都基于
StuffDocumentsChain(Day 18)的扩展!
🛠️ 三、准备工作:加载长文档
假设我们有一份 50 页公司年报(PDF) :
ini
# day22_long_document.py
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 加载 PDF
loader = PyPDFLoader("docs/annual_report_2025.pdf")
docs = loader.load()
# 分块(比 RAG 更大,因需完整语义)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=2000, # 每块约 1000 字
chunk_overlap=200,
separators=["\n\n", "\n", "。", ";", " ", ""]
)
chunks = text_splitter.split_documents(docs)
print(f"📄 文档共 {len(chunks)} 个片段,总长度 ≈ {sum(len(c.page_content) for c in chunks) // 1000}K 字")
🔁 四、策略 1:MapReduce(快速摘要)
原理
- Map 阶段:每个 chunk 生成局部摘要
- Reduce 阶段:合并所有摘要 → 全局摘要
ini
from langchain_ollama import ChatOllama
from langchain.chains.summarize import load_summarize_chain
llm = ChatOllama(model="qwen:7b", temperature=0)
# 创建 MapReduce Chain
map_reduce_chain = load_summarize_chain(
llm,
chain_type="map_reduce",
verbose=True
)
# 执行摘要
summary = map_reduce_chain.run(chunks)
print("📝 全局摘要:\n", summary)
✅ 优势 :可并行处理(未来支持),速度快
❌ 劣势:合并时可能泛化过度
🔍 五、策略 2:Refine(精细总结)
原理
- 用第一个 chunk 生成初始摘要
- 依次用后续 chunk 迭代优化摘要
ini
# 创建 Refine Chain
refine_chain = load_summarize_chain(
llm,
chain_type="refine",
verbose=True,
return_intermediate_steps=False
)
# 执行
refined_summary = refine_chain.run(chunks)
print("✨ 精炼摘要:\n", refined_summary)
✅ 优势 :保留更多细节,逻辑更连贯
❌ 劣势:串行执行,耗时较长(N 次 LLM 调用)
💡 提示:可在 Prompt 中指定格式,如"用 bullet points 列出三大重点"
🎯 六、策略 3:Map-Rerank(精准问答)
场景
用户问:"年报中提到的 2025 年营收目标是多少?"
我们不需要全文摘要,只需最相关的片段。
ini
from langchain.chains import RetrievalQA
from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings
# 先建向量库(复用 Day 17)
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma.from_documents(chunks, embeddings)
# 创建 Map-Rerank 风格的 QA(通过高 k + 精排)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="map_rerank", # 注意:此模式要求 LLM 输出分数
retriever=vectorstore.as_retriever(search_kwargs={"k": 10}),
return_source_documents=True
)
# 但注意:Qwen 不原生支持打分,改用以下方式模拟
⚠️ 现实问题 :
map_rerank需 LLM 在回答中包含置信度分数(如{"answer": "...", "score": 0.9}),多数开源模型不支持。
替代方案:用 Reranker(Day 19)+ Top-1
ini
# 使用 Day 19 的重排序器
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-large")
compressor = CrossEncoderReranker(model=model, top_n=1)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 5})
)
# 直接取最相关片段回答
query = "2025年营收目标是多少?"
docs = compression_retriever.get_relevant_documents(query)
answer = docs[0].page_content
print("🎯 精准答案:", answer)
✅ 更实用!尤其适合事实型问答。
⚙️ 七、如何选择策略?
表格
| 需求 | 推荐策略 |
|---|---|
| 快速生成全文摘要 | MapReduce |
| 高质量总结/报告 | Refine |
| 精准问答(单事实) | Map-Rerank(或 Reranker + Top-1) |
| 多跳推理(需多片段) | Refine + 自定义 Prompt |
💡 混合策略:先用 MapReduce 得全局概览,再对关键章节用 Refine 深挖。
⚠️ 八、注意事项 & 最佳实践
表格
| 问题 | 建议 |
|---|---|
| 分块太小 | 导致上下文断裂 → 增大 chunk_size(2000~4000) |
| 分块太大 | 超过 LLM 单次处理能力 → 监控 token 使用 |
| Refine 太慢 | 对超长文档,先用 MapReduce 筛选关键章节 |
| 中文标点分割差 | 在 separators 中显式加入 ["。", "!", "?"] |
| 成本控制 | MapReduce 可缓存中间结果;Refine 难以缓存 |
💡 生产建议:
- 对 >100 页文档,先做章节分割(用 PDF 目录)
- 记录每种策略的耗时与效果,A/B 测试
📦 九、配套代码结构
bash
langchain-30-days/
└── day22/
├── long_doc_summarize.py # MapReduce / Refine 摘要
└── long_doc_qa.py # Map-Rerank 风格问答
📝 十、今日小结
- ✅ 理解了长文档处理的核心挑战
- ✅ 掌握了 MapReduce、Refine、Map-Rerank 三大策略
- ✅ 实现了 50 页 PDF 的自动摘要与精准问答
- ✅ 知道了如何根据场景选择最优策略
- ✅ 学会了用 Reranker 替代原生 Map-Rerank
🎯 明日预告:Day 23 ------ Agent 进阶!Function Calling 与 Tool 自动注册,打造智能 AI 助手!