ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

本地大模型驱动Excel库存盘点自动化流水线

本地大模型驱动Excel库存盘点自动化流水线

每到月底盘点日,我都要面对同一堆烂摊子:进货单、出库单、退换记录散落在不同Excel文件里,表头叫法五花八门,还有一堆合并单元格和手打的合计行。过去我都是打开表格手工筛选归类,再一个个核对数量,折腾四五个小时才凑出一张勉强能用的盘点表。后来我把这条流程改成了一条本地化的AI自动处理流水线:让本地大模型直接读原始流水,自动整理成规范库存盘点表格,全程不用上传数据到任何平台,也不依赖在线服务。这篇文章就是把这条流水线的完整做法写出来,包含工具选型、核心原理、完整代码和踩坑记录,适合经常和Excel台账打交道的仓管、采购、运营同学,也适合想在校内小项目里落地AI数据处理的学生团队参考。

1. 盘点表为什么必须自动化:手工流程的痛点与AI切入点

1.1 手工盘点表最常见的四类低效场景

我做过的库存整理工作,凡是需要人工处理的,翻来覆去就是这四个问题。

第一类是格式不统一。同一个仓库,入库单有人写成“进货数”,有人写成“来货量”,还有人直接写“+50”;出库单有的写“出库数”,有的写“销量”,还有的写“-30”。如果这些表来自不同的人或不同的月份,光是把字段名对齐就要花不少时间。

第二类是合并单元格和错位行。Excel里有人喜欢把同一种货品跨多行合并,还有人会在中间插一行“小计”“合计”之类的汇总行。程序读到这些行时,要么读出None,要么把汇总数当成普通数量,后续求和全部崩掉。

第三类是货品命名混乱。“苹果”和“苹 果”、“苹果(红富士)”和“红富士苹果”,在人工眼里是一回事,在Excel里却是完全不同的两行。盘点表要汇总,就得先把这些名称归一到同一个标准写法上。

第四类是版本管理混乱。每个人手里都有一份“最终版”,发来发去又变成“最终版2”“最终版3(定稿)”。等到真正要盘点时,根本说不清哪个文件才是截止到月底的那一份。

这些问题单个看不严重,叠加在一起就是一场灾难。更麻烦的是,它们恰恰是重复性最高、最不值得花人工时间的事情。我算过一笔账:假设仓库有500个SKU,手工整理一份盘点表平均要花3到6个小时,其中至少七成时间花在“看懂数据、对齐字段、归类文字”上,真正需要人来判断的数量核对反而只有很少一部分。

1.2 AI在盘点表场景里到底能做哪些事

有人一听到“AI生成盘点表”,第一反应是让AI凭空造一张表出来。这是个错误的理解。在库存盘点这个场景里,AI真正擅长的不是“生成”,而是理解和规整。

具体来说,本地大模型可以承担这么几个环节:

  • 字段语义识别:给它一段原始表头,它能判断出“进货数”“来货量”“+50”这些其实都表示入库数量。
  • 货品名归一:把“苹 果”“苹果(红富士)”“红富士苹果”归一到统一的“苹果”,或者归一到你指定的SKU主数据。
  • 记录分类和异常标注:它能根据备注里的“破损”“退换”“临期”等字样,给记录打上分类标记,方便后续人工优先处理。
  • 自然语言筛选:你可以直接问“找出所有数量为负数的出库记录”,它能把这些记录挑出来形成子表。
  • 结构化输出:它能把一段杂乱文本整理成固定的JSON结构,交给程序进一步处理。

这些工作的共同点是:有明确规则但不完全规则化,需要语言理解能力,同时重复量很大。这正好是大模型相对传统脚本的优势区域——传统脚本能处理“完全规整”的数据,但对“写得比较随意”的文本毫无办法,而仓库台账恰恰充满了随意写法。

1.3 哪些环节不适合交给AI

必须泼一盆冷水:AI只是辅助工具,不该碰库存管理中需要“担责”的环节。

