← 返回文章列表

成长记录

WMS 智能仓储辅助决策系统:我的毕业设计,把 AI 接进真实业务

这是我第一次认真把 AI 从聊天框推进到一个有登录、有数据、有权限的真实业务系统里。毕业后我又把分析助手改成了工具调用式的 Agent,并用自建评测集验证了改造效果。

2026 年 10 月更新:毕业以后我又回来改了这个项目——给分析助手补上评测集,把它改成原生工具调用的 Agent,把资料问答换成真正的检索。前面是毕业设计阶段的记录,保持原样;改造过程和数据补在文末「毕业后的改造」。源码在 GitHub。

这个项目是我的毕业设计,论文题目是「基于 Spring Boot 的智能问答系统设计与实现」,系统落在仓储场景,做成了这套 WMS 智能仓储辅助决策系统。选这个题目的时候,我给自己定了一个要求:不要做一个只会聊天的 AI 页面,而是让 AI 真的进到业务里去。

前端我用的是 Vue 3、Vite 和 Electron,后端是 Spring Boot 3.4、Java 17 和 SQL Server。系统的目标不是再造一个完整 WMS,而是在已有的仓储数据之上,提供更高效的查询、分析和辅助决策能力——把库存预警、自然语言数据分析、资料增强问答和文档处理放到同一个闭环里。

从「能聊天」到「能处理业务」

做之前我对 AI 应用的理解很朴素:接一个模型接口,做一个对话页面,能问能答,好像就已经是 AI 项目了。

真做起来才发现,业务系统里的 AI 不是这样工作的。一个仓库管理员想要的不是一句泛泛的回答,而是:

  • 登录以后,只能看到自己权限范围内的数据;
  • 上传一份制度或商品说明,让 AI 回答时优先参考它;
  • 直接用自然语言问库存和销售,让系统去查真实数据库;
  • 一进来就能看到哪些商品快没货了,而不是自己去翻表;
  • 能把文档丢进去,提取内容,预览和下载处理结果。

这些问题逼着我把项目从「智能对话」一点点改成了「仓储辅助决策系统」。也是从这时候开始,我意识到 AI 项目的难点常常不在调模型,而在把模型放进一个可靠的工作流。

自然语言查数据库:分析助手

这个项目里我最满意、也最有成长感的部分,是分析助手。

用户可以直接用自然语言提出库存或销售问题,比如「最近哪些商品库存风险最高」。后端会把这个意图翻译成 SQL,去查真实的 SQL Server 业务库,然后把分析结论、SQL 明细和查询结果一起返回。整个过程还支持连续追问,不用每次都从头描述上下文。

但让它能上线,光会生成 SQL 是不够的。我加了几层保护:SQL 只能是只读查询,限制写入、删除、建表和系统过程调用这类危险关键字;只允许查询白名单里的表;并且对生成的 SQL 做修复保护,降低危险语句的风险。

这一步让我真正理解了 NL2SQL 的价值,也理解了它的边界——让模型查数据很有用,但「让模型随便查数据」是不可接受的。安全和能力必须一起设计。

给模型补上下文:资料增强问答

另一个让我有触动的功能是资料增强问答。

纯靠模型自己,它不知道这家仓库的制度、流程和商品细节。所以我做了一个资料管理模块:用户可以上传、查看和删除业务资料,AI 在回答时会优先参考当前账号上传的这些文档。

这其实就是最朴素的 RAG 思路——不追求复杂的检索算法,而是先解决「让模型看到用户私域资料」这件事。它让我从「直接问模型」走到了「给模型补上下文」,也让我开始思考检索质量、资料归属和上下文长度这些更深的问题。

把多个功能串成一条业务流

单独看,这些功能都不算特别复杂。但我刻意把它们串成了一条完整的业务主线:

  1. 管理员登录,进入工作台;
  2. 在资料管理上传一份业务资料,形成这个账号的知识库;
  3. 进库存洞察,看到低库存商品、风险分级和优先处理清单;
  4. 对某条高风险商品点「生成处理建议」;
  5. 系统跳到分析助手,自动带上针对这个商品的分析问题;
  6. 后端查真实数据库,返回结论和 SQL 明细;
  7. 再切到智能对话,基于已上传资料继续做制度解释和业务问答。

