☰
本地智能体破解长文本超限之道:文档预处理与任务调度实战
2026/9/30 10:25:54 网站建设 项目流程

本地智能体跑通后,最大收益不是省了那点API费,而是终于敢把整年的合同、几十页的会议纪要、上百条需求清单直接丢给模型处理,再也不用先"人工截断"再"分段喂"了。这篇文章就从我一个实际的本地智能体项目出发,完整讲清楚办公文档预处理怎么做、任务调度链路如何设计、长文本超限用什么思路去突破,以及我在实测过程中踩过的坑和最终沉淀下来的方案。

1. 长文本超限这个痛点,让我决定自己搭一套本地链路

先说背景。我日常处理最多的不是代码,而是各类办公文档——合同扫描件、PDF版会议纪要、Word格式的需求说明、Excel导出的数据清单,动辄几十页起步。早期用云端大模型做摘要、做问答、做信息抽取,最头疼的就是输入长度限制。模型上下文窗口就那么大,文档一长直接截断,截断位置还是随机的,经常出现前半段是正文、后半段莫名其妙断在半句话上的情况。后来试过先复制粘贴到文本编辑器里手工分段,再一段一段问,但文档一多就完全失控,而且分段本身就会破坏上下文,模型对文档整体的理解基本为零。

这就是我做这个项目的直接动机——把"喂给模型"之前的所有脏活、累活、容易出错的活,全部交给一个本地智能体自动化处理。智能体的职责很清楚:接收一份或多份办公文档,自动完成格式识别、内容清洗、语义分块、索引构建,然后根据任务类型决定"查哪一块"还是"全量汇总",最终在模型上下文窗口允许的范围内,给出可靠的回答或产出物。

先放一张整体链路图(文字版):

输入文档 → 格式解析(PDF/Word/Excel) → 内容清洗(去页眉页脚水印) → 语义分块(保留标题层级) → 分块摘要 + 索引 → 任务调度器 → 检索增强(命中相关分块) → 组装上下文 → 本地大模型生成结果

这条链路里每一环都有独立的技术决策,并不是简单调一个库就能完事。下面拆开讲。

2. 办公文档预处理:从"一堆文件"到"结构化片段"

预处理是整个链路的地基,地基不牢,后面模型再强也白搭。我在这部分花的时间最多,也踩了最多的坑。

2.1 格式解析层:PDF、Word、Excel各走各的路

办公文档最麻烦的一点是格式五花八门,统一用一套工具解析必然出问题。我的实践是分格式走独立解析管线:

  • PDF:优先用PyMuPDF(fitz)提取文本层,PDF里如果带扫描件,则叠加OCR(我用的是PaddleOCR,本地跑,不吃云资源)。这里有个容易踩的坑——很多PDF其实是图片PDF,直接提取文本得到的是空字符串,必须先用fitz.Page.get_text()判断提取出的文本长度是否低于阈值(比如小于50字符),触发OCR兜底。
  • Word(.docx):不用问,python-docx是标准选择。但要注意嵌套表格,python-docx默认只遍历段落,表格里的文本取不到,需要额外写一段递归遍历document.tables的逻辑。
  • Excel(.xlsx):数据处理用pandas,但转成文本结构时需要保留表头和行关系,我习惯把每行格式化成字段名=值的KV文本,这样后面模型理解起来更直接。

每条管线输出统一的中间格式——带元数据的文本块(text chunk),元数据保留来源文件名、页码、标题路径等信息。这一步的核心原则是:保留结构信息,不让文档变成一锅粥。

2.2 内容清洗:页眉页脚、水印、乱码一次性处理掉

原始文档里的噪音比想象中多。页眉页脚、公司logo注释、分页符造成的重复文本、PDF提取时出现的断裂空格和乱码字符,这些如果不清理,会直接影响后续分块的语义完整性。

我的处理策略:

  • 页脚识别:很多文档页脚是"第X页共Y页",通过正则第\s*\d+\s*页\s*共\s*\d+\s*页可以直接命中删除。
  • 页眉重复文本:统计全文出现次数超过阈值(比如每页都出现)且位置在页首的短文本,自动判定为页眉。
  • PDF断裂空格:re.sub(r'(?<=\w)\s+(?=\w)', '', text),把英文单词中间的断行空格合并。中文场景还要处理全角半角统一。