盘点表的最终数量确认,不能交给AI。因为AI没有亲眼看过实物,它只能根据你给的文件做账面推算。账面数据再漂亮,和实物对不对得上,得靠人去仓库里数。另一个不能交出去的是财务对账。金额、成本、损耗这些涉及钱的数据,任何一步都应该有明确的计算公式和人工复核,不能依赖模型的模糊判断。还有一点:AI生成的盘点表只能作为“草表”或“参考表”,最终对外发布前,必须有专人审核签字。这不是技术问题,是流程责任问题。

我的原则是:AI负责把脏活累活干完,给人省出时间去处理真正需要判断的事情。自动化不代表甩手不管,而是把人的精力从“整理表格”挪到“核对异常”上。

2. 为什么偏向本地化:数据主权与可用性的胜负手

2.1 在线AI工具的三个顾虑

市面上的在线AI工具确实方便,粘贴一段数据就能出结果。但在库存数据这个场景,我始终不太敢用。不是工具不好用,而是有三个无法忽略的顾虑。

第一是数据上传风险。库存数据看着不起眼,实际上包含了SKU结构、进货渠道、销售节奏、库存成本这些信息,拼在一起能推断出不少商业情报。把整份台账粘贴到在线平台,等于把这些数据交给了平台方,哪怕只是训练数据,也让人不踏实。

第二是网络链路不稳定。在线工具依赖网络,而仓库或工厂的办公网络经常不稳定。卡在半路、请求超时、结果丢失都是常事。更麻烦的是,有些在线服务对单次请求的长度有限制,表格一长就得手动拆成好几段,来回粘贴非常痛苦。

第三是定制能力太弱。在线工具不会为你的仓库专门调整输出格式。我想要“货品编号+品名+规格+结存数量”的标准盘点表,它给我一段散文式的总结;我提示词写了半天,它还是按照自己的格式来。

当然要说清楚:如果你手头的库存数据完全不敏感、只有几十行、整理频率一年就两三次,用在线工具确实更省事。但如果你和我一样,要长期、反复、批量地处理库存台账,本地化几乎是必然选择。

2.2 本地化方案的取舍

本地化方案的核心思路是:把开源大模型部署在你自己的电脑或内网服务器上,数据全程不出内网,处理流程完全由你控制。

这样做换来的是三个明确的好处。一是数据封闭:原始台账、中间结果、最终盘点表都存在本地,模型不会把数据发到任何外部接口。二是可按需定制:提示词、输出格式、后处理脚本全部自己说了算,想怎么调就怎么调。三是离线可用:没有外网也能跑,断网断电都不影响在机房内网运行。

代价也很现实:本地模型的能力和云端商业模型有明显差距,尤其是复杂推理能力。7B量级的量化模型在理解长文本、处理多维嵌套逻辑时,明显不如云端大模型。此外,部署本身有门槛,普通办公电脑如果配置低(内存低于8G),跑模型会比较吃力,需要压缩模型规模或改用CPU推理的轻量版本。

我个人的感受是:这个取舍是值得的。因为盘点表场景不需要模型做复杂的创造性推理,只需要它稳定地完成“提取、归一、结构化”这些中等难度的任务,本地模型完全够用,而且胜在稳定可控。

2.3 软硬件准备清单

先别急着写代码,确认一下你的基础环境。我列一张清单,按重要性排序:

项目最低要求建议配置说明
内存8GB16GB以上决定能否流畅运行7B量化模型
CPU4核8核以上纯CPU推理速度更快
硬盘20GB空闲50GB以上存放模型文件和中间数据
操作系统Windows 10 / Ubuntu 20.04任意64位系统主要看能不能装Python环境
Python3.9+3.11+推荐用3.10以上版本
关键库pandas、openpyxl、requests同左核心数据处理库

软件层面不需要安装什么重型平台,我用的组合是:Python脚本 + pandas处理Excel + 本地大模型推理服务。本地模型通过一个兼容接口暴露给Python脚本调用,脚本负责读取原始表格、构造提示词、解析模型输出、写出最终盘点表。

