给老 Spring 项目装个 AI Agent
摘要老系统想接 AI别急着搭 Python sidecar。用 Spring AI 在原有 JVM 里跑起一个三段式 Agentplanner 拆活、executor 拿白名单工具干活、reporter 汇报。能力边界靠运行时隔离强制比在提示词里反复叮嘱管用。我们内部那套工单系统2020 年上线跑了快六年——框架这些年从 Spring Boot 2.x 一路升到 3.5但业务代码、表结构还是当年的老底子。一线客服每天被问这周哪些高优先级工单还没关哪个部门积压最多以前这些得人肉查列表、拼 SQL、再敲一段回复。慢还容易漏。我想让它自己回答。需求一句话把老系统能查什么、能改什么交给模型让它自己动手回话像个懂业务的同事。先说结论省得往下读还绕真正要的不是更聪明的聊天接口是 Agent——一个能调用老系统能力的循环。而这个东西在 Java 里就有现成做法Spring AI 1.1.x官方框架2026 年 6 月发到 1.1.8不用换技术栈不用起 Python 服务。先想清楚老系统缺的是手只调大模型 API 的路子试过的人都知道三个坎模型不会查。你只能把数据整个塞进提示词一次塞不下塞下了也是过期快照。输出靠运气。让它列个工单清单它可能给你编一段 Markdown 表格列名还是它猜的。它管不住自己。提示词里写只读、不要改数据它照样可能在你没注意的地方自作主张。ChatGPT 是问答——你问一句它答一句。Agent 是循环——理解目标、选工具、执行、看结果、不行再来。对后端工程师来说这个概念其实不陌生Agent 就是一个有 LLM 大脑的微服务只是以前你写死 if-else 的分支现在由模型决定调哪个工具、传什么参数。给老系统装 Agent本质是给它装手把 Service 方法暴露成模型能调的工具让它在你的代码里干活而不是在你的提示词里猜。三段式拆活、干活、交差分开第一个坑来得很快让同一个模型既拆任务、又执行、又检查长任务里它必串台——拆着拆着开始编结果执行时忘了边界。解法是把一次请求拆成三个角色各用独立的 ChatClient角色职责手里有什么planner看懂目标拆成任务清单没有工具只输出结构化清单executor一次干一个任务把结果交回去只拿当前能力组的工具reporter汇总各步结果组织成人话回复只读上一步的结果代码长这样——三个角色共享同一个 ChatModel但 prompt 和工具完全分开recordPlan(ListStringsteps){}ServiceclassTicketAgentService{privatefinalChatClientplanner,executor,reporter;TicketAgentService(ChatModelmodel,ToolCallbacksqueryTools){this.plannerChatClient.builder(model).defaultSystem(你是工单客服助手。把用户目标拆成 3 步以内的执行清单输出 JSON不要调用任何工具。).build();this.executorChatClient.builder(model).build();// 工具按请求动态给this.reporterChatClient.builder(model).defaultSystem(根据执行结果用中文回复用户注明数据来源不要编造没有查到的数字。).build();}}拆开之后每个角色的上下文都很短planner 不需要猜执行细节executor 一次只看一个任务reporter 只做归纳。实践下来模型编造结果的次数明显变少——它手里的事情少了能编的空间也就小了。capability边界靠结构不靠提示词这是整篇最想讲的部分。Spring AI 2.0.1 的发布公告里有一个安全修复CVE-2026-59318某些配置下即使某个工具没有公开给当前请求模型仍可能被提示注入拐到全局兜底解析把没暴露的工具调起来。官方原话的意思很直接Agent 的边界不能只靠告诉模型维持必须由运行时强制执行。所以老系统的能力要分级我分了两个 capabilityquery只读。查工单、按部门聚合、按状态筛选。handle可写。改状态、派单、加备注。真实客服流程里写操作往往还要人工确认一环这里先不展开。实现上每个 capability 是一组独立的 ToolCallback。调用时用.tools()显式传入——Spring AI 1.1 的规则是运行时工具完全覆盖默认工具也就是说这次请求里模型手里只有你给的那几个多一个都没有// 只读会话模型手里只有查询工具物理上碰不到写操作Stringanswerexecutor.prompt().system(你只能查询工单禁止任何修改操作。).user(userQuestion).tools(queryCapability.callbacks())// 只有 query 组的工具.call().content();想走处理流程关单、派单那需要另一个带着handle工具的 executor 实例并且由业务代码决定什么时候构造它——是否放权是代码逻辑不是模型自觉。落地老 Service 怎么变成 Agent 的手老系统的 Service 不需要大改加注解就行ServiceclassTicketQueryService{Tool(description按部门统计未关闭工单的数量按数量降序返回)ListDeptCountcountOpenByDept(){...}// 老方法原样保留}Tool是 Spring AI 的声明式工具注解org.springframework.ai.tool.annotation一个方法一个工具方法签名自动变成模型可理解的参数协议。planner 的输出要结构化成任务清单用entity()把 JSON 直接映射成 recordPlanplanplanner.prompt().user(userQuestion).call().entity(Plan.class);// 输出非法 JSON 时这里会抛异常见下文踩坑然后是循环与兜底三件事缺一不可步数上限。1.1.x 没有内置的工具调用次数限制我手写任务不超过 3 步executor 单任务重试不超过 2 次。超过就报错退出让 reporter 如实告诉用户这个问题我没处理完绝不编一个已完成。超时。整个 Agent 调用包一层超时60 秒超时直接回退已转人工。客服场景里宁可让人等不能让机器人空转烧钱。校验。planner 拆出来的任务如果不在已知能力清单里丢弃并跳过reporter 如实说明哪些没做。踩坑记录坑一模型想调用不存在的工具。执行把单号 T20260901007 直接关了时executor 手里只有查询工具模型仍尝试调用closeTicket。工具名解析失败请求直接报错——我最初把报错透传给了用户“系统错误”后来改成捕获并让 executor 用文本返回当前会话只能查询工单不能改单。拦截是结构保证的但这个台阶要自己铺。坑二循环烧 token。有次 executor 在同一个问题上连续调了十几轮工具每次都差一个数据始终没停下来。加了步数上限后这类问题从悄悄烧钱变成快速失败。工具调用循环必须有预算——这条后来写进了团队的代码规范。坑三结构化输出翻车。planner 偶发返回非法 JSON——被截断或者外面多包了一层 Markdown 代码块。处理解析失败重试一次仍失败就把整件事降级为暂时处理不了转人工。坑四工具描述就是泄密面。一开始我把Tool的 description 写得很内部“查询 t_ticket 表按 dept_id 聚合”。工具描述是要发给外部模型厂商的——这等于把表结构送出去了。改成业务化描述“按部门统计未关闭工单数”。查什么、怎么查留在方法里。坑五事务边界别指望 Agent。老 Service 方法各自带Transactional。Agent 一次任务调多个方法不代表一个事务。需要原子性的操作绝不能拆成多步让模型自己拼——拆之前先想清楚哪些是一个动作那部分留给普通代码。怎么验证这套东西我把上面这套装进一个模拟工单库的示例工程跑了客服最常见的几类问题做对照单轮全量上下文直答对比三段式 Agent。样本小、没做严格基准结论只能当方向参考但有两个差异是结构上必然成立的越权被拦是必然的。写工具根本不在 executor 的工具列表里模型想调也调不到——这不是它自觉是它手里没有。多条件组合查询的格式稳定是设计出来的。reporter 只做归纳输出口径由它的 prompt 约束不会出现这次表格、下次散文的漂移。代价也要说清楚三段式明显更烧 token——拆解、执行、汇总每个角色都在消耗。多花的 token 买的是可控性值不值取决于你的场景对乱来的容忍度。客服这种对用户可见的场景值。总结给老系统装 Agent不是重写一遍是加一个会调工具的大脑。核心三件事拆角色planner/executor/reporter、分能力capability 隔离工具、设上限步数、超时、重试。适用场景Service 边界清晰、操作可枚举、对一致性要求不高的查询与辅助场景。不适用一次动作横跨多库多事务、需要强一致的场景——那是人的活别硬塞给 Agent。版本提示本文代码基于 Spring AI 1.1.8Java 17 Spring Boot 3.5 可直接用参考官方文档。2.0 已于 2026 年 6 月 GA要求 Java 21 Boot 4把工具循环挪进了 ToolCallingAdvisor循环预算maxToolCalls和执行校验都有了原生配置见2.0.1 发布公告。老系统升 2.0本质是一次 Boot 大版本升级别当小版本顺手升。作者唐悦玮 | 从后端出发用 AI 拓展到全栈的工程师。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →