Map-Reduce 摘要模式
分别总结每个文本块,然后综合这些摘要
Map-Reduce 摘要模式 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Prompt Engineering 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Prompt Engineering 课程共包含 4 节课。
映射-归约模式
当文档超出 LLM 的上下文窗口时,您无法一次性传入全部内容。映射-归约模式可以解决这一问题:
- 映射:独立摘要每个文本块
- 归约:将所有文本块摘要综合为一份最终摘要
这与分布式系统中的经典 MapReduce 相似——将同样的分而治之逻辑应用于语言任务。
第 1 步:映射阶段
在映射阶段,每个文本块都会连同摘要提示词发送给 LLM。模型只返回该文本块的简短摘要。这些摘要会收集到一个列表中。
每份摘要都应比原始文本块短得多——通常只有原始长度的 10–20%。正是这种压缩使归约步骤成为可能。
import openai
client = openai.OpenAI(api_key='sk-...')
def summarize_chunk(chunk, model='gpt-4o'):
resp = client.chat.completions.create(
model=model,
messages=[
{'role': 'system', 'content': 'Summarize the following text concisely in 3-5 sentences.'},
{'role': 'user', 'content': chunk}
]
)
return resp.choices[0].message.content
def map_phase(chunks):
return [summarize_chunk(c) for c in chunks]第 2 步:归约阶段
在归约阶段,所有文本块摘要会被拼接起来,并连同 synthesis 提示词发送给 LLM。模型会生成一份连贯的最终摘要。
如果文本块摘要仍然过长,无法放入一个上下文窗口中,就递归地应用归约——先摘要若干摘要组成的分组,再综合这些摘要。
def reduce_phase(chunk_summaries, model='gpt-4o'):
combined = '\n\n'.join(
f'Section {i+1}:\n{s}'
for i, s in enumerate(chunk_summaries)
)
resp = client.chat.completions.create(
model=model,
messages=[
{'role': 'system', 'content': 'You are given summaries of consecutive sections of a document. Write a single coherent summary of the entire document.'},
{'role': 'user', 'content': combined}
]
)
return resp.choices[0].message.content整合应用:原始 API
下面是使用原始 OpenAI API 调用实现的完整映射-归约流水线——无需框架。这让您可以完全控制每个阶段的提示词和参数。
def map_reduce_summarize(document, chunk_size=1000):
chunks = fixed_chunk(document, max_tokens=chunk_size)
print(f'Chunks: {len(chunks)}')
chunk_summaries = map_phase(chunks)
print(f'Map phase complete. Summaries: {len(chunk_summaries)}')
final_summary = reduce_phase(chunk_summaries)
return final_summary
with open('long_report.txt') as f:
doc = f.read()
result = map_reduce_summarize(doc)
print(result)LangChain MapReduceDocumentsChain
LangChain 提供了现成的 MapReduceDocumentsChain,可以处理分块、并行映射调用和归约步骤。它使用方便,但不如原始 API 调用灵活。
from langchain_openai import ChatOpenAI
from langchain.chains.summarize import load_summarize_chain
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.docstore.document import Document
llm = ChatOpenAI(model='gpt-4o', openai_api_key='sk-...')
splitter = RecursiveCharacterTextSplitter(chunk_size=3000, chunk_overlap=200)
with open('long_report.txt') as f:
text = f.read()
docs = splitter.create_documents([text])
chain = load_summarize_chain(llm, chain_type='map_reduce')
result = chain.invoke(docs)
print(result['output_text'])并行化映射阶段
每个文本块的摘要彼此独立,因此可以并行执行映射阶段。使用 Python 的 ThreadPoolExecutor,所有文本块的 API 调用都会并发发送,从而显著减少实际耗时。
from concurrent.futures import ThreadPoolExecutor, as_completed
def map_phase_parallel(chunks, max_workers=10):
summaries = [None] * len(chunks)
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(summarize_chunk, chunk): i
for i, chunk in enumerate(chunks)
}
for future in as_completed(futures):
idx = futures[future]
summaries[idx] = future.result()
return summaries递归归约
当文档非常长时,文本块摘要本身可能会超出上下文窗口。请递归地应用归约:将摘要分成多个批次,分别归约每个批次,然后再归约各批次的摘要。
def recursive_reduce(summaries, batch_size=10):
while len(summaries) > 1:
batches = [
summaries[i:i + batch_size]
for i in range(0, len(summaries), batch_size)
]
summaries = [reduce_phase(batch) for batch in batches]
print(f'Reduced to {len(summaries)} summaries')
return summaries[0]保留关键细节
朴素映射-归约方法常见的问题是:重要细节会在压缩过程中丢失。可以采取以下缓解措施:
- 提示映射步骤保留名称、数字、日期
- 要求归约步骤检查各章节之间是否存在矛盾
- 使用更大的文本块大小,使映射上下文包含更丰富的信息
- 执行一个验证环节:询问模型原文中的关键实体是否出现在最终摘要中
MAP_PROMPT = '''Summarize the following section in 5 sentences.
Preserve all key names, numbers, dates, and conclusions.
Section:
{chunk}'''精炼链:另一种模式
精炼链是映射-归约的另一种方案。它按顺序处理文本块:将文本块 N 的摘要与文本块 N+1 一起传入,模型再更新当前摘要。这会生成更连贯的输出,但无法并行执行,速度也更慢。
当叙事连贯性很重要时(例如法律合同),请使用精炼链。当速度更重要时(例如新闻文章批次),请使用映射-归约。
def refine_summarize(chunks):
current_summary = summarize_chunk(chunks[0])
for chunk in chunks[1:]:
prompt = (
f'Existing summary:\n{current_summary}\n\n'
f'New section:\n{chunk}\n\n'
'Update the summary to incorporate the new section.'
)
resp = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': prompt}]
)
current_summary = resp.choices[0].message.content
return current_summary成本与词元管理
映射-归约会发起许多 API 调用。对于一个包含 100 个文本块、使用 gpt-4o 且输入词元价格为每 100 万个 5 美元的文档:
- 映射:100 个文本块 × 1000 个词元 = 100k 个输入词元 ≈ 0.50 美元
- 归约:约 10k 个词元(摘要)≈ 0.05 美元
- 总计:每份文档约 0.55 美元
要降低成本,可以在映射阶段使用 gpt-4o-mini(每 100 万个词元 0.15 美元),仅在归约阶段使用 gpt-4o。这种混合方案只会造成极小的质量损失,却能将成本降低 70%。
def map_phase_cheap(chunks):
# Use mini model for map — cheaper, sufficient for chunk summaries
return [
summarize_chunk(c, model='gpt-4o-mini')
for c in chunks
]
def reduce_phase_quality(summaries):
# Use full model for final synthesis
return reduce_phase(summaries, model='gpt-4o')何时使用映射-归约
映射-归约摘要最适合以下情况:
- 长度超过模型上下文窗口的文档
- 批量摘要多个文档(还可以在文档层面进行并行处理)
- 需要控制每个步骤所使用提示词的情况
它不太适合以下情况:提取特定事实(应改用检索),或者文档足够短、可以放入上下文中(直接进行摘要)。
知识检查
在映射-归约摘要模式中,归约阶段会执行什么操作?
回顾:映射-归约摘要
映射-归约模式可以处理无法通过一次 LLM 调用完成的超长文档:
- 映射:独立摘要每个文本块——可以并行执行
- 归约:将文本块摘要综合为一份最终摘要
- 递归归约:当摘要本身过长时应用
- 成本提示:映射阶段使用廉价模型,归约阶段使用高质量模型
LangChain 的 MapReduceDocumentsChain 提供了现成的实现。下一课将介绍适用于书籍和研究论文的层级式摘要。
常见问题解答
「Map-Reduce 摘要模式」课时是免费的吗?
是的 — 「Map-Reduce 摘要模式」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。
「Map-Reduce 摘要模式」这节课中我会学到什么?
分别总结每个文本块,然后综合这些摘要 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Prompt Engineering 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Prompt Engineering 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「Map-Reduce 摘要模式」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Prompt Engineering 课中编写并运行代码吗?
能。每节 AI Prompt Engineering 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 长文本分块策略
- Map-Reduce 摘要模式
- 层次化摘要
- 在文本块之间保持上下文