其中pandas负责表格读写和数值计算,openpyxl负责处理Excel格式细节(比如识别合并单元格),requests负责和本地模型服务通信。这个组合非常轻量,安装简单,不需要GPU也能跑得动。

3. 核心思路:让AI“读表”而不是“生成表”

3.1 提示词设计决定输出质量

很多人以为本地模型效果差,其实是提示词没写对。盘点表场景的提示词,和平时聊天完全不是一回事,它需要的是一个带完整上下文的作业指令。

我写提示词的经验是:不要只说“帮我整理成盘点表”,而是要把目标表结构、字段含义、命名规则、输出格式、异常处理方式全部写清楚,最好再附一小段示例。模型拿到越明确的任务说明,输出的稳定性就越高。

我常用的提示词结构是这样的:

你是一个库存盘点数据整理助手。现在给你一段原始库存记录,请你完成以下任务: 1. 将每条记录归类为“入库”“出库”“退换”或“未知类型”。 2. 将货品名称做归一化处理:去掉多余空格、括号内容统一保留、同一货品合并成统一名称。 3. 输出JSON数组,每个元素包含字段:record_id、item_name、item_type、quantity、unit、record_date、note。 4. 如果某条记录信息不完整,在note字段中标注“信息缺失:xxx”,不要跳过。 原始记录: {原始数据文本} 输出格式(只输出JSON,不要额外解释): [{...}]

这里的核心是把“读表”逻辑讲透。模型不是一个表格编辑器,它是一个阅读理解引擎——你给它什么上下文,它就按你的要求提取和重组。它不需要知道仓库里有多少货,只需要根据给定文本做规范化输出。

3.2 结构化输出的两种实现方式

从模型拿到“能用的结果”,我在实践中积累出两种可靠方式。

方式A:让模型返回JSON,程序解析。我把一段原始台账文本塞进提示词,要求模型只回复一个JSON数组。脚本拿到后用json.loads解析。这种方式适合记录量中等(每次20到50行)、字段相对规整的场景。优点是结构清晰、后续处理方便;缺点是模型偶尔会在JSON前后加一些解释性文字,必须写一个清洗函数过滤掉Markdown代码块标记和多余文本。

方式B:让模型填充表格模板。我先定义好目标盘点表的列结构,把模板字段表放在提示词里,让模型按模板逐条填充。这种方式适合输出字段固定、层级简单的场景,但模型对模板的理解不如对JSON来得准确,所以我现在基本都用方式A。

无论用哪种方式,都建议在程序侧做一道Schema校验:检查返回结果是否是合法JSON,字段名是否全部存在,数量字段能否转换成数字。校验不通过的记录自动标记为“待人工处理”,而不是直接丢弃——这样可以防止模型偶尔抽风导致数据缺失。

3.3 先清洗后建模:数据预处理的两条铁律

我踩过最大的坑,就是把一坨乱七八糟的数据直接丢给模型。结果模型输出比原数据更乱,害得我花了更多时间去修正。后来我总结出两条铁律:原始数据太乱,AI也救不了;清洗环节必须放在模型调用之前。

铁律一:统一文本编码再进模型。我实际遇到的Excel文件,有的用UTF-8编码,有的用GBK编码,还有的是CSV格式但分隔符是中文逗号。如果不先把这些统一转成UTF-8纯文本,模型读到的就是乱码。清洗方法很简单:用pd.read_excel读入后,把非文本列全部转成字符串,再用str.replace清理多余空格和换行符。

铁律二:数值列先做基础运算,不需要模型代劳。比如“结存数量=期初+入库-出库”,这类加减运算直接用pandas做,比让模型用文字描述准确得多。让模型做复杂算术是幻觉的高发区,一定要避免。模型的职责是理解语义和归类,数据处理的计算部分永远交给脚本。

4. 从零搭建本地盘点表生成流水线(实操步骤)

4.1 第一步:准备好样本数据与表结构模板

任何自动化流程,第一步永远是定义“输入是什么、输出是什么”。我建议先准备一份典型的原始台账文件,以及一份目标盘点表的模板。

原始台账通常长这样(我拿一个虚拟例子说明):

