长文档处理:切块策略与 Map-Reduce 式摘要
彩球呐喊
7 分钟阅读
把整本文档一次性塞进上下文,既会撑爆窗口,也会让模型在海量背景里丢失重点。工程上更稳的做法是切块后做 Map-Reduce:每块独立处理,再合并中间结果。这条路线牺牲了一点端到端简洁性,换来了可控的窗口占用和稳定的输出质量。
切块不是"按字数切"这么简单
固定大小切分最简单,但常在句子或段落中间断开,破坏语义。更好的起点是按结构边界:章节标题、空行、句子边界。再用滑动重叠补回被切断的上下文:
def chunk_by_heading(text, max_chars=1500, overlap=200):
blocks = []
cur = ""
for para in text.split("\n\n"):
if len(cur) + len(para) > max_chars and cur:
blocks.append(cur)
cur = cur[-overlap:] + "\n\n" + para # 尾部重叠
else:
cur = (cur + "\n\n" + para).strip()
if cur:
blocks.append(cur)
return blocks
块大小要匹配下游用途:给"检索"用的块宜小(几百字),让召回更精准;给"摘要"用的块可稍大,保留上下文。重叠能缓解割裂,但会放大 token 消耗,重叠比例经验上 10%–15% 足够,再高就是纯浪费。
Map 阶段:每块独立产出中间结果
每块单独过模型,做摘要或抽取,得到一组"中间摘要"。这一步可以并行,互不依赖:
def map_stage(chunks, question=None):
mid = []
for c in chunks:
prompt = build_map_prompt(c, question)
mid.append(call_model(prompt))
return mid
注意给每块带上"来源标记"(页码、章节),否则 Reduce 阶段无法追溯结论出处,出了问题无从排查。中间结果建议结构化:让模型每步产出"要点 + 出处"的字段,而不是一段自由文本,后续合并和校验都更稳。
Reduce 阶段:合并而非堆砌
中间结果加起来仍可能超窗口,所以要分层合并:先两两或按组压缩,再整体汇总。不要直接把所有中间摘要拼成大 prompt 丢回去。
def reduce_stage(mids, max_group=5):
while len(mids) > 1:
next_round = []
for i in range(0, len(mids), max_group):
group = mids[i:i + max_group]
next_round.append(call_model(build_reduce_prompt(group)))
mids = next_round
return mids[0]
分层合并的层数本身就是个调参项:层数少,单层输入长,可能超窗;层数多,信息损耗大。经验上每组 5 条左右、控制在 3 层以内,能在不超窗的前提下保住关键事实。
全局问题与局部摘要的矛盾
Map-Reduce 最怕"跨块信息"。如果答案需要"全文第 3 页和第 20 页同时出现的条件",逐块摘要必然漏掉——因为单块根本看不到另一块的内容。这类"全局问题"要用另一条路径:先检索定位相关块,再把少数相关块一起读,而不是盲摘要。换句话说,摘要和检索是互补的两套机制,别用摘要去硬扛检索的活。
边界条件与踩坑
- 块太小会让单块丢失上下文,模型看不到"它在讲什么";块太大又回到原问题。块大小要和文档密度一起调,表格密集的文档块要更小。
- Reduce 层数越多,信息损耗越大。每层合并都会丢掉细节,关键事实应在 Map 阶段就显式抽取成结构化字段,而不是埋在自由文本里。
- 冗余块(相似段落)会拉高成本且让 Reduce 偏重复内容,去重可在切块后做一遍相似度过滤,把近乎重复的块合并或剔除。
- 切块会切断跨页引用("见上图""如前所述"),Map 阶段遇到指代缺失要标记,Reduce 时才能决定是否回溯原文。
- 不要对已经结构化的文档(如带目录的合同、带表头的报表)用纯字数切块,按已有结构切能保住语义完整性。
切块与检索如何衔接
摘要和检索不是孤立两步。常见流程是:先用检索把长文档定位到少数相关块,再对这些块做摘要,而不是先摘要整本文档再检索。前者省 token、保精准,后者在"找得到"之前就先"读全篇",浪费且易丢重点。块大小还要和检索粒度对齐:检索用小块保证召回准,摘要用大块保证上下文,同一文档可以存两种粒度的块,各取所需。
当摘要本身要被检索
多层 Reduce 产出的"文档级摘要",常常还要再进向量库,供上层问答检索。这时摘要的质量直接决定上层召回的上限——摘要漏掉的关键实体,上层永远检索不到。对策是在 Map 阶段就强制抽取结构化字段(实体、时间、结论),让摘要既能被人读,也能被机器索引。把"摘要"和"索引"看成同一件事的两个出口,比先写散文再想办法提炼更稳。
成本别忘算在块数上
文档越长,块数越多,Map 阶段调用次数线性增长。一本几百页的手册,块数可能上千,单次摘要的成本会被放大到不可忽视。这时要先用检索把范围缩小到相关章节,再只对那些章节做 Map-Reduce,而不是对全书无差别处理。先收口范围,再加工内容,是控制长文档成本的主线。
补充:切块参数不要全局一套。技术文档和闲聊记录的最优块大小差很多,按文档类型分组配置,比统一值更稳。还可以给块打"类型标签",检索时按类型调整召回策略——表格块和叙述块本就该区别对待,混为一谈会拉低整体质量。
长文档处理的关键,是把"读全文"拆成"定位 + 分块加工 + 受控合并",用结构换可控性,并承认摘要有它覆盖不到的全局视角。
浏览 0
点赞 0
评论 0