时至今日,很多传统公司的大部分的数据依然使用office文件去管理,,把excel和图片当做数据库去管控是一个很常见的现象。
Excel、CSV、PDF、Word、邮件正文、聊天记录、扫描件、截图、各种系统导出的半结构化表。它们每天在业务人员之间流转,被下载、转发、改名、复制,再上传到另一个系统。文件很多,数据以一种极低效率的方式在企业中运转。而且这些数据往往有时效性,所以自然会有“过期”无用的数据。

可以说文件只是信息的载体。它还不是数据。
文件只是信息的载体,还不是数据。真正的数据,至少要能被查询、关联、复用,也要能解释它从哪里来。否则它只是一个看起来很完整的 JSON,出了错却没人敢用。
我最开始做这类文件解析时,也很自然地想过:能不能直接把文件扔给大模型,让大模型直接输出业务需要的JSON?
后来发现这个想法太轻了。
难点不在于模型能不能识别几个字段。难点在于系统敢不敢相信这些字段。企业文件有一个很现实的特点:他们最初是给人看的,不是给机器看的。
人看得懂标题、备注、合并单元格、上下文、颜色、空行、缩写、附件说明。机器不懂。机器看到的是行、列、段落、文本块、坐标、页码,还有一堆不稳定的表格:
同一个字段,在不同文件里可能叫不同名字。
同一个表格,今天从第 3 行开始,明天从第 7 行开始。
同一个业务对象,可能被拆在多个 sheet、多个段落、多个附件里。
有些数字是金额,有些数字是数量,有些数字只是备注里的编号。
所以想要引入AI去解决文件导入的问题,一开始如果就想着怎么引入大模型,然后使用大模型去把数据“转成JSON”,那这个问题开始就已经偏了。

所以一上来就问“怎么让大模型把文件转成 JSON”,问题已经偏了。更应该先问的是:哪些内容可以稳定识别?哪些内容必须结合上下文?哪些字段即使识别出来,也不能直接入库?哪些地方必须保留来源,方便后面复查?
企业更想要的是一条能解释、能复查、能修正的数据生产线。
先看整体流程
这张图看着长,其实核心就几件事:先认识文件,再拆开处理,再保留来源,再检查有没有漏,最后再决定能不能入库。
Excel 文件解析技术分析

阶段一 试图使用大模型去解析Excel/CSV数据
我们最开始试过一条很直接的路:把 Excel / CSV 转成 Markdown,再交给大模型理解,让模型输出符合业务 schema 的结果。
这个方案上手很快,非标准文本也能覆盖一些。但大表一来,问题就很明显:等待时间长、输出太大、容易触发 context window 或 max tokens,金额事实也不可控。
更麻烦的是,一旦字段错位,很难追溯到底是哪个单元格导致的。
阶段二 使用大模型去做layout处理
我们尝试过让 LLM 做 Layout detector,也尝试过轻量 Layout hint。
这个方向比“LLM 直接输出金额”更安全,因为模型只判断结构,不给事实金额。并且在这个过程中也尝试过引入MarkItDown。MarkItDown 可以把 Excel 转成更容易阅读的 Markdown。它适合:人工快速理解文件内容,离线分析某些文件的文本表达,给模型提供较友好的文本上下文。
但是它不适合作为正式解析主链路的原因主要在于:
Markdown 可能丢失合并单元格语义。
横向块和空表头列的坐标语义会变弱。
模型读 Markdown 后仍可能生成事实。
解析结果不容易回到 workbook row/column。
所以这个阶段我们都是考虑如何处理数据才能让大模型得以解析,但是慢慢发现一个问题,那就是如果后端已经能稳定识别结构,调用 LLM 只会增加等待。并且如果 LLM 输出完整 LayoutPlan,仍然有 schema 重和超时问题。如果只输出 hint,后端还需要负责 section 展开、坐标校验和 executor。
方案结论:
LLM layout 能力可以作为低置信复杂结构的辅助。 生产主链路仍应优先后端 resolver 和 executor。 当前 Local Fee 主链路不依赖 Layout Detector,LLM 只保留为后端解析结果校验器。
阶段三 后端规则 + 通用布局层 + LLM 校验器(当前方案)
对于Excel文件而言,不同的企业里面有着截然不同的使用方式,Excel不再是纯数据文件,同时承载着数据、布局、视觉表达和人工填写习惯。在真实的业务文件里,关键信息不是常来自某个固定字段,它可能来自sheet名,可能来自文件名,或者它可能来自标题行、表头甚至子表头。并且出现很多场景会合并单元格,有的列有的行又可能是空。所以Excel解析的目标不是“把所有文本读出来”,而是:把带布局语义的表格,转成业务可消费/可追溯/可复核的结构化事实。