Sheet“入库明细”: 合并单元格:水果类 货品编号 品名 进货日期 进货数量 备注 A001 苹 果 2024-11-02 50 红富士 A002 香蕉 2024-11-03 30 无 A003 苹果(红富士) 2024-11-05 60 精品果

同一份工作簿里还有“出库明细”“退换记录”等Sheet,字段各不相同。这些不规则内容,就是流水线要处理的“脏数据”。

目标盘点表模板我定义为:

货品编号 品名 规格 单位 期初数量 入库数量 出库数量 结存数量 异常标记

有了输入样例和输出模板,后面写脚本就有了坐标,不会被数据牵着鼻子走。

4.2 第二步:脚本读取原始表格并做基础清洗

核心脚本的第一步用pandas把Excel读进来,并把多个Sheet合并成一个DataFrame。这里有一个容易忽略的细节:合并单元格在pandas里读出来是NaN,必须用ffill()(向前填充)补上。

import pandas as pd def load_and_clean(file_path): sheets = pd.read_excel(file_path, sheet_name=None, header=None) frames = [] for sheet_name, df in sheets.items(): # 删除完全为空的行 df = df.dropna(how="all") # 如果第一列存在合并单元格,向前填充 df = df.ffill() frames.append(df) merged = pd.concat(frames, ignore_index=True) # 统一转成字符串,清理多余空格 merged = merged.astype(str).apply(lambda x: x.str.strip()) return merged

这一段做了三件事:把多Sheet合并、清除空行、规整文本格式。做完这些,数据就可以进提示词了。

有一点要提醒:不要试图让模型一次性读整个大文件。本地模型上下文长度有限,硬塞几千行进去,轻则速度极慢,重则直接截断导致结果残缺。正确做法是把数据切成小块(我一般每块20到30条记录),分批喂给模型,最后合并结果。

4.3 第三步:调用本地模型批量生成盘点记录

清洗之后,就可以把每块数据构造成提示词,发给本地模型服务。我这里用一个通用的调用方式:把提示词发送到本地推理服务的兼容接口,模型名用local-model代表。

