本地AI办公助手:文档分片与L0硬规则调度实战解析

发布时间:2026/9/26 20:04:40
本地AI办公助手:文档分片与L0硬规则调度实战解析 先说个背景。我一直在搞一个基于 Node.js 的本地 AI 办公助手模型用的是 Ollama 拉下来的开源模型跑在公司内网一台闲置工作站上。做到中途我发现自己掉进了一个很尴尬的坑模型侧其实没怎么折腾就通了真正让我连续加了好几个夜班的是“喂给模型之前的那一坨文件数据”。本地模型跟云端大模型的差别用起来才体会得到。上下文窗口有限推理速度慢指令跟随能力也比商业大模型弱一截。把一份几十页的 Word或者一个几万行的 Excel 直接怼给它结果大概率有两种要么上下文被塞爆回复到一半直接断掉要么模型盯着局部文本开始自由发挥完全没把文件当整体看。问题压根不出在模型身上而是数据没有做前置处理。所以我把方向改了——不在模型调优上死磕转而在模型前面加了一个前置预处理模块。核心做两件事一是办公文档的解析、清洗、分片把原始文件变成模型能吃的结构化文本块二是在模型之前加一层“L0 自然语言硬规则调度”用确定性的轻量规则先把简单任务消化掉只有真正复杂的任务才放给大模型。这篇文章就是把这两块内容、以及我在实操中踩过的一堆坑完整复盘一遍。1. 这个项目到底在解决什么问题1.1 本地 AI 最真实的痛点不是模型是“前置数据”先说我最初接到的需求内网环境里团队有大量本地办公文档Word、Excel、PDF、PPT 都有大家希望用一个本地 AI 助手来自动完成“整理会议纪要、汇总销售数据、抽取合同关键信息”这类事情。一开始我图省事想直接把文件路径扔给本地模型让它自己“看”文件。结果第一轮测试就翻车了。本地模型根本不会去看文件本身你给它一个路径它只能看到路径字符串你让它读一个 docx它压根解不开 zip 包里的 XML 结构。就算我先把文件转成了文本直接把几万字一次性塞进上下文本地模型的处理质量也惨不忍睹。上下文一长注意力就涣散经常出现“前面提到的数据后面就忘了”的情况。说白了本地 AI 的推理能力本来就要省着用你越是不加筛选地把原始数据喂进去它越容易把算力浪费在无关紧要的噪声上。后来我还做了个统计在一周的实际请求里大约 70% 的任务其实都是确定性任务从文档里提取电话、按照模板生成摘要、把表格按某一列做聚合。这些任务用规则引擎处理又快又准根本不需要大模型上场。这说明什么说明我缺的不是一个更强的模型而是一个能把数据“收拾干净”、把任务“分好类”的前置预处理层。1.2 前置预处理模块拆开看就三块整个链路如果用文字描述大概是这样的上传文件 → 文件类型识别 → 文档解析与清洗 → 分片 → L0 规则匹配 → 命中则直接走规则处理器未命中则组装上下文交给本地模型 → 结果返回。前置预处理模块拆开看就是三块文档解析把 docx、pdf、xlsx、pptx 这些二进制格式转成纯文本或半结构化中间表示分片与清洗把长文本切成符合模型上下文要求的片段同时去掉页眉页脚、重复空白、特殊符号等干扰内容L0 调度对用户指令和文档内容做一层确定性规则匹配决定这个任务该走哪条执行链路。这三块在同一条流水线上串着但逻辑上完全独立。第一块做不好后面分片切出来的全是乱码第二块做不好模型看到的就是断裂、重复、没有逻辑的碎片文本第三块做不好你就是在拿“核弹打蚊子”——明明一个正则就能解决的事非要让本地模型吭哧吭哧推理半天结果还不稳定。所以我特别建议如果你也在做本地 AI 方向的工具链别急着调模型参数先花时间把前置数据链路打通。这个投入的性价比远比你想象中高。1.3 “L0 自然语言硬规则调度”是什么很多朋友第一次听“L0 自然语言硬规则调度”这个词觉得绕。我拆开解释一下。“L0”就是层级结构里的第 0 层最靠前、最轻量、成本最低的一道闸门。对应的是一个确定性的规则层规则匹配结果是固定的命中了就是命中了没命中就是没命中不会有“模型今天心情不好给你胡诌一句”的情况。“自然语言硬规则”重点在“硬”。规则的定义可以用接近自然语言的文本来写比如“如果用户输入中包含‘提取联系电话’或‘整理号码’则走电话抽取处理器”。但规则一旦被加载就编译成正则、关键词列表、阈值比较这些确定性的计算逻辑不会出现“大概、也许、应该可以”的模糊结果。“调度”就更好理解了。一个文档进来该走“PDF 解析正则抽取”还是该走“分片LLM 摘要”又或者是“Excel 聚合并直接算数”需要一个调度器来做决策。传统做法是写一大坨 if-else但规则一多if-else 就变成无法维护的灾难。L0 规则引擎就是把决策逻辑抽出来做成可配置、可排序、可命中的规则表让调度行为有据可查。我常说L0 层是给本地 AI 装的一个“前置小脑”。它不替代大模型而是把大量确定性任务拦在第一道关口让大模型只处理那些真正需要语义理解的复杂场景。2. 办公文档分片的思路与实操2.1 先把文档分类再谈分片文档分片不是拿到文本就无脑按字数切。我踩过的第一个认知上的坑就是以为“分片固定长度切字符串”。实际上办公文档之间差异巨大分片策略必须跟着文档类型走。我自己把办公文档分成两大类线性文本型docx、pdf、txt、md。这类文档读起来是连续的分片时重点考虑段落边界、标题层级、句子完整性结构化表格型xlsx、csv、以及 docx 里的表格区域。这类文档的价值在行列关系里切片的单位不是“行文长度”而是“表头若干行”组成的一个数据块。举个例子。同一份销售报告Word 版可以用“按章节标题切分”的策略先把“一季度总结”“二季度规划”“附录数据”分开再对超过上下文长度的大章节做二次切分。而 Excel 版如果也按字符数切数据行被拦腰截断模型根本看不出字段对应关系等于白切。所以处理 Excel 时我一般按“表头50~100 行”为一个单元做分块在块与块之间保留表头上下文这样每个块都自解释。PPT 又不一样。pptx 的文本天然按“幻灯片”组织每页幻灯片就是天然的分片单元。直接把每页的文字抽出来作为一个 chunk再配上页码标记就足够给模型使用了。比强行按字数拼接要合理得多。2.2 分片策略顺着文档骨架切具体怎么做我现在的标准流程是“先抽骨架再切内容”。第一步解析文档时尽量保留结构信息。比如用 mammoth 把 docx 转成 HTML而不是纯文本因为 HTML 里带了标题层级h1/h2/h3、段落、列表、表格这些标签。这些标签就是文档骨架分片时可以顺着它们走。第二步设计分片单位。我常用的规则是优先按“一级/二级标题”切分把每个章节作为一个大块如果某个章节仍然超过上下文限制再按段落聚合把连续的段落合成一块表格区域单独处理表头单独保留正文按行数分块代码块、引用块不要拆散整体保留。第三步控制块大小。我这里说的“大小”不是字符数而是 token 数。本地模型上下文一般是 8k 或 16k token但你不要顶满我建议给主模型留20%~30%的余量用于放系统提示词、用户指令和模型输出空间。比如上下文是 8k那么分片块的有效目标大小就设在 1.5k~2k token 之间。这个数值下既能保证单块信息密度够用又能给多轮对话留出拼接空间。2.3 分片的重叠与边界兜底分片最容易出问题的地方在边界。你按段落切分看起来没问题但段落的末尾往往是个半截句子你按标题切分标题下的第一段可能依赖上一章的定义或上下文。如果模型拿到的每个 chunk 都是断头断尾的信息丢失率会非常高。我的做法是引入“重叠窗口”机制。每个 chunk 除了主体内容还会带上上一块末尾的一小段长度大约 100~200 个 token。这样虽然会带来一些重复存储的浪费但换来的是模型在任何一块里都能看到相对完整的上下文。实测下来对摘要类任务的质量提升非常明显。还有一个被很多人忽略的兜底检查分片完成后对每一块做“完整性校验”。我写了几个简单规则块的结尾不能处于一个句子中间如果最后一句没有以句号、问号、叹号结束就吞掉半个句子把它并入下一块的开头表格分块时每一块必须包含完整的表头引用、列表、代码块如果跨越了块的边界直接把整个块挪到下一块去不要硬切。这些规则看起来琐碎但在实际效果上它们比任何“聪明”的分片算法都顶用。2.4 不同类型文档的参数推荐我整理了一份参数配置表直接抄作业就行文档类型分片单元块大小token重叠token注意事项docx / word标题段落1500~2000150保留标题层级表格单独处理pdf章节段落1200~1800150PDF 常有多栏布局解析时要先做栏序还原xlsx / csv表头数据行每块 50~100 行保留表头数据量大的先做列裁剪剔除无关列pptx每页幻灯片每页一个块无页码标记保留方便后续定位txt / md段落聚合2000~2500200md 文件保留标题标记代码块整体保留这个表是我反复磨合出来的经验值不同模型可以微调但大方向不会错。3. L0 自然语言硬规则调度怎么落地3.1 硬规则 vs 大模型不是二选一是上下级我在项目一开始也纠结过既然已经接了本地大模型为什么还要搞一套规则引擎这不就是把问题复杂化吗后来实际跑通了才发现这类系统里规则和大模型根本不是替代关系而是上下级关系。大模型是“兜底的万能执行者”但它的成本高、速度慢、结果不确定规则呢速度快、成本低、结果百分百可预期。一个健康的架构一定是用低成本的确定性规则处理绝大多数简单请求只把少量高难度请求留给大模型。举个具体例子。用户上传一个 PDF说“帮我提取里面的联系电话”。这件事本质上就是个正则匹配任务。如果走大模型你得把 PDF 分片、塞上下文、等推理运气不好模型还会编一个假电话出来。但如果 L0 层先识别出“提取联系电话”这个意图然后直接调一个电话号码抽取处理器用正则把符合手机号格式的字符串全捞出来再去重、格式化整个过程不到 100 毫秒结果百分百靠谱。这才是 L0 层存在的意义。它不是一个可有可无的装饰而是整个系统能不能“快、稳、省”的关键。3.2 规则引擎的三个基础件我设计 L0 规则引擎时只保留了三个基础件规则表、匹配器、优先级仲裁器。规则表是核心。每条规则包含几个字段规则 ID、任务名称、触发条件正则或关键词列表、执行路由、优先级、超时时间。规则文本尽量写得接近自然语言方便维护但最终编译结果必须确定性。匹配器的任务很简单拿用户指令和文档类型去遍历规则表返回命中的规则列表。这里有个细节匹配不是只在用户指令里跑正则还要结合文档元信息比如文件扩展名、文件大小、解析出的正文前 200 个字符。为什么呢因为“文档类型”本身就是一个极强的前置条件。一个 .xlsx 文件你就基本不用考虑“论文写作总结”这种规则一个 .pptx 文件“提取表格里的业绩数据”这条规则也可以直接跳过。优先级仲裁器解决的是“规则命中多条怎么办”的问题。我处理的原则很简单优先级数值大的先执行如果两条规则优先级一样就按规则表里的顺序执行执行完一条规则且结果有效就不再执行后面的规则。3.3 一条文档进来后的完整调度链路写一个真实的例子。用户上传了一份 Excel 销售明细指令是“按地区汇总销售额”。L0 层会这样走文件类型识别模块返回 .xlsx解析器用 exceljs 把文件读进来抽取表头和数据行指令匹配规则表命中“表格聚合”规则规则条件是指令中包含“汇总/统计/按…聚合/求和”等关键词规则引擎检查前置条件文件类型必须是表格类、数据行列数必须大于等于 2 列命中后直接走“聚合处理器”在 Node.js 里用数组 reduce 按“地区”字段分组对“销售额”字段求和输出一张聚合结果表整个链路不经过大模型耗时在几百毫秒级别。如果同样的文件指令变成“帮我写一段这个表格的分析总结”那 L0 层怎么判断规则表里可能没有一条规则能覆盖“写分析总结”这个动作或者匹配到的规则置信度很低那 L0 层就会把任务降级走分片模块把表格按“表头数据行”切块再组装进提示词模板交给本地大模型去生成。这就是“硬规则优先大模型兜底”的协作逻辑。L0 层的工作不是把所有任务都拦下来而是在该拦的地方拦、该放的地方放。3.4 规则与大模型的协作兜底还有一个容易被忽视的点规则的“置信度”和“降级策略”。我在规则表里并不是简单地“命中就执行”而是会给每条规则配一个置信度阈值。比如“提取电话”这条规则命中关键词后置信度给 0.95但如果指令是“帮我整理一下文档里的信息包括电话和邮箱”关键词也命中了“电话”但指令其实包含了更多语义这时候置信度只能给到 0.6 以下。对于低置信度命中我的策略是“不完全阻断大模型”而是先执行规则处理再把规则结果和大模型的输出合并或者直接把规则结果作为提示词上下文喂给大模型参考。这种“规则模型”的混合模式在处理真实办公场景时的效果明显强于单走任何一条路。比如第 6 章我会提到我最后把整个链路做成了规则引擎先做一次粗筛能确定完成的就地完成不能确定的生成一个“半结构化草稿”再把草稿和原文一起交给模型精修。这样既保证了确定性任务的稳定性又没丢掉大模型在复杂语义任务上的灵活性。4. 关键代码实现与配置细节4.1 基于 mammoth exceljs 的文档解析Node.js 生态里办公文档解析我目前用得最顺的组合是docx 用 mammothxlsx 用 exceljsPDF 用 pdf-parsePPT 用 pptx-parser 或直接解压 XML。一段很典型的 docx 解析代码const mammoth require(mammoth); async function extractDocxText(buffer) { // 只抽纯文本如果后面需要标题结构改用 convertToHtml const result await mammoth.extractRawText({ buffer }); return result.value; }注意几点。第一extractRawText返回的是纯文本如果文档里有很多表格表格内容会按“逐行拼接”的方式出现可读性很差。我一般改用convertToHtml拿到带标签的 HTML再用 cheerio 抽取结构化的行文和表格效果会好很多。第二mammoth 对 WPS 保存的 docx 兼容性有个别问题后面踩坑部分我会详细说。Excel 解析我用 exceljs但这里有一个非常重要的性能分水岭文件小比如 1 万行以内可以老老实实用常规 API文件大超过 2 万行就必须开流式读取。流式示例const ExcelJS require(exceljs); async function readExcelStream(buffer) { const workbook new ExcelJS.Workbook(); await workbook.xlsx.read(buffer); // 小文件常规读法 // 大文件时把 buffer 换成 read stream并开启 stream: true // const workbook new ExcelJS.Workbook(); // await workbook.xlsx.read(stream, { stream: true }); }这个大文件坑我在第五章会专门展开这里先说结论能用流式就别一次性加载Node 的内存没你想象中那么扛得住。4.2 分片与清洗模块分片模块的核心是一个带重叠窗口的切分函数。我用的是逻辑比较直白的分法先按段落拆分再按目标 token 数合块。const { encoding_for_model } require(dqbd/tiktoken); // 本地 tokenizer const enc encoding_for_model(gpt-3.5-turbo); // 换成本地模型对应的 tokenizer function estimateTokens(text) { return enc.encode(text).length; } function splitIntoChunks(text, maxTokens 1600, overlapTokens 150) { const paragraphs text.split(/\n\s*\n/); const chunks []; let currentChunk ; let currentTokens 0; for (const para of paragraphs) { const paraTokens estimateTokens(para); // 单个段落就超限做硬切 if (paraTokens maxTokens) { if (currentChunk) chunks.push(currentChunk.trim()); chunks.push(...hardSplit(para, maxTokens)); currentChunk ; currentTokens 0; continue; } if (currentTokens paraTokens maxTokens) { currentChunk \n\n para; currentTokens paraTokens; } else { if (currentChunk) { chunks.push(currentChunk.trim()); currentChunk getLastTokens(currentChunk, overlapTokens); currentTokens estimateTokens(currentChunk); } currentChunk \n\n para; currentTokens estimateTokens(currentChunk); } } if (currentChunk.trim()) chunks.push(currentChunk.trim()); return chunks; }这个实现不追求优雅但足够稳。我特别想在代码上标注一个容易忽略的点段落之间的分隔是\n\n不是单个换行。因为单换行在很多文档里只是软换行硬用\n切分会把同一段落拦腰截断。4.3 L0 规则引擎骨架规则引擎的核心代码其实很短。规则表我用一个数组存匹配器就是 filter 加排序仲裁器就是一个 for 循环。const rules [ { id: R001, name: 提取电话, pattern: /(?:提取|整理|找出|汇总).{0,6}(?:联系|手机|固定)?(?:电话|号码)/, requiredFileType: [pdf, docx, pptx, txt], route: phoneExtractor, priority: 90, confidence: 0.95, }, { id: R002, name: 按字段聚合, pattern: /(?:按|根据)([\u4e00-\u9fa5]{1,6})(?:汇总|统计|求和|整合)/, requiredFileType: [xlsx, csv], route: excelAggregator, priority: 80, confidence: 0.9, }, // ... ]; function matchL0Rules({ instruction, fileType, contentPreview }) { return rules .filter((rule) { if (rule.requiredFileType !rule.requiredFileType.includes(fileType)) { return false; } return rule.pattern.test(instruction); }) .sort((a, b) b.priority - a.priority); }实际使用中我还会给每条规则加一个enabled开关方便灰度发布和临时下线问题规则。毕竟正则这种东西藏着太多你测试时候测不出来的边缘 case。5. 踩坑实录这些问题我花了几个晚上才解决5.1 文档编码的隐形坑做中文办公文档处理编码是绕不开的。我遇到的最典型情况是同事从 Windows 导出一个文本文件内容是 GBK 编码我用 Node 的fs.readFile读取后直接toString(utf-8)中文变成一坨乱码模型看着乱码当然什么都干不了。排查过程很简单但发现之前很闹心。我先打印 buffer 前几个字节做十六进制判断发现没有 UTF-8 BOM再用 jschardet 测编码类型确认是 GBK。解决也很直接用iconv-lite转码就能搞定const iconv require(iconv-lite); const content iconv.decode(buffer, gbk);还有一个更隐蔽的坑WPS 保存的 docx在解析时偶尔会碰到内部缺少关系文件的问题mammoth 直接抛异常。我一开始以为是我代码写错了后来打印堆栈才发现是文件本身结构不标准。对这个坑的处理办法是解析前先用一个轻量文件头校验判断 docx 的 zip 结构是否完整如果 mAMMoth 抛异常就退回去用 unzip 手动解包 document.xml 再抽文本。这个兜底代码虽然丑但在国内办公环境下很管用。5.2 Excel 大文件内存溢出从死等到流式这个问题我印象太深了。第一次压测我拿一个 7 万行、20 多列的销售明细 xlsx 测试exceljs 常规模式直接内存飙到 1.5G进程反复 GC最后 OOM 被杀。原因不复杂exceljs 常规读取会把整个工作簿的对象模型全放内存每个单元格都是一个对象7 万行乘以 20 列就是 140 万个对象再加上样式、公式、合并单元格信息内存不爆才怪。解决方案有两层。第一层能用 CSV 就用 CSV或者在业务侧要求导出时同时给一份 CSV第二层必须用 xlsx 文件时走流式读取只按行消费不保留完整工作簿模型。Node 里的流式 Excel 读取和“同步全量读”的内存消耗能差出一个数量级。实测同一份 7 万行文件流式模式内存稳定在 300M 以内。另外一个配套细节流式读取时不要在每个 data 事件里做异步的 LLM 调用否则会造成严重的背压问题。先把数据行攒成块再批量处理处理完一块再继续读下一段。5.3 正则回溯把服务打挂这个坑来自我写的一条规则目的是提取“包含金额的句子”正则写成了/(?:金额|价格)[\s\S]*(?:元|块)/。初看没问题但当输入文本很长、且全文根本没有“元”或“块”字时[\s\S]*会不停回溯查找可能的终配位置CPU 直接干到 100%接口超时。这个问题的专业名称叫“灾难性回溯”。排查时我用node --trace-gc和日志里的处理耗时异常定位到是正则耗尽了 CPU但真正让我意外的是一条规则就能拖垮整个服务。从那以后我给自己定了几条铁律正则里的.*、[\s\S]*必须搭配长度上限写成.{0,50}尽量使用非贪婪匹配正则匹配前先估算输入长度超过 10 万字符的文本一律先分块再匹配对每条规则加一个超时时间用 worker 里跑超时就终止匹配。后来我把这条规则改成/(?:金额|价格).{0,50}?(?:元|块)/问题立刻消失匹配准确率反而更高了。限制匹配长度不仅防回溯还让规则意图更精确。5.4 用 worker_threads 救回事件循环Node.js 是单线程事件循环这一点在处理大文档时表现得特别残酷。有一阵子一个 20MB 的 PDF 解析进来主线程同步解析加分片跑了三四秒钟这期间整个 HTTP 服务的所有请求全部 pending前端轮询接口也卡死看起来就像服务挂了。我第一反应是换异步库但文档解析这种事本身就是 CPU 密集型的再怎么异步也还是要占用主线程的计算资源。后来用worker_threads把解析、分片、规则匹配全部丢进子线程主线程只负责接收请求和返回结果问题才彻底解决。踩坑提示worker 池要限制数量不要文件一多就无限开线程内存照样会被吃光。我的经验是“CPU 核心数 - 1”四核机器开 3 个 worker 就够用了。每个 worker 处理完一个任务就回收不要长时间驻留一堆空闲线程。5.5 分片上下文断裂的细节处理分片之后我把 chunk 交给模型做摘要结果发现摘要质量忽高忽低。手动检查了几条样本发现一个共性问题很多 chunk 的第一句话是从半截句子开始的或者表格块没有表头模型拿到之后根本不知道这些数字代表什么。这其实是“分片上下文断裂”问题。除了之前说的重叠窗口还有一个关键细节分片点的选择必须尊重“自然结束位置”。如果一个大段落有 2000 个 token超过目标块大小你是硬切还是整段跳过我的答案是如果段落本身语义完整宁可让 chunk 超一点点控制在目标值的 30% 以内也不要为了对齐长度把它切碎。另外表格场景里我强制要求每个分块带表头。哪怕表头在前后两块里重复了 100 遍也不要为了省 token 去掉它。模型没有表头的情况下对数字列的解读几乎全靠猜这种错误在工作中是不能接受的。6. 优化扩展与个人体会6.1 能直接抄的性能优化清单经过这几个月的折腾我总结了一份简洁的优化清单你直接拿去用不会踩大坑文件先落本地缓存解析结果按文件哈希缓存同一个文件二次请求直接走缓存大文件先做快速预处理生成“文本指纹”做规则匹配时只跑指纹不跑全文分片结果落盘到临时目录不要每次请求都重新分片文档解析和规则匹配全部放进 worker_threads 池主线程只做编排规则匹配做到超时保护超时自动降级给大模型处理对超大 Excel 优先走流式 列裁剪只保留需要的列再做后续处理。这些优化做完之后单纯从接口耗时来看中位数从原来的 3 秒降到了 400 毫秒左右。效果最明显的是缓存和 worker 池基本能挡住 80% 的重复性能问题。6.2 和本地 AI 模型结合的进阶玩法L0 层跑通之后我越来越觉得这套架构的扩展性很强。比如我后来在规则引擎后面接了一个“指令归一化器”先用 L0 规则把用户指令里的口语化表达标准化成任务类型。像“帮我把这个表整理一下”“这个数据你给我看看”这类模糊指令先经过规则层归一化成“表格摘要”“数据体检”等明确任务再决定路由。这个改造对本地模型的效果提升很明显因为模型收到的不再是模糊指令而是规范化的任务描述输出质量自然更稳。另一个扩展方向是把分片结果和向量检索打通做本地知识库问答。分片模块输出的每个 chunk加上文档标题、页码、章节路径这些元数据一起入库做向量化。用户提问时L0 规则层先判断是“事实抽取型问题”还是“开放讨论型问题”前者走检索 TopK 再让模型做抽取式回答后者才走长文本全量上下文。这样能让本地模型在有限上下文下处理更大型的文档库。6.3 最后分享几个调试技巧收尾之前分享几个我在实际调试中觉得很顺手的小技巧。第一给规则引擎加一个“解释模式”。开启后每条规则的命中/不命中原因都会打到日志里格式类似R002 命中原因: 指令包含按地区汇总文件类型 xlsx 匹配置信度 0.9。这个功能在调规则时价值巨大能帮你快速定位“为什么这条规则没生效”。第二分片模块一定要做“分片预览”接口。把任意文件切完 chunk 后可以在浏览器里查看每个 chunk 的头尾各 100 个字。我有很多分片边界问题都是靠这个可视化方式发现的比看纯代码日志直观得多。第三所有解析器都要包一层“失败降级”。mammoth 失败了就降级到 unzip 手动抽 XMLexceljs 流式失败了就降级到 csv 解析连规则引擎匹配超时了都要能降级到“直接丢给大模型”。这个兜底设计能保证你的服务不会因为某一个解析器的小毛病就整体不可用。我在实际做这些优化的时候最大的感受是本地 AI 应用真正考人的不是模型本身而是工程化能力。什么时候用规则什么时候放模型每个环节怎么兜底这些细节串起来才是一个能真正落地跑起来的东西。这套分片加 L0 硬规则调度的架子我现在已经稳定支撑了公司内部几个固定的办公自动化流程后面还会继续往更多文档类型和任务场景上扩展。