这句话里有三个关键要求:
可消费:输出必须符合业务schema
可追溯:重要字段最好能回到sheet、row、column或原文片段
可复核:不确定的地方要进入missing / warning / confidence,不能静默编造
实际业务结合这是一个真实的需求,在海运报价的场景中,我们需要把报价文件的数据转变成运价的一个routes,关键的信息可能包含:船公司、币种,还要重要的数据比如起运港、目的港、中转港等,除此之外还要航线柜型对应的价格的信息,一条route对应包含有效期,除此之外还有其他数据就不在这里过多罗列。
而在另一个local费用的场景中,解析的目标不是routes,而是本地费用的模版,包含的信息则为船公司、起运港、费用项(各类的附加费和计费规则)、币种等。
消费方往往是上游查询的应用,解析结果最终会进入报价池,用于后续报价匹配。两个业务都在“解析excel”,但它们不是同一个解析任务,维度不同关注的数据也不一样。但是对于AI应用而言,数据又需要保持跟业务相关,且需要保证数据的准确性。
但是两者又有本质行为的相似之处,输入源都是Excel,底层文件读取模型调用或者是trace、token usage又可以复用。但是又需要将业务的schema、prompt、归一化、校验、失败策略拆开。
解决思路做 Excel 解析时,我现在不会先问“用哪个 OCR / LLM / 库”。我会先问几个更业务的问题。
第一,先定义业务对象。
最重要的入库对象是什么?它的业务主键是什么?哪些字段必填?哪些字段可以后续补?哪些字段绝对不能推断?它会不会直接参与金额计算或业务决策?
报价解析里的港口和柜型金额不能乱猜。Local Fee 里的金额也不能被模型结合常识补齐。很多解析问题,都是因为一开始没有把这个边界说清楚,最后把“读到了文本”误认为“解析成功”。
第二,再定义消费方。
同一份解析结果,给后台入库、给业务人员预览、给上游报价匹配系统使用,设计都不一样。哪些字段入库,哪些字段展示,哪些字段只作为 trace 保留,需要提前想清楚。
第三,再判断 Excel 的结构类型。
不要按客户名或文件名堆判断。真实 Excel 更适合先归纳成结构原语:标准表、分段表、宽表、横向块、文本块、混合表。
这一步要回答:业务实体在行上还是列上?金额是单列还是拆列?信息来自显示列,还是来自标题、sheet 名、文件名?备注和条件是不是混在某个单元格里?
第四,区分结构判断和事实读取。
LayoutPlan 只告诉系统“去哪里读”,不能代替事实本身。关键金额、柜型、费用项,最终应该由 executor 回读 workbook 原始单元格。
第五,设计观测字段。
至少要有 parseTrace、timings、missing、warning、confidence、source。否则解析失败时,只能靠人肉看文件,很快就会变成玄学。
实现路径我现在把链路拆成了几部分
1. 文件预处理只保留信息
文件预处理不应该过早去做业务解释,它更重要的职责在于:识别文件类型、读取workbook bytes、sheet文本和对Excel做必要内容的提取
可以说,它不应该决定哪个是业务模版,哪一列是金额,然后哪一行对应什么数据,模板是否覆盖这样的业务问题。
对于业务解释,应该交还给后面的parser、LayoutPlan、normalizer和人工确认
2. 高确定性规则先走规则parser
规则parser适合处理稳定、明确、可重复的格式,它不是大模型解析器,更多时候它是用工具进行解析的函数。这类规则特别适合处理:显示表头完整、列角色明确、section标题清晰、列表达固定等场景。
好处是,规则parser提供快路径:解析速度快、可测试、不消耗token、不依赖模型稳定性、出错时容易定位。但坏处是没法覆盖全部情况。
3. 复杂Excel引入布局中间层
真实的文件越来越以后,你会发现企业的数据是多样的,不可能每个文件都要写一个parser。企图处理所以场景,然后用枚举的方式去兼容是不现实的。更可维护的方式是引入布局中间层,我把这个层成为LayoutPlan。这个中间层主要描述sheet范围、行列范围、header行、数据起止行,还有meta数据的映射。
这样做的好处是新的Excel先归类为结构原语,不安客户名和文件名堆判断,并且executor 可以统一回读 workbook,validator 可以统一校验越界和缺轴。
4. Executor必须回读workbook
布局中间层只告诉后端“去哪里读”,但是不能代替后端读取事实,最终模版还是应该由executor从workbook原始单元格读取
这样做边界可以避免很多风险,比如模型看错数、模型补齐不存在的数据、markdown转换丢失坐标无法追溯。
5. LLM应该怎么用
大模型不是不能用,而是应该放在正确的位置:我更推荐使用LLM去做低置信解析结果的风险校验,或者去校验疑似错位字段,还有在 schema 很小的情况下输出 layout hint。最不推荐做的就是使用LLM 直接输出完整 workbook 解析结果并作为事实。
6. 输出必须经过schema和normalizer
无论结果来自规则还是 LLM,都应该经过统一输出层。输出层负责: 缺失字段进入missing。 不确定内容进入warnings。风险费用标记needsReview或displayOnly。这样可以避免每个 parser 都各自输出一套字段习惯。
最终我们的方案职责拆分如下:

这个组合的好处是:
标准文件快。
非标准文件可以扩展结构原语。
关键金额来自原始单元格。
LLM 有价值,但不控制事实。
trace 能解释每一步为什么这样解析。
工程化保障schema隔离
不同业务必须有不同 schema。报价解析和 Local Fee 解析如果共用一套 schema,短期看起来省事,长期一定会互相污染。
正确的做法是:底层能力复用,业务 schema、normalizer、测试样本分开。这样出问题时,才能知道是哪个业务、哪个模板、哪个规则出了问题。
Trace和Timings
真实样本回归
Instructor 是什么
Instructor 不是一个大模型,也不是 Agent 框架。它是一个围绕大模型调用的结构化输出工具。
更准确地说:
它把“请模型输出 JSON”变成“请模型输出符合 Pydantic schema 的对象”。
它会把模型原始输出解析成 Python 类型对象。
它会用 Pydantic 校验字段是否存在、类型是否正确、枚举值是否合法。
它可以在校验失败时自动重试,但本项目报价解析默认不建议开启重试。
from pydantic import BaseModel, Fieldfrom langchain_openai import ChatOpenAIimport instructorclassBusiness(BaseModel):# 模型定义llm = ChatOpenAI(model="gpt-3.5-turbo",api_key="你的openai密钥")instructor_llm = instructor.patch(llm)# invoke 直接返回校验后的结构化对象,无需手动解析JSONresult = instructor_llm.invoke(input=prompt,response_model=Business)# 关键:强制模型输出匹配该模型结构
它解决什么问题
在当前涉及大模型解析的业务场景中,模型经常需要返回 JSON。只靠 Prompt 约束会遇到这些问题:
返回半截 JSON。
字段名漂移,例如routes写成route_list
类型错误,例如价格应该是数字却输出"USD 3050"
嵌套结构缺字段,例如某条 route 没有pod_raw
枚举值不受控,例如status输出成“解析完成”而不是success。
Instructor 可以把这些问题前置为 schema 校验问题。
它不能解决什么问题
Instructor 不能替代 OCR、不能替代业务规则,也不能自动理解业务逻辑。它就像一个输出约束,它不能替代业务解析的能力,更不知道每个字段代表了什么含义,它只知道哪些字段应该存在。
所以它应该放在“结构化输出校验层”,不能被误用成“报价解析能力本身”。
推荐接入方式
当前边界是:
单轮模型调用优先。成功就是返回完整 JSON。失败就是返回失败状态和错误信息。不开放同步人工复核中间态。默认不做“失败后二次模型修复”。
因此 Instructor 的默认策略应该是:

这能避免为了修复 JSON 格式而额外调用模型,导致成本和耗时不可控。
文本解析技术分析最后附上我完整项目的时序图,这篇文章分享就到这里,感谢阅读。