import requests import json def call_local_model(prompt, url="http://127.0.0.1:8000/v1/chat/completions"): payload = { "model": "local-model", "messages": [{"role": "user", "content": prompt}], "temperature": 0.1, # 盘点场景要低温度,追求稳定 "max_tokens": 2000 } resp = requests.post(url, json=payload, timeout=120) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] def build_prompt(chunk, schema_text): prompt = f""" 你是库存盘点数据整理助手。请根据以下原始记录,按给定字段抽取并输出JSON数组。 字段定义: {schema_text} 要求: - 将“入库”“出库”“退换”等类型归类到type字段 - 品名统一规范写法,去除多余空格 - 数字字段必须为纯数字,不要带单位 - 只输出JSON数组,不要输出任何解释 原始记录: {chunk.to_json(force_ascii=False)} """ return prompt

温度参数我固定在0.1。盘点数据整理需要的是稳定复现,不是创意发挥。温度越高,模型输出的随机性越大,同一批数据跑两遍可能得到两个不同版本的盘点表,这在数据场景是绝对不能接受的。

还有一点:如果模型偶尔返回空结果或超时,脚本里要加重试逻辑。我一般做三次重试,每次间隔5秒。实测下来,本地模型偶尔会因为内存不足或线程繁忙而卡住,重试能解决大部分问题。

4.4 第四步:解析模型输出并写入Excel

模型返回的是一段文本,需要把它清洗成真正的JSON,再转成DataFrame,最后写出Excel。

import re import pandas as pd def parse_model_output(text): # 去掉Markdown代码围栏 text = re.sub(r"```(json)?", "", text).strip() # 截取第一个 [ 到最后一个 ] 之间的内容 start = text.find("[") end = text.rfind("]") if start == -1 or end == -1: raise ValueError("模型输出不包含JSON数组") array_text = text[start:end+1] data = json.loads(array_text) return pd.DataFrame(data) def build_final_table(df, period_start, period_end): # 按货品编号和品名分组汇总 summary = df.groupby(["item_name", "item_type"])["quantity"].sum().unstack(fill_value=0) # 这里用pandas计算,而不是让模型算 # 合并得到最终盘点表结构 final_df = summary.reset_index() # 计算结存数量 = 期初 + 入库 - 出库 # 期初数需要从期初库存表单独导入 return final_df

实际项目里,这种解析函数很容易在JSON解析这一步报错。原因往往是模型在JSON前后写了一句“好的,这是整理后的结果”。所以清洗逻辑不能只做一次,而是要循环尝试:第一次直接json.loads,失败就去掉围栏再试,还失败就截取方括号内容,最后仍然失败就标记该块为“待人工处理”。

写Excel时还有一个小技巧:在最终盘点表旁边单独生成一页“异常记录”,把程序解析失败、字段缺失、数量为负的记录全部汇总到这一页。这样审核人员打开文件,先看异常页,再核对主表,效率比逐行扫高很多。

4.5 第五步:校验环节

最后一个环节是校验,绝对不要跳过。我推荐至少做两层校验。

第一层是脚本自动校验:账面平衡校验。对于每个货品,必须满足“期初数量 + 入库数量 - 出库数量 = 结存数量”,如果有差异,自动标记异常。这一步用pandas一行就能算出来,看着简单,能挡住大多数错误。

第二层是抽样人工核对:从最终盘点表里随机抽10到20个SKU,回到原始台账比对。抽样时优先抽那些数量异常大、名称包含“退换”“破损”的记录,因为这些最容易出错。

校验通过后,这份盘点表才能算真正完成。整个过程跑下来,500个SKU的台账,从读文件到出结果,我实测一般10到20分钟就能完成,其中大部分时间都是在等模型推理。

5. 实测演示:一份凌乱出库单怎么变成盘点表

5.1 模拟一份典型的乱台账

为了把整个流程讲透,我构造了一份高度“仿真”的原始台账,尽量还原仓库里最常见的那种混乱程度。

文件名叫“仓存流水2024-Q4.xlsx”,包含三个Sheet。“入库明细”里有合并单元格“水果类”跨了五行,品名写的是“苹 果”“苹果(红富士)”;“出库明细”中品名写的是“红富士苹果”,数量既有正数也有负数(负数表示退货);“退换记录”里数量列写的是“退3”“换5”这种带文字的内容。三个Sheet的表头编号、日期格式、备注列各不相同。

这份数据如果靠人工整理,先得把三个Sheet对齐字段,再把“苹 果”和“苹果(红富士)”归成同一个品名,还得把“退3”这种文本量转换成数字3并标记类型——每步都是重复劳动。

5.2 完整运行流水线后的实际效果

我把这份模拟数据丢进上面说的流水线,跑完整流程,得到的结果如下(节选):

货品编号品名入库数量出库数量退换数量结存数量异常标记
A001苹果11065540无
A002香蕉3012018无
A003葡萄252023数量偏低,建议复核

注意几个细节:“苹 果”“苹果(红富士)”“红富士苹果”被统一成“苹果”;“退3”“换5”被识别为退换记录,数量被程序解析成3和5;结存数量全部由脚本计算,不是模型猜的。这一份原表如果手工整理,我估计要半小时以上,流水线实际运行时间是4分12秒,其中模型推理占了绝大部分,真正脚本处理不到十秒。

5.3 原文件和盘点表的差距在哪里

从原始台账到最终盘点表,差距主要体现在四个维度:

一是字段标准化:多Sheet的异构字段被统一为标准列名。二是语义归一化:同货品的不同叫法被合并成主数据标准名称。三是类型标签化:入库、出库、退换被自动分类,数量正负不用人工再看。四是异常显性化:程序把解析失败和可疑记录集中到异常页,审核人员只需要处理少数异常行。

用户拿到这份表,做实物盘点时只需要带着“品名+账面数量”去仓库对照实物,回来把实际数量填进“实盘数量”列,差异部分再单独复盘。这才是库存盘点应有的速度。

6. 踩坑记录与改进建议:让盘点表不再返工

6.1 编码和Excel格式的一堆暗坑

第一次跑通流水线我很兴奋,但第二天就被打击了。同事发来一个.xls格式的老文件,我的脚本直接报错——openpyxl不认.xls,只有.xlsx才能读。后来我在读取函数里加了格式判断,遇到.xls先用另一个库转成xlsx再读。

更烦人的是编码问题。有些Excel是从老系统导出的CSV,虽然扩展名是.csv,实际编码是GBK。pandas默认按UTF-8读,直接乱码。解决方法是pd.read_csv(path, encoding="gbk"),但不同文件编码不一样,最好写一个探测函数,尝试多种编码读,哪个不报错用哪个。

还有一个平时不会注意但关键时刻能坑死人的细节:pandas读合并单元格时,只有左上角第一格有值,其余全是NaN。我第一次没做ffill(),结果几十条记录的品类全部缺失。后来在清洗函数里固定加上向前填充,这类问题才算根治。

6.2 模型“幻觉”数字的防范办法

本地模型在生成数字时,偶尔会“编”出不存在的数量。有一次模型把“进货50”写成“进货100”,盘点表上凭空多出50件货。这件事给我提了个醒:绝对不要让模型直接生成最终数量,它只负责抽取和分类,所有加减汇总必须由脚本计算。

我现在有三条防幻觉的硬性规则:

  • 提示词里明确写“如果原始记录中没有数量,请在quantity字段填0,不要推测”。
  • 脚本对模型输出的quantity做边界检查:超过原始记录最大值的,直接标记异常。
  • 任何涉及库存数量的最终表,都跑一遍“期初+入-出=结存”的平衡校验,不通过就不输出正式版。

这些规则不复杂,但能挡住绝大多数模型瞎编的情况。核心思路是:模型可以有理解偏差,但数字必须经过程序逻辑的过滤和校验。

6.3 上下文长度与分批策略

本地模型上下文长度有限,这是很多初用者最容易忽略的瓶颈。我曾经贪方便,一次性把300条记录塞给模型,结果模型只处理了前150条,后面全部被截断,结果还像模像样地输出了一个“完整”JSON——这个错误最危险,因为看起来像正常结果,实际数据是残缺的。

所以我的建议是:模型调用前先检查数据量,超过30条的批次必须切分。每批返回后立即写入结果,最后再合并。宁可多调几次接口,也不要冒险让上下文被截断。分块处理还有一个附带好处:某一块模型抽风时,只需要重跑那一块,不用整个流程推倒重来。

批次大小没有绝对标准,取决于模型的上下文长度。我的经验是6B到9B量级的模型,每批15到30条比较稳妥。记录本身字段多、文本长时取小值,字段少时取大值。

6.4 从单表到多仓库的扩展思路

如果这套流水线验证可行,后面可以往两个方向扩展。

一个方向是沉淀标准模板。把归一名称、输出字段、提示词、校验规则全部固定成仓库的“标准盘点规范”,以后无论谁来交接,只要按这个规范投喂原始流水,出来的盘点表格式永远一致。这样就能解决“每个人的表格风格都不同”的长期问题。

另一个方向是做定时自动盘点。如果原始台账是每天或者每周自动更新的,可以利用操作系统的定时任务,每天凌晨自动跑一遍流水线,自动生成当日库存快照。月底盘点时,直接基于这些快照做汇总和差异分析,不用再去翻旧账。实测下来,把流水线挂到定时任务里以后,我每个月的盘点整理时间从四五个小时降到了半小时以内,剩下的时间全部用来做异常复核。

最后再分享一个我个人的习惯:跑任何自动化流程之前,先保留一份原始文件的只读备份。AI处理数据再顺手,也不能确保每次都完美。有备份兜底,遇到问题随时可以退回人工处理,心里不慌。这套本地化AI盘点流水线对我最大的价值,不是省了多少时间,而是把月底那种焦躁的重复劳动变成了一个稳定的脚本任务,让我能腾出精力去处理真正需要判断的库存问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进