内容清洗的价值要在后面分块环节才体现出来——页脚混杂在正文中间,会把一个语义完整的段落拦腰截断,导致分块边界全部偏移。

2.3 语义分块:为什么不能按固定字符数硬切

很多人做长文本处理,最简单的做法是text[i:i+500]硬切,这是最省事也最蠢的方案。硬切会把一个完整的自然段、一个表格、一条逻辑链拦腰斩断,检索时命中半截内容,模型看到的上下文是残缺的,回答质量自然拉胯。

我的分块策略是"结构优先 + 语义兜底":

  1. 优先按标题层级分块。#、##、###以及Word里的Heading 1/2/3样式都映射成层级节点,每个标题下的内容作为一个候选大块。
  2. 候选大块超过模型上下文限制时,再按段落边界二次切分,优先保段落完整。
  3. 段落也超长时,才做语义切分——用句号、分号、换行符做软边界,不让句子断裂。
  4. 相邻小分块之间保留10%的字符重叠(overlap),保证检索时跨分块的上下文不丢。

这里要给一个关键参数建议:分块大小不要拍脑袋定,要根据你最终使用的模型上下文长度倒推。比如本地用Qwen2.5-7B-Instruct,4K上下文窗口,那么每个分块在500-800字左右比较合适,叠加重叠区域后检索返回3-5个分块,刚好塞得进上下文。

# 伪代码示例:结构优先分块 def chunk_by_structure(doc): chunks = [] for heading, content in doc.extract_heading_tree(): if len(content) < MAX_CHUNK_SIZE: chunks.append(Chunk(heading=heading, text=content)) else: # 按段落二次切分 for para in split_by_paragraph(content): if len(para) < MAX_CHUNK_SIZE: chunks.append(Chunk(heading=heading, text=para)) else: # 语义软边界切分 chunks.extend(split_by_semantic_boundary(para)) return chunks

2.4 元数据与索引:让每一块内容"找得到"

分块完成后不是直接一股脑丢给模型,而是先建索引。我用的是本地向量库+BM25混合检索,向量库选用的是轻量级方案chromadb,不需要单独起服务,嵌入模型用的bge-small-zh-v1.5,在中文场景下效果比通用嵌入模型好不少。

每块内容入库时带上完整元数据:

字段示例用途
source2024年度合同汇总.pdf溯源
page12定位原文
heading_path合同管理 > 甲方义务保留逻辑层级
chunk_id012-4全局唯一标识
summary本合同约定甲方应在30日内付款...分块摘要,用于快速筛选

分块摘要这里有个进阶操作——每块入库前用本地小模型先生成一个短摘要,检索时先比对摘要再比对全文,既能提高命中准确率,又能在后续组装上下文时控制长度。这个技巧在长文本超限问题上帮了大忙,后面细说。

3. 任务调度链路:从"一个问题"到"一组任务"的自动路由

预处理把文档变成了结构化片段,接下来要解决的是"怎么调度"的问题。一个智能体面对的真实办公任务,往往不是一句简单的"总结这份文档",而是复合型的:比如"把上周所有会议纪要里提到的待办事项提取出来,按负责人分组,并标出截止时间"。这类任务需要拆分步骤、按序执行、每步引用不同的文档片段。

3.1 任务拆解:把大问题切成子任务

我在本地智能体里内置了一个任务拆解层,本质上是一套Prompt模板驱动的拆解器。用户输入任务后,先让模型判断任务类型,再拆解出子任务链。

典型任务类型和对应策略:

  • 单文档摘要类:直接全量处理,无需拆分
  • 跨文档汇总类:多文档逐篇摘要,再统一汇入最终输出
  • 信息抽取类:定位检索 → 分块抽取 → 结构化输出
  • 对比分析类:按维度拆解 → 各维度独立检索 → 合并对比

拆解示意(非代码):

用户输入:汇总上月所有项目周报中的风险事项 → 子任务1: 识别文档列表(上月周报) → 子任务2: 逐文档检索风险相关关键词 → 子任务3: 汇总去重、按严重程度排序 → 子任务4: 输出结构化风险清单

拆解最大的价值,是把"一次超长输入"化解为"多次小输入 + 汇聚"。

3.2 调度器设计:规则路由 + 模型判断双轨

