当 ERP 学会用 SQL:Spring AI 2.0 函数调用如何让老板直接「问」数据
"最近一周哪个店铺退款最多?"——这个问题在过去需要:运营提需求 → 技术写 SQL → 出报表 → 截图回微信。而在启航电商 ERP v4.1 里,答案是一句话的事。这篇文章拆解我们基于 Spring AI 2.0 + DeepSeek 的函数调用实现,聊聊为什么这么设计、坑在哪。
一、为什么不直接让大模型写 SQL?
这是所有人第一个想到的方案,也是我们最先否掉的方案:
- 安全红线:把生产库结构交给大模型生成可执行 SQL,等于把数据库的钥匙递出去——一条被幻觉出来的
DROP/UPDATE就足以酿成事故; - 权限失控:NLP-to-SQL 无法细粒度控制「谁可以看什么」,RBAC 体系形同虚设;
- 不可信结果:模型对表结构的记忆可能过时,JOIN 错字段、聚合口径错,业务方拿到的是「一本正经的错误数字」——这比没有数字更糟。
所以我们的答案是:SQL 由代码写死并经过验证,大模型只负责「选哪个函数、传什么参数」。这正是 Function Calling(函数调用)的设计哲学。
二、11 个 @Tool 工具类:系统的「手和眼」
Spring AI 2.0 通过 @Tool 注解把 Java 方法注册为大模型可调用的函数。我们把 ERP 的核心查询能力拆成了 11 个原子工具类:
| 工具类 | 职责域 | 典型问题示例 |
|---|---|---|
OrderTools | 订单查询 | "今天还有多少单没发货?" |
RefundTools | 售后退款 | "上周退款原因分布是怎样的?" |
InventoryTools | 库存状态 | "哪些商品库存低于安全线?" |
PurchaseTools | 采购分析 | "7 月采购花了多少钱?" |
GoodsTools | 商品档案 | "A001 这个 SKU 在哪些店铺上架?" |
ShopTools | 店铺信息 | "现在授权了几家抖店?" |
MemberTools | 会员数据 | "本月新增了多少会员?" |
SupplierTools | 供应商 | "哪家供应商报价最低?" |
LogisticsTools | 物流 | "XX 单现在到哪了?" |
WarehouseTools | 仓库仓位 | "主仓还剩多少可用库位?" |
StockFlowTools | 出入库流水 | "昨天出库量是多少?" |
设计原则:小而原子,而非大而全
我们没有做「queryData(question)」这种万能函数,而是坚持每个方法只做一件事、返回结构化的 DTO。原因有三:
- 描述即提示词:方法的 Javadoc 和参数说明会被发给模型,写得越具体(含单位、时间范围默认值),模型选择越准;
- 组合涌现能力:"哪个店铺退款最多且是哪款商品导致的"这类复合问题,模型会自动串联
RefundTools→GoodsTools完成,无需我们预先编排; - 审计友好:每次调用都有明确的函数名与参数日志,出了错能精确定位到「哪一步算错了」。
三、一次提问的完整旅程
用户:"上个月采购金额 TOP3 的供应商是谁?"
│
├─ ① ChatClient 组装上下文(系统提示词 + 对话历史 + 工具清单)
├─ ② DeepSeek 决策:调用 PurchaseTools.queryPurchaseSummary(period=上月)
├─ ③ 后端执行经过验证的 MyBatis 查询,返回 List<SupplierStatDTO>
├─ ④ 模型继续决策:按金额排序取前 3(纯文本推理,无需再查库)
└─ ⑤ 流式输出:"7月TOP3:义乌XX服饰 ¥110,400 …"
整个链路中大模型接触不到任何真实连接串或表名,它看到的只是函数签名。前端通过 SSE 逐字渲染回答,等待感大幅降低。
四、从「你问我答」到「主动找你」
对话式查询解决的是「人找数据」,而运营真正的痛点是「异常没人盯」。于是我们把同一套工具复用到了定时监控场景:
// 定时任务伪代码
cron("0 0 9 * * ?") {
val lowStock = inventoryTools.listLowStock(threshold)
if (lowStock.isNotEmpty()) {
ai.analyze(lowStock) // 让 AI 总结影响面与补货建议
notifier.push(feishuWebhook, summary) // 推送到飞书群
}
}
销售额为零、退款激增、发货超时、库存不足……这些巡检规则由系统触发、由 AI 补充解读,最终经通知渠道分级推送(高危外推飞书/钉钉/企微,低优站内静默)。同一套 @Tool 体系,两种消费方式——这是我们觉得这套架构最有价值的地方。
五、在你的部署里怎么开启?
# application.yml
spring:
ai:
deepseek:
api-key: sk-xxxxxxxx # DeepSeek 官网申请,几块钱能用很久
配置后重启,侧边栏出现「AI 智能」菜单即可使用。两个进阶选项:
- 本地零成本:支持切换 Ollama 本地模型,内网环境也能跑(效果与在线模型有差距);
- RAG 知识库:部署 pgvector 后可挂载企业知识文档,让 AI 回答「退货规则是什么」这类制度性问题。
六、写给想自己实现的同学
- 工具的 Javadoc 就是给模型看的说明书,认真写参数含义和默认行为;
- 所有查询强制分页上限(如 limit 200),防止一次提问拖垮数据库;
- 金额、比率等关键数字保留原始值返回,格式化交给模型,避免二次计算误差;
- 为每个工具调用记录审计日志(谁、何时、传了什么参数),这是企业落地的信任基础。