把功能连成流程,是我在这个项目里学到的很重要的一件事。别人看到的就不再是一堆零散的功能点,而是一个能解决实际问题的系统。

前端、后端和数据库,缺一不可

做这个项目以前,我更关心页面能不能跑起来、接口能不能返回结果。做完以后,我开始关心系统之间的连接。

前端不只是展示,它要处理登录态、路由保护、多会话的本地存储和后端同步、流式渲染(我用的是 SSE,请求里带上 Authorization 头),还要处理资料上传、结果预览这些交互。后端不只是转发模型请求,它还要负责认证(JWT)、数据库访问、文档处理、SQL 安全和一整套业务接口。数据库也不只是存数据,它直接决定了 AI 能分析什么、给出的结论可不可信。

用户账号、会话、聊天历史、还有仓储业务记录,都落在 SQL Server 上;测试环境我用 H2 内存库,这样跑自动化测试时不依赖真实数据库。为了部署方便,我还写了一键部署脚本,用 Nginx 加 systemd 把前后端一起拉起来。

边界和诚实

我也在项目里给自己留了一些诚实的边界。

比如系统配置页集中维护真实的数据库连接和模型配置,分析助手直接用这里保存的配置;普通账号由管理员创建,不开放公开注册;图片入口我保留了,但当前文本模型路径并不承诺图像理解能力,所以演示主线放在文本问答、资料问答、库存洞察和分析助手上。我宁可把能力说小一点,也不想做一个看起来什么都能、实际一碰就露馅的演示。

我学到的东西

第一,AI 功能不能孤立地看。一个好用的 AI 系统,应该知道用户是谁、有什么数据、能访问哪些资料、当前问题属于哪个业务场景。

第二,业务系统要有边界。SQL 只读、表白名单、API Key 不写进代码和提交记录、模型配置能在系统里维护——这些细节不炫技,但它们决定系统能不能继续往前走。

第三,演示也要有主线。一条从登录、资料上传、问答、库存洞察到分析助手的完整流程,比一堆孤立功能更有说服力。

回头看

现在回头看,这个毕业设计还有很多可以打磨的地方:权限颗粒度可以更细,资料检索质量还能提升,文档处理可以更强,部署细节也还能优化。

但它对我最大的意义,是让我第一次把 AI、数据库、后端、前端和业务流程放到一起考虑。它让我从「做一个能对话的页面」,往「做一个能解决业务问题的系统」实实在在地走了一步。

毕业后的改造

毕业设计完成后,我想先回答一个问题:分析助手到底有多准?原来没有可复现的评测,所以毕业后第一件事是给它补一套评测,再按评测结果改进。

先有测试集,再谈改进

我用固定随机种子生成了一份模拟医药流通企业的出入库数据(2026 年 1–9 月,7798 行,名称全部虚构),把每个字段的业务含义写进 SQL Server 的字段说明里。然后写了 100 道分 9 类的问题,每道配一条标准 SQL,覆盖单表查找、聚合统计、分组排名、时间口径、库存与效期、复杂组合、多轮追问、需要澄清和安全拦截。另外留了 30 道题,等调优全部做完才写、才测,用来检查有没有对着测试集「调过头」。

判分只看结果:取 Agent 作为结论依据的那条 SQL,在库里执行,结果集和标准 SQL 一致才算对,不比较 SQL 的写法。

原实现跑了 3 次,执行准确率平均 70.3%。

70% 输在哪

我把 31 道错题逐条看了一遍,最多的几类是:

  • 最后执行的是一条探索性查询,正确结论只出现在回答文字里,没法用 SQL 核对(11 道);
  • 用 单据类型 LIKE '%销售%' 统计销售,把「销售退货」也算了进去(9 道);
  • 不理解库存口径,比如现有库存只在每个仓库、每个商品的最后一行有值(4 道);
  • SQL 一报错整次分析就结束,模型没有机会改(2 道)。

