本地AI自动生成库存盘点表格:从数据清洗到结构化输出全流程
库存盘点表格这件事我有段时间看到就想皱眉。仓库几百个SKU纸质单据堆成一摞人工录入电子表格就得耗掉一整天录完还要反复核对任何一个数量填错后续的补货、财务对账全都跟着遭殃。于是我开始琢磨能不能让AI自动生成这份盘点表格而且整个处理全程跑在本地数据不出内网。试了几个月踩了不少坑总算跑通了一套相对稳定的流程。这篇文章就是把我完整的实操过程、踩坑记录还有可复现的方案整理出来给同样被盘点表格折磨的伙伴做参考。你不需要多深的AI基础能跑通几个脚本就行。1. 整体设计思路与方案选型1.1 传统盘点流程的四个瓶颈先说说我为什么要动这个念头。仓库月度盘点一般是这样走的打印空白盘点表库管员逐个货位清点并在纸上填写单据回收后专人录入电子表格再和账面数比对标注差异原因最后生成汇总报表。听起来不复杂实际跑起来全是时间黑洞。第一个瓶颈是录入慢。几千行记录就算两个人分工五笔加数字键也要敲两三个小时。更麻烦的是录错一个数很难发现往往要到对账环节才能察觉到时候根本不知道是录入错了还是盘点错了只能重新数一遍。第二个瓶颈是格式不统一。同样一种商品不同人写的完全不一样。有人写苹果 20有人写苹果 20箱还有人写苹果 二十箱。这些记录进到同一个表格以后后面想用公式做匹配、透视都做不了先得花大量时间手工对齐。第三个瓶颈是复核链路太长。盘点、录入、核对往往是不同人负责中间只要有一个人理解偏差后面全部跟着乱。纸质单据中间还可能丢失、折角、字迹模糊想追溯都没法追溯。第四个瓶颈是单证管理。纸质盘点表留档占地方电子版又散落在不同人手里下次盘点点数据的时候根本没有统一的基线。这四个瓶颈叠加下来AI能介入的价值点就很明确了。它不应该去替人盘货而是把杂乱的人类记录转成统一结构的表格这件事自动化这才是整个项目的核心判断。别本末倒置非要让模型做它不擅长的事。1.2 为什么坚持本地化而不是调用云端接口最早我也想过直接调云端大模型API毕竟方便识别能力又强丢一段文字进去就出来结构化结果。但库存盘点这个场景有几个绕不开的顾虑。第一库存数据是供应链的核心数据里面包含采购价、成本、供应商信息很多企业明确要求数据不能出内网光这一条就否掉一半云端方案。第二仓库不一定有稳定的外网有些库房在偏远物流园区一旦断网云端方案直接瘫痪而盘点又往往卡在月底、季末这种必须出结果的时间点。第三高频调用成本不低每个月盘一次、每次几千行长年累月算下来是一笔完全没有必要的开销。所以最终方案定为本地部署模型数据留在内网随时能跑也不用担心外传问题。代价是得准备一台内存过得去的机器不过这个后面细说其实门槛不高。1.3 端到端的任务分工整个系统不是一个大模型从头包到尾而是拆成了几个明确模块数据采集扫描、拍照、文字识别或者直接用已有电子记录转成文本预处理统一编码格式、剔除噪声、对齐字段模型解析本地大模型根据提示词把记录转成JSON或CSV程序校验检查完整性、合法性不合格的重新解析表格写入把结果写入电子表格并高亮异常项AI在整个链路里只负责解析与结构化这一环前后的数据准备工作全由脚本完成。这个分工非常关键。如果你指望让大模型从头到尾全包输出很容易失控最后还得人肉返工。我在前期测试里已经深刻体会过这一点。2. 数据准备与标准化处理2.1 原始数据的几种常见形态在正式让模型干活之前先得把手头的数据收拾齐整。实际仓库里盘点记录长什么样都有。一种是手写单据数字潦草单位缩写多比如箱直接写成一个叉。这种要么靠OCR引擎识别要么靠录入员先敲进电脑反正最终要转成纯文本。另一种是打印清单加手改仓库在预印清单上手写差异数量印刷模糊和手写笔迹叠在一起识别难度最高我的做法是固定用扫描件统一跑OCR不要有的用手机拍、有的用扫描仪否则光线和角度不一致识别效果差别很大。还有一种是手机拍照加语音转文字。有些库管习惯边走边看边念最后导出一段语音转写文本。这种文本里经常带口语词比如那个大概预处理阶段要专门过滤掉。最后一种是历史电子表格碎片。多个人维护过的表格列头五花八门有的叫品名有的叫商品名称有的单位写支有的写个。这些数据如果要并入统一模板清洗量并不小。不管哪种形态第一步都是把内容转成纯文本。文本进了程序后面才能统一处理否则每个格式都是单独的流程工作量会翻好几倍。2.2 字段规范与命名约定为什么一定要规范字段因为模型的强项是理解语义但输出的表格必须能被程序校验。如果字段没有事先约定模型今天输出品名 数量明天输出商品 实盘数后天的表格就没法对齐。我建议在最开始就定死六个核心字段SKU编码、品名、规格、单位、账面数、实盘数。差异值后面用公式计算就行不需要模型输出模型只负责前六列。字段示例说明SKU编码KJ-0234唯一标识格式统一品名苏打水标准商品名称不带修饰规格24瓶/箱包装信息单位箱计量单位账面数120系统内库存数量实盘数118实际清点数量这个模板可以直接照抄。如果后面需要批次号、生产日期、备注再额外加列但核心字段越少越好。模型输出面越小出错的概率越低这是硬道理。2.3 清洗规则和边界情况数据清洗里最容易踩的坑是你以为只有脏数据需要处理其实格式统一同样费时间。我总结了一套常规清洗规则全半角统一把中文逗号、括号统一替换成英文半角否则CSV解析时会莫名其妙多出字段数量单位拆分原始记录20箱/300瓶这种复合写法预处理阶段只保留主单位箱其余写进备注不让模型自己判断选哪个别名映射同一商品在不同单据里叫法不一致比如可乐和可口可乐必须在清洗阶段替换成标准品名不能指望模型去猜空值处理盘点表格里留空的格子程序统一填未盘不留给模型发挥清洗脚本建议先简单后复杂不需要一开始就做全。我的顺序是先处理格式类问题包括全半角、空白、非法字符再处理别名映射最后才处理业务规则类的合并问题。一步步来哪一步出问题都好定位。再强调一遍清洗规则必须在模型解析之前跑。如果把这些不规范文本直接丢给模型让它自己整理结果往往是一个字段对不齐、单位乱跳反而更麻烦。3. 本地模型环境的搭建与调用3.1 模型选型的三个参考维度本地部署大模型第一个问题就是选哪个模型。我不建议追最新最大的按自己的硬件选才是正路。参照三个维度来看。第一是参数量。普通办公室的台式机或者一台中等配置的服务器7B到14B级别的量化模型是最实用的档位。这个规模的开源大语言模型对中文文本的理解和结构化能力已经够用。再大的模型需要多卡或大显存很多小团队不具备这个条件硬上只会天天卡死。第二是量化精度。模型量化之后体积小、跑得快但理论上有精度损失。实测下来4bit量化在表格解析这种结构性任务上和满精度输出的差异基本可以忽略。所以直接上量化模型省内存又提速。第三是上下文窗口。盘点记录如果一次给太多模型上下文不足就会截断。建议至少选8K上下文的版本这样一次能处理两三百行记录效率比较舒服。补充一点运行环境只需要支持常用的本地推理框架就行不需要额外配置复杂的东西。CPU也能跑只是慢一些后面有性能数据可以参考。3.2 部署运行的参数配置模型部署好之后真正决定输出质量的是推理参数。我调试了很长时间最后固定下来几组参数基本没翻过车。temperature一定要低设置在0.1以下。温度越低输出越稳定表格解析任务不需要模型发挥创造性要的是确定性优先。top_p设置在0.5左右和低温配合进一步压低随机性。max_tokens按输出行数估算一般一行记录大约40到60个token300行记录就设置到20000以上宁可给多不能给少。repeat_penalty适度调高到1.1到1.2防止模型在长输出里自我重复。我之前有一版跑得不太对劲排查了半天发现是temperature设成了0.7。模型输出看似合理但字段顺序随机换位同一个商品一会儿写数量 20一会儿写20 数量后面校验脚本直接炸掉。把温度降下来之后立刻就好了。所以记住模板解析类的任务参数就往保守调不要让它自由发挥。3.3 用脚本封装模型调用部署完模型以后不管是本地推理服务还是命令行接口都建议统一封装成一个函数不要让业务代码直接依赖具体的调用方式。我习惯写一个简单的HTTP请求封装大概长这样import requests def chat_completion(prompt, historyNone, temperature0.1, max_tokens16384): url http://127.0.0.1:8000/v1/chat/completions headers {Content-Type: application/json} payload { messages: [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: prompt} ] (history or []), temperature: temperature, max_tokens: max_tokens, top_p: 0.5, stream: False } resp requests.post(url, jsonpayload, headersheaders, timeout300) resp.raise_for_status() return resp.json()[choices][0][message][content]注意超时时间一定要给足。本地模型首token可能慢长文档推理有时要跑一两分钟300秒是底线。如果没设置超时文档稍长就会被默认30秒掐断你只会看到报错根本不知道模型其实还在算。这套封装的好处是以后换模型、换端口只需要改url和参数不用动业务逻辑。我后来把预处理、解析、校验、写入都挂在同一个流程里这层封装起了很大作用维护成本低了很多。4. 提示词设计与表格生成实现4.1 一套可以直接复用的提示词模板提示词是整个流程里最值得反复打磨的部分。我迭代了很多版最后沉淀下来一套在实际环境中跑过几千行数据都稳定的模板。系统提示词的作用是定义角色和任务边界你是一名仓库数据录入专员负责把用户提供的盘点原始记录整理成结构化表格。 任务要求 1. 只输出CSV内容不输出任何解释、前言或表格之外的内容。 2. CSV第一行必须是表头SKU编码,品名,规格,单位,账面数,实盘数 3. 每条记录一行字段用英文逗号分隔。 4. 遇到无法识别的记录在实盘数列填写未识别不要擅自编造。 5. 数字保持原样不要四舍五入不要加千分位。 6. 单位统一用箱、瓶、件、包。原始记录里其他写法需转换为这四种。 7. 如果原始记录里没有账面数则账面数为空字符串。用户提示词就简单了直接粘贴清洗后的原始记录以下是仓库盘点原始记录文本请整理成CSV表格 [粘贴预处理后的文本]这段模板为什么有效因为它把角色、任务、输出边界、格式约束、异常策略五个维度都讲清楚了。模型输出时没有歧义只要原始数据本身不是乱到离谱结果基本一次过。4.2 让输出稳定可解析的四个技巧除了提示词模板本身还有四个关键操作决定输出稳定性。第一强制表头。必须在提示词里明确写出表头行模型照着抄就不会自己发明列名。列名一乱后面的解析脚本必炸。第二只输出CSV主体。如果允许模型带解释它会在末尾加一句以上是根据您提供的记录整理的结果这句话混进CSV就直接脏了。所以提示词里要写死不输出任何解释。第三用少量示例带节奏。如果某些记录格式很特殊比如包含批号20240506数量50箱这种连写文本可以在提示词里加一个用户示例和期望输出的示例。模型看到样例后处理同类文本的稳定性会提升非常明显。第四要求逐行对应。在提示词里强调保持与输入顺序一致这样程序可以把输出和输入逐行对比定位哪一行解析错了而不是整段作废。这些细节都是反复测试中总结出来的。最开始模板只写了整理成表格输出五花八门加了约束之后才稳定下来。4.3 自动校验与兜底机制模型输出的CSV不会百分百干净所以校验脚本是必要的。我的校验逻辑分四步import csv import io def validate_csv(raw_text, expected_rows): reader csv.reader(io.StringIO(raw_text)) rows list(reader) if not rows: raise ValueError(没有解析出任何行) header rows[0] expect_header [SKU编码, 品名, 规格, 单位, 账面数, 实盘数] if header ! expect_header: raise ValueError(f表头异常: {header}) if len(rows) - 1 ! expected_rows: raise ValueError(f行数不匹配: 期望{expected_rows}行, 实际{len(rows)-1}行) for i, row in enumerate(rows[1:], start2): if len(row) ! 6: raise ValueError(f第{i}行字段数异常: {len(row)}) if row[5] ! 未识别: try: float(row[5]) except ValueError: raise ValueError(f第{i}行实盘数非数字: {row[5]}) return rows校验失败的时候不要直接放弃。让程序自动重新请求一次把具体的错误信息拼进提示词再试比如上一次输出表头不对请严格按示例输出。重试一两次后八成能恢复正常。如果重试还不行就把原始文本标记为待人工处理缩小返工范围。这一步我强烈不建议省掉。早期我以为模型输出得够整齐跳过校验直接写入表格结果有一批单位全部错位排进电子表格才发现返工成本巨大。多花几秒钟做校验后面省几个小时。5. 常见问题与排查实录5.1 输出格式不稳定的排查顺序如果你遇到模型输出格式飘忽不定按这个顺序排查基本能定位到九成问题。先看temperature。凡是输出一会儿带表头一会儿不带、一会儿多一列一会儿少一列十有八九是温度太高先降到0.1以下再看。再看提示词里有没有只输出CSV不要额外内容这条。没有的话模型会在表格前后写好的以下是整理结果解析时直接被这些杂质干扰。再看上下文是不是太长。如果上下文接近窗口上限模型在后半段的输出质量会断崖式下跌表现就是前面格式正常、后面缺字段。最后才考虑模型本身的表现。如果以上几个都排除了还不稳定换一个量级更大的模型往往能解决。这个排查顺序我每次遇到问题都先过一遍节省了大量无头苍蝇式调试的时间。5.2 数量与单位识别错位的实战案例有一次盘点记录里有一行怡宝 550ml 20箱 480瓶我期望的结果是规格栏填550ml单位填箱实盘数填20。模型第一次输出把单位写成了瓶实盘数写成了480。排查下来发现问题出在原始文本里20箱 480瓶是一个复合数量模型不知道应该以哪个为准。解决方案是在预处理阶段就把复合数量拆成单值字段而不是丢给模型判断。我加了一条规则凡是一行里面有多个数字加单位的组合预处理程序把它们拆成主单位数量和备注两列主单位数量只保留第一个组合。这样模型收到的是已经降过噪的文本自然不会再选错。这也是整个流程里很重要的一条心得能用规则解决的问题就不要交给模型。模型只处理规则解决不了的部分才能发挥最大价值。这条原则同样适用于其他数据处理场景不只是盘点表格。5.3 大批量盘点时的性能优化最初跑整仓5000行记录一次性丢给模型结果等了好久还没跑完而且输出后半段质量明显下降。优化的思路是分批处理我按200行一批每批独立请求四个并发同时跑。处理时长从原来的二十多分钟压缩到了四分钟左右人工巡检一遍结构化结果直接导入电子表格。并发请求的写法很简单给请求函数加上一个线程池from concurrent.futures import ThreadPoolExecutor def process_batches(batches): with ThreadPoolExecutor(max_workers4) as executor: results list(executor.map(process_batch, batches)) return results这里要提醒一句并发数不是越多越好。本地模型的显存和内存都是瓶颈开太多并发会导致排队甚至OOM。我实测四路并发在中等配置的机器上比较合适两路更稳八路以上容易崩。还有个小技巧如果盘点的差异行不多可以先让脚本比对历史账面数和已完成的实盘数只把差异行丢给模型补充差异原因。这样模型需要处理的文本量大幅缩减准确率和速度都能上去。最后聊点个人体会。这套流程跑下来我最深的感受是AI自动生成表格并不是把模型一部署就完事真正花时间的反而是数据预处理、提示词约束和校验兜底这三件小事。模型本身不难选难的是给它划清楚边界哪些是它该干的哪些必须由规则来做。如果你也想在自己团队里落地这套流程建议从一次两周以内的试点开始拿最近一次盘点的原始记录跑通全流程再逐步扩大范围。先跑通再优化比一开始就想做一个完美系统靠谱得多。