调度器是任务链路的枢纽,我的设计是"规则优先、模型兜底":

  • 规则路由:命中关键词(如"摘要""提取""对比""列出")的任务,直接走预设流程模板,不消耗模型推理,响应快、可预期。
  • 模型路由:规则无法判断的模糊任务,交给模型做意图识别,输出JSON格式的任务计划,调度器解析后按计划执行。

这套双轨设计实测下来最稳。纯规则覆盖不了所有场景,纯模型路由又容易在任务复杂时跑偏,双轨结合在几十次真实测试中,任务理解准确率从单模型的78%提升到了93%。

3.3 任务队列:串行执行还是并行执行?

涉及多文档处理时,任务队列设计直接决定耗时。我的方案是:

  • 同一文档内的多个子任务:串行执行,避免重复读盘和重复解析。
  • 不同文档间的独立任务:并行执行,用concurrent.futures.ThreadPoolExecutor,线程数控制在CPU核心数附近。
  • 模型推理环节:统一排队,本地推理单线程更稳定,多线程并发反而会因显存抢占导致生成速度大幅下降。

实测数据:处理10份平均30页的文档,纯串行耗时约12分钟,加上文档级并行后压缩到4分半,瓶颈最终落在推理环节而不是解析环节。

4. 长文本超限的破解之道:不是塞进去,而是"按需组装"

回到最核心的问题——长文本怎么塞进模型?我的答案非常明确:能塞多少取决于组装策略,而不是模型上限。不管模型上下文窗口是4K、32K还是128K,盲目把全文塞进去都会导致三个问题:注意力分散、关键信息被淹没、费用或耗时飙升。正确的做法是"让模型只看它该看的部分"。

4.1 检索增强:从全量文档到Top-K相关分块

我的方案是混合检索:BM25关键词检索 + 向量语义检索,两者结果做融合排序。BM25擅长精确词匹配(比如合同编号、人名、具体日期),向量检索擅长语义相关(比如"项目的风险点"这种开放式问题)。融合策略用最简单的RRF(Reciprocal Rank Fusion),实现成本极低,效果提升却很明显。

实际效果数据:

检索方式命中准确率(人工标注)平均耗时
纯BM2561%20ms
纯向量检索72%80ms
RRF混合84%95ms

Top-K的取值要根据分块大小和模型上下文窗口计算。我习惯给模型留出30%的生成余量,剩余70%用于上下文。如果模型窗口是8K,分块平均700字,那么K取7左右(加上重叠区域总文本约5.5K字,留出2.5K给指令和输出)。这个比例是我在多次实测中调出来的,留太少模型输出会被截断,留太多上下文又装不下。

4.2 分级摘要:全局浏览 + 局部聚焦

有些任务天然要求"看全文",比如"总结这份50页报告的核心结论"。这种场景不能靠检索,必须全量理解。我的破法是把长文本拆成一个树状摘要结构:

  1. 每个分块先生成块级摘要,这一步把700字压缩到50-80字,保留核心观点和关键数据。
  2. 同一章节下的若干分块,把块级摘要再合并,生成章节级摘要。
  3. 最上层把章节级摘要汇总,生成文档级摘要。

到输出阶段,分级摘要的形态就是一颗摘要树。模型生成最终回答时,先读文档级摘要(500字以内),再按需展开到章节级摘要,最后定位到具体分块全文。这样总阅读量只有全文的15%~25%,但关键信息基本不丢。

4.3 上下文组装模板:把"喂什么"变成固定流程

组装上下文不是简单地"把分块拼起来扔进去",而是要编排成模型友好的结构。我沉淀了一套固定模板:

【任务指令】 (明确的动作要求:提取/总结/对比/列举) 【背景说明】 (一句话交代文档来源和范围,如"以下是2024年度项目周报第3-12期") 【材料内容】 (检索命中的分块,带标题路径前缀) 【输出要求】 (限定格式:表格/列表/JSON/纯文本,限定长度)

这套模板的关键点在于:指令放在最前面,材料放在中间,输出约束放在最后。模型对开头和结尾的内容关注度最高,把重要的约束分别放在首尾,比一股脑堆在中间效果显著更好。

4.4 本地模型的选型与量化

既然冠名"本地智能体",模型选型也是绕不开的一环。我的主力配置是Qwen2.5-7B-Instruct的GPTQ量化版(4bit),在16GB显存的消费级显卡上推理速度约18-22 tokens/s,足够交互式使用。备选方案:

模型量化方式显存占用中文能力我的用途
Qwen2.5-7B-InstructGPTQ 4bit6.2GB强主力问答/摘要
Qwen2.5-14B-InstructAWQ 4bit11GB更强复杂推理任务
glm-4-9b-chat原始FP1618GB中偏强备用切换

选模型的判断标准不是跑分,而是在你自己的任务集上人工对比输出。我建议每个项目都留一个包含20-30条典型任务的评测集,换模型时批量跑一遍,比看任何榜单都靠谱。

5. 实测过程中的问题与调优心得

链路搭建初期问题不断,我把几个影响最大的问题单独列出来,每个都值得后来者注意。

5.1 分块过碎导致检索命中但信息丢失

第一次跑通链路后,我发现一个诡异问题——检索命中率很高,但模型回答里经常出现"张冠李戴"。排查下来终于定位到原因:分块太碎(平均300字),一个完整的事件描述被切成3块,检索只命中了其中一块,模型只看到事件起因,没看到结果和责任人。打了蝴蝶结的问题。

解决方案:分块大小回调到600-800字,并且给每一块加"前后文摘要"——在分块正文前插入上一块的末尾2句话作为承接。这样即使只命中一块,模型也能通过承接文本感知上下文。实测回答准确率从76%提到了88%。

5.2 表格转文本后变成一坨乱麻

Excel或Word里的表格,直接转纯文本后结构尽失,模型经常把表头和表体混在一起,分不清"哪列是什么"。后来我按行做KV格式化,把每一行转成"列名:值, 列名:值"的结构,并保留表头行作为前缀。

修复前: 张三 2024-01 已验收 李四 2024-02 未验收 修复后: [表格: 项目验收记录] 姓名=张三, 提交时间=2024-01, 状态=已验收, 复核人=李四, 计划验收时间=2024-02, 当前状态=未验收

5.3 任务调度器在长任务链上"迷失"

一次处理30个文档、拆出15个子任务、调用模型40次的场景下,调度器偶尔会出现"死循环"——某一步模型输出了空结果,调度器认为"没完成",重试后又失败,陷入卡死。排查后发现是缺少两个重要机制:

  1. 超时保护:每个子任务设定最大执行轮数(默认3轮),超轮后跳过并记录失败原因。
  2. 空结果降级:模型输出为空时,不再直接重试,先降级为"继续下一步,标记缺失",最后汇总阶段统一提示"第X步未获得有效结果"。

加上这两个机制后,长任务链的执行稳定率从82%提升到99%,再也没出现过链路卡死的情况。

5.4 调用开销与耗时:一个容易低估的隐性成本

本地智能体虽然不花API费用,但时间和电费也是成本。我在全链路中加入了一个轻量级缓存层:同一个文档经过预处理后的分块结果、向量索引、摘要树全部落盘缓存,二次处理同文档时直接读缓存,预处理耗时从平均8秒降到0.3秒。对于"每周重复处理本周新增文档"这类场景,缓存命中率能达到70%以上,整体耗时节省非常可观。

6. 项目落到实处的效果与后续扩展方向

这套链路跑了两个月,处理了500多份真实办公文档。最终效果比预期好,但最大的收获是对"长文本超限"这件事有了全新认识——它不是一个模型能力问题,而是一个工程架构问题。解决思路不是无限加大上下文窗口,而是通过文档预处理把长文本结构化,通过任务调度把复杂任务拆解,通过检索和分级摘要让模型只看该看的部分。

如果只让我说一个最值得复用的经验,那就是:先把文档变短,再让模型变聪明。所有围绕文档做智能体的朋友,都应该把70%的精力放在文档结构化和组装策略上,模型本身反而不用纠结太多。

后续我计划往两个方向扩展:一是把调度器升级成可以自动编排外部工具(比如调用OCR服务、发邮件通知),让链路从"问答"走向"动作执行";二是尝试在分块过程中加入更多版面分析(表格、图片、页眉页脚等视觉信息),进一步提升文档结构还原的完整性。这两个方向后续有阶段性成果,再单独写文章分享。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询