还有一个意外:原来的系统提示词里有一段中文业务规则因为编码问题变成了乱码。我顺手修好,准确率反而从 70% 掉到 57%——模型读懂了那条「销售类问题用 LIKE ‘%销售%’」的旧规则,照做以后把退货全算了进去。这件事让我记住:提示词里的业务规则依赖数据,必须用评测验证,不能凭感觉加。

改成原生工具调用

后端升级到 Spring Boot 3.5,接入 Spring AI 1.1,把「模型输出一段 JSON、后端用正则抠出 SQL」改成模型原生的 Function Calling,工具拆成五个:

  • list_tables、describe_table:按需查看表和字段,包括字段说明、空值数、数值范围和枚举取值,不用再猜列名;
  • run_sql:只读执行,报错时把错误原样交还模型,让它自己修正;
  • search_docs:检索上传的业务资料(见下一节);
  • ask_user:条件不全时反问用户。

工具执行没有交给框架,而是自己写循环,这样原来的步数上限、SSE 进度推送和 SQL 安全校验都还在同一个地方。

第一版只有 69%:回答准确率涨到了 94%,但模型经常在得出结论后再并行跑一条「核对查询」,最后一条 SQL 不再是结论依据。第二版给 run_sql 加了 is_final_result 参数,要求模型标出回答依据的那条查询,并把当前日期和可查询的表直接写进系统提示词。

版本 执行准确率 平均模型调用 平均输入 token P95 耗时
原实现(JSON 文本) 70.3%(3 次:69–71%) 2.3 次 2527 15.0s
工具调用 v1 69.0% 4.8 次 12032 12.8s
工具调用 v2 95.5%(2 次:94–97%) 3.5 次 9545 9.3s

留出的 30 道题上,原实现 66.7%,v2 是 100%。

代价也要说清楚:多轮调用工具让输入 token 涨到原来的近 4 倍。在这个场景里我认为值得,准确率和可追溯性比 token 更重要;但如果换成高并发场景,就需要缓存表结构、压缩工具结果。

资料问答:从整篇拼接到真正的检索

毕设版的资料问答,是把资料原文按更新时间截断后塞进上下文。资料少的时候没问题,资料一多就装不下。

这次换成了完整的检索链路:资料按标题分块,用 bge-m3 向量化存进 pgvector;查询时向量召回和 BM25 关键词召回各取 20 个候选,用 RRF 融合,再用 bge-reranker 重排,取前 5 块交给模型,回答里标注来源。两路召回都按当前账号过滤,删除资料时同步删除分块;向量库不可用时自动退回整篇拼接。

我写了 40 道有标准答案的问题和 3 道资料里没有答案的问题,在两种规模的知识库上对比:

知识库 整篇拼接 检索 平均输入 token(拼接 → 检索)
10 篇仓储制度(8.7k 字) 100% 100% 5611 → 443
再加 20 篇无关制度(共约 43k 字) 47.5% 100% 11111 → 460

需要说明的是:这批资料上单用 bge-m3 向量召回,Hit@5 已经是 100%;加上 BM25 做 RRF 融合反而降到 97.5%,要重排之后才回到满分。测试集在重排后已经饱和,区分不出混合检索的价值,所以我没有把它写成「提升」。

检索还封装成了分析 Agent 的 search_docs 工具。比如「按照安全库存标准,株洲前置仓哪些商品低于安全库存」,Agent 要先查两条制度算出阈值,再写 SQL 查库存。这类 6 道题,原实现一道都做不了,新版本 3 次运行都是 6/6。

这次改造让我想清楚的事

第一,没有评测的改进只是感觉。修乱码这种「明显正确」的改动都可能让结果变差。先建测试集、拿基线,每次改动都跑一遍,是我以后做 Agent 的默认流程。

第二,让模型知道自己在查什么,比让它更会写 SQL 重要。准确率的提升主要来自按需读取字段口径、把错误交还模型修正、标明结论依据,而不是换一个更强的模型。

第三,局限要写清楚:数据和测试集都是我自己造的,只有一张宽表,只测了一个模型(DeepSeek),30 道留出题的样本也不大。完整的测试集、评分脚本和每次运行的报告都在仓库的 eval/ 目录,可以一条命令复现。