
做本地智能体最烦的不是模型能力不够而是输入框根本装不下你的文档。有一回我处理一份两百多页的投标文件PDF解析完光文本就六万多字直接丢给模型调用接口那边立刻抛了一个上下文超限错误前半部分还白读了因为截断点正好落在关键报价表附近后面所有结论全是错的。那次之后我彻底放弃“一把梭”的思路把重心放在文档预处理、任务调度这两件看似不起眼的事上最后串成一条完整的本地智能体链路长文本超限问题才真正被解决。这篇就把我的完整做法、参数设计、踩过的坑都整理出来给正在做本地文档问答、批量文档分析、办公自动化的人一个可以直接抄作业的参考。1. 整体设计思路本地化、预处理、调度怎么串成一条链1.1 本地智能体不是聊天机器人而是一条数据流水线很多文章把智能体说成“能对话的AI”实操过你会发现办公场景里真正值钱的部分全在对话之前。文档要读取、清洗、切块、入库大模型调用要排队、限流、重试这些问题不解决对话框再聪明也白搭。我把它定位成一条流水线文件进、结果出中间每个环节都可观测、可替换。本地化的核心理由有两个一个是数据安全合同报价、人事制度这类东西不适合往外部API传另一个是可控性本地部署可以让每个环节的日志、缓存、失败原因都捏在自己手里。这个定位很重要因为它决定技术选型。如果只是简单调用云端接口你根本不需要队列和状态机但要走全链路调度就成了刚需。我的建议是哪怕你手头只有一两个文档处理需求也先把流水线搭出来后面扩展场景时不用推翻重来。部署方式也不用复杂一台16G内存的机器跑全部服务或者用Docker Compose按服务拆分都行关键是每层职责清晰。1.2 长文本超限的本质不是“不够长”而是“不会拆”模型上下文窗口有限很多人第一反应是“买更大的窗口”但实际办公场景里200页文档你真正需要模型看的可能只有其中两页。硬塞全文一来浪费token二来无关信息会稀释注意力导致回答质量反而下降。所以长文本超限的解法不是换更大的模型而是让系统在问答前就完成一次信息筛选。具体是三种能力拆文档切成若干语义完整的块筛根据问题召回最相关的块缩在必要时对长文档先生成摘要再回答。这三件事对应着后文要讲的预处理、检索和摘要策略。你先记住这个判断超限报错是表象信息管理才是内核。只要把文档提前处理好模型窗口大小反而不是瓶颈8K上下文足以应对绝大多数办公场景。1.3 全链路四个环节采集、预处理、调度、问答以我最后落地的系统为例链路分四层。采集层负责监听文件夹、读取邮件附件、扫描共享盘把文件路径和元信息写入任务表。预处理层负责解析格式、清洗乱码、切片、生成向量并把处理状态回写。调度层是所有任务的中枢负责触发、排队、并发限制、失败重试。问答层接收用户问题先从向量库检索再把命中的切片和问题一起交给大模型生成回答。这四层可以全部跑在同一台机器上也可以用Docker拆成四个服务。关键不是架构多复杂而是每一层的输入输出都是结构化数据。只要任务表里状态清晰哪怕某一层挂了重跑也能恢复不会出现“不知道处理到哪”的情况。我最初犯的错就是跳过采集层手动把文件扔进处理脚本结果文件一多整个人成了调度器。2. 文档预处理从原始文件到“模型能吃的干净文本”2.1 别直接用open()读文件格式解析要先归一化办公文档最常遇到的就是PDF、Word、Excel、PPT每种格式都有解析库但细节坑非常多。我的经验是分格式处理PDF用pdfplumber能处理文字版PDF带表格时效果比pypdf好Word用python-docx读取段落和表格分开处理因为表格里的文字顺序和段落完全不同Excel用openpyxl直接读取所有sheet不要只读第一个PPT用python-pptx只提取文本和表格图片里的文字交给OCR。这些库读取之后统一定义一个结构文档ID、来源文件、块序号、块内容、页码、表格位置等元信息。后面的切片和检索都依赖这个统一结构所以第一步必须做格式归一化。我见过有人在预处理阶段只保留纯文本结果页码信息全丢了后面定位“这份内容在原文第几页”时完全做不到。归一化还有一个容易被忽略的点统一字符集和换行符Windows和Mac的换行不一样混合来源的文档经常在这上面出问题。2.2 编码、乱码与特殊符号最容易翻车的细节中文办公文档最大的坑是编码。我自己遇到过从客户系统导出的TXT其实是GB18030编码直接用UTF-8读出来全是乱码。后来统一用chardet探测编码读取时指定编码。还有一个常见坑是带BOM的CSV第一列列名会多出一个看不见的字符处理数据时很容易踩雷。预处理里还要做三个清洗动作全角转半角中文标点除外、把连续空行压缩成一个、去掉不可见控制字符。另外PDF解析出来的文字经常会有单词间多余空格或者错误的断行尤其是双栏排版解析顺序会混乱。这类问题没有通用解法只能针对具体的模板加正则规则。我的做法是先把常见脏数据样本收集起来写一套清洗规则每次新文档类型进来再补充规则慢慢积累成一张“脏数据特征库”。2.3 长文本超限的第一道防线切片策略与重叠参数设计切片的目的是把长文档拆成独立、语义完整、又不超出模型上下文的小块。固定大小硬切是最省事的但效果最差因为可能一句话被拦腰截断检索和生成都会出问题。我用的是滑动窗口法设置chunk_size和overlap两个参数让相邻切片之间保留重叠内容。具体参数中文文本我一般用chunk_size1200字符overlap200字符。为什么是1200中文一个字符在大多数模型里约等于0.7到1个token1200字大约在800到1200 token之间常见的嵌入模型和生成模型的上下文都放得下再大就贴近上限批处理时容易超时。200字的重叠是为了保证切在段落中间时下一段还能“想起来”上一段的结尾。英文文本可以按词数切chunk_size300词、overlap50词的配比也够用。切片顺序也很关键。我建议优先选自然边界段落边界、句子边界其次才是字符数强制切。先按段落整理文本段落太长就递归往下切直到段落小于chunk_size段落太短就合并相邻段落直到接近chunk_size。这样切出来的块语义完整性远高于纯固定长度。如果是表格类内容不要把表格硬拆成两片宁可把整张表作为一块稍微超出阈值也没关系检索时再单独处理。2.4 预处理结果自检切片入库前必须过一遍切片不是切完就完事了。我每次批量预处理结束都会跑一个自检脚本统计三样东西切片总数、空切片数量、异常字符比例。空切片一般说明源文件有很多页是纯图片或解析失败异常字符比例高说明编码或OCR出了问题。自检通过后把切片写入本地向量库我用的sqlite-vec或者chroma都行数据量不大不需要ES同时把原始文本备份成JSON方便之后回溯。这一步多花五分钟后面问答环节出问题时能省一小时。还有一个小习惯给每批处理生成一份质量报告记录文件数量、切片数量、失败数量、耗时。下次调参前先翻报告用数据说话而不是靠感觉改参数。3. 任务调度把“人肉排队”变成自动化流水线3.1 为什么要引入调度文件一多脚本就乱了如果你的需求只是“今天处理这一份合同”手动跑脚本没问题。但我做这个系统是因为每周要处理几十份标书和合同这时候就会遇到三个问题同一时间多个文件要处理靠人力排队某一步失败需要重跑靠记忆容易漏新文件到了要立刻处理靠肉眼盯着文件夹太累。调度器要解决的就是这三件事怎么排队、怎么触发、怎么重试。我一开始用最简单的方案——一个Python脚本循环扫描目标文件夹有文件就处理。后来文件多了并发错乱失败重试也说不清楚才改成正式的队列加状态机。现在回想调度器不是给机器用的是给人用的——它把“下一步该干什么”这个脑力活从人脑转移到了系统里。3.2 任务队列与优先级核心还是状态管理任务队列的核心不是“队列”本身而是每个任务的当前状态。我定义了一张任务表字段包括任务ID、文件路径、任务类型解析、OCR、向量化等、状态、优先级、重试次数、创建时间、完成时间。本地环境数据量不大直接用SQLite存这张表比Redis还稳当重启服务也不会丢任务。优先级怎么定我一般只分两档普通和紧急。紧急任务插队到队列头部普通任务按创建时间排序。不需要搞复杂的权重计算办公场景里规则越简单越好。调度进程启动时从库中取出所有PENDING状态的任务按优先级排序后逐个处理。这里要特别提醒任务表一定要加唯一约束防止同一个文件被重复调度。我踩过这个坑同一个PDF被两个调度进程同时取走处理结果互相覆盖。3.3 定时触发、并发控制与失败重试定时触发我用APScheduler三类场景每天定时扫描共享目录、每N分钟检查一次任务表、新文件落地立即入队。代码大概这个样子from apscheduler.schedulers.blocking import BlockingScheduler scheduler BlockingScheduler() # 每天9点自动扫描 scheduler.add_job(scan_shared_folder, cron, hour9, minute0, idscan) # 每5分钟检查待处理任务 scheduler.add_job(process_queue, interval, minutes5, idqueue)并发控制是很多人会忽略的点。PDF解析、OCR这类任务非常吃CPU盲目开多线程反而拖垮机器。我用线程池限制并发数为2任务完成一个再接下一个。这里有个经验解析类任务用多进程而不是多线程因为CPU密集型的任务在多线程下反而因为GIL变慢只有模型调用这类IO密集型任务才适合协程或线程。失败重试也不建议无限重试。我设置最大重试次数为3次间隔按1分钟、5分钟、20分钟递增。超过3次后任务状态变成FAILED并写一条带堆栈的错误日志宁可人去看一眼也不要让它在队列里无限循环。设计调度系统时永远把“系统自己会挂”当成默认前提而不是意外情况。3.4 状态机与日志让每一步都看得见任务在管道里会经历这几个状态PENDING等待、PROCESSING处理中、SUCCESS成功、FAILED失败、RETRY待重试。我在每次状态切换时都会记录一条日志包含任务ID、操作用时、输入输出摘要。这样事后排查问题时不用靠回忆直接翻日志就能知道是哪一步挂的。日志我建议写到文件而不是只打控制台因为定时任务大概率在后台无人值守跑控制台的输出过几天就找不到了。按天拆分日志文件配合任务表里的状态字段基本能做到“任何一条数据从进到出全链路可追溯”。这听起来有点像运维方法论但在本地智能体场景里同样适用只是规模小一些。4. 长文本超限的四套实战解法4.1 检索增强问答让模型只读最相关的几段最常用也最推荐的做法是把文档切块向量化存进向量库。用户提问时先用相同嵌入模型把问题也向量化然后在库里做相似度检索取出最相关的3到5块再连同问题一起交给生成模型。这样无论原文档是10页还是200页最终进入模型上下文的只有几千字根本不会触发超限。这套方案的关键在嵌入模型选型。我是用本地的bge-m3效果比我之前用的那个小模型好很多中文语义召回准确率提升明显。参数上我一般设置top_k5相似度阈值0.45低于阈值的切片直接丢弃宁缺毋滥避免无关内容混进去污染答案。实测场景里一本两百页的技术手册用户问“这个接口的错误码有哪些”系统只检索出三块内容模型回答既准确又详细响应速度还快。4.2 摘要压缩对超长文档先做分层概括有些文档虽然长但你问的问题非常宏观比如“这份年度报告的核心结论是什么”。这时候检索切片反而麻烦不如直接对文档做摘要。方案是分层摘要先按章节切分每章调一次模型生成200字以内的摘要再把所有章节摘要合并成完整文档最后再生成总摘要。两层结构的好处是单次调用的输入都控制在窗口内不会超限。我实测下来一份三万字的年度报告章节摘要加总摘要总共调用十几次模型耗时大概两分钟输出一份800字以内的核心内容完全在一个对话里展示。这个方法适合政策文件、财报、合同条款这类“结论集中”的文档。注意摘要任务也会消耗token但比全文喂入便宜得多而且摘要结果可以缓存同一文档被问多次时直接复用。4.3 增量式拆分把大任务拆成子任务流水线还有一类情况是单步逻辑太重比如“把这份文档的核心信息提取成结构化表格”需要对全文做多次判断。这时候可以拆成子任务链先识别文档类型再提取关键字段然后校验字段完整性最后汇总输出。每一步只依赖上一步的输出上下文窗口自然不会爆。举个例子合同信息提取我拆成4步解析合同文本、识别甲乙双方名称与金额、检查金额格式与单位、汇总成台账。每一步单独调用模型输入输出都很小随便跑都稳。如果非要一步到位让模型“从合同里提取所有关键信息”长合同必超限而且输出质量也会因为信息太多而下滑。增量式拆分的额外好处是每一步都可以单独调试出问题时只需要重跑那一步不用整个任务推倒重来。4.4 动态降级超限估算与应急截断策略即使前面做了切片和调度偶尔还是会有单条文本超限的情况比如某个Excel单元格里放了三万字。我在调用模型前会先估算token数中文按字符数乘以0.8估算英文按词数乘以1.3估算。如果估算值超过模型上下文窗口的70%就自动降级。降级的优先级是先走摘要再走分块实在不行才做截断。举个例子上下文窗口是8k的话我设在5.6k触发降级。一个3000字的中文段落估算约2400 token安全一段两万字文本估算16000 token直接进入摘要流程不做全文喂入。这个保障逻辑保证了接口永远不会因为超限而报错因为它根本不会被送到超限的地步。动态降级策略让我在切换不同模型时很从容不管窗口是4K还是128K系统都能自适应不用改核心代码。5. 落地过程中的坑与排查实录5.1 常见问题速查表我用一张表把预处理和调度环节的高频问题整理出来放在运维手册里。遇到问题先查表能省下大量时间。现象可能原因解决方法PDF解析全是乱码扫描版PDF没走OCR引入OCR模块用PaddleOCR识别图片文字Excel只读到第一个sheetopenpyxl默认只读活动表遍历workbook.sheetnames逐个读取中文文件名入库后找不到文件名编码不一致统一用ID命名文件原文件名存入元信息模型调用经常超时没有做并发控制限制模型并发设超时时间并自动重试检索结果答非所问切片太碎或重叠不足调整chunk_size和overlap走语义边界切任务一直处于PROCESSING进程崩了没更新状态加心跳检查超时任务重置为RETRY向量库检索速度慢数据量大且没建索引用HNSW索引并限制检索范围5.2 我踩过的几个典型坑第一个坑是PDF双重解析。很多PDF工具包默认会提取页面的文本和图片图片里的文字和正文文本区域出现重复导致切片里同一段话出现两遍。我的解决办法是在解析阶段对同一页面做去重只保留文本层的内容图片层交给单独的OCR任务。第二个坑是切片的“重复幻觉”。重叠窗口导致同一句话可能在两个切片里都出现检索时如果两个切片都命中模型可能把重复内容当成文档真的重复写了回答时会出现啰嗦甚至错觉。我在召回阶段加了去重逻辑按原文位置去重保留首次出现的那块。这个问题不跑真实问答很难发现很多人会误以为是模型生成能力弱。第三个坑是调度器的死循环。刚开始失败重试没设上限有个坏文件名让任务反复失败反复跑日志一天刷了几千行。后来我把重试次数、错误类型都记录在任务表里连续三次失败直接标记FAILED不再重试问题立即缓解。现在我看任何任务队列第一件事就是确认重试上限和失败转移逻辑。5.3 性能调优的几条实操建议预处理阶段PDF解析和OCR是耗能大户建议把这两个任务做成独立服务用消息队列连起来避免互相拖累。向量化阶段是GPU密集如果机器没有GPU可以用CPU跑只是慢一点效果不变。我在一台旧笔记本上跑过两千页文档的向量化耗时大约半小时完全可接受。模型调用层面强烈建议加缓存。同一段文本的向量化结果、同一组切片的摘要结果都可以做成KV缓存命中缓存时直接返回。我实测过高频文档去重后模型调用量能下降三成以上。缓存过期策略也很简单按文档版本号失效文档重新处理了旧缓存就作废。还有一个容易被忽视的点中间产物一定要落盘。我开发阶段就因为没保留中间产物调了几次参数后想回溯对比效果只能重新跑一遍全量白白浪费了两个小时。从那以后每一批处理结束我都会生成一份JSON报告包含切片数、耗时、错误数。调试效率和之前相比完全不一样。本地智能体这个方向我做得越多越觉得难点从来不是模型而是模型外面的工程链路。文档预处理决定了输入质量任务调度决定了运行稳定性检索与摘要策略决定了超限问题能不能根治。如果你正被长文本超限折磨别急着换大窗口模型先把这三层打好。我现在的环境里8K上下文的模型处理两百页文档绰绰有余靠的全是链路设计。至于下一步我打算把定时报表生成和异常检测也接进这套流水线里让智能体从“回答问题”进化为“主动发现问题”。你手头的场景哪怕再小按这条链路走一遍积累的经验后面都会加倍还给你。