ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Spring AI Alibaba构建Agent实战:从Tool Calling到订单查询

Spring AI Alibaba构建Agent实战:从Tool Calling到订单查询 最近一两年的AI应用开发圈子里有个特别有意思的变化当你跟做Java后端的同学聊起“要不要上个Agent”时很多人第一反应不再是去搜LangChain或LangGraph而是会问一句“Spring能不能直接干这事”这其实反映了一个很现实的需求——绝大多数企业的核心业务系统都是Java技术栈与其把AI能力拆出去单独搭一套Python服务不如直接在Spring生态里把Agent做出来。Spring AI Alibaba的出现恰好把这条路铺平了。不管你之前是不是搞过AI开发只要熟悉Spring Boot理论上就能用一套熟悉的依赖注入、自动装配和配置项快速搭建出一个具备自主规划、工具调用能力的智能体Agent。这篇文章我就围绕“使用Spring AI Alibaba构建智能体Agent”这条主线从Agent的架构思路、核心概念、实际写代码到问题排查完整走一遍。文中的示例项目是一个“订单查询Agent”目标是让模型能够自己决定调用哪个数据库查询工具来回答用户问题。通过这个案例我会把Spring AI Alibaba最关键的能力——Tool Calling工具调用——讲透同时也会覆盖ChatClient用法、Prompt模板、模型配置等绕不开的实操细节。1. Agent到底是什么以及为什么用Spring AI Alibaba来实现1.1 Agent和普通的“聊天机器人”差在哪很多刚接触这个领域的同学会把“接入了大模型API的聊天接口”误当成Agent。实际上两者有本质区别普通聊天机器人只能“说”不能“做”。你可以问它“杭州今天的天气怎么样”它如果不知道只能告诉你“抱歉我无法实时获取天气信息”。而Agent不仅知道自己的知识边界还能通过工具去补齐这个边界——它会自己决定去调用天气API拿到结果之后再把答案整理给你。打个比方传统聊天机器人像一个只有嘴的客服Agent则是一个有嘴、有手、有脑的完整办事员。它的大脑是大模型负责理解意图、拆解任务、决定下一步干啥它的手是各种工具Tool比如查数据库、调接口、发消息、操作文件它的嘴则是最后一环的答案生成。这种“模型推理 工具调用 任务规划”的组合才是Agent的完整形态。1.2 Spring AI Alibaba在Java生态里的位置Spring AI本身是Spring官方发起的AI应用框架项目提供了对接各种大模型厂商的统一抽象层。Spring AI Alibaba则是阿里在Spring AI基础之上做的适配增强版本专门用来对接阿里云的通义千问系列模型同时继承和扩展了Spring AI的核心能力。选择Spring AI Alibaba有几个非常实际的考量与Spring Boot无缝集成不需要额外引入一堆乱七八糟的SDK依赖注入、配置绑定、自动装配这些Spring老本行通通派上用场。模型抽象统一今天你用通义千问明天想换成别的模型改动成本被抽象层吞掉了大半。内置了Agent开发所需的底层组件比如Tool Calling机制、Prompt模板管理、ChatClient对话编排、输出解析器等这些正是写Agent最核心的积木。文档齐全且社区活跃因为是国内团队维护中文资料和issue反馈相对友好遇到问题能较快找到解法。如果说LangChain是Python圈的Agent全家桶那么Spring AI Alibaba就是Java圈里最值得关注的Agent开发基础设施。它解决的核心问题是不让Java开发者为了搞AI而被迫换技术栈。1.3 本文示例项目的目标为了把概念转成看得见摸得着的东西我设计了一个“保温杯工厂订单查询Agent”。背景很简单工厂有一套MySQL数据库里面存着订单表表里有订单号、产品名、数量、状态、下单日期等字段。用户的诉求是——能不能让AI直接用自然语言查这些数据用户问“帮我查一下订单总量有多少。”Agent回答先识别用户要查的是“订单总量”然后调用指定的SQL查询工具传入必要的表名和查询条件拿到结果后组织成中文答案。这个场景覆盖了Agent开发最典型的核心链路意图理解、工具选择、工具参数生成、工具执行结果回填、最终答案生成。弄懂这一套你就能举一反三扩展到更多业务场景。2. 构建前的架构拆分与关键组件选型2.1 Agent的核心循环模型-工具-反馈无论用什么框架Agent的底层运行逻辑都逃不出一个循环模型决定行动 → 执行行动 → 返回结果 → 模型基于结果再决定下一步。在实际代码中这个循环通常不显式出现在我们的业务代码里而是由Spring AI Alibaba框架替我们驱动。我们要做的反而是两件事提供给模型可调用的工具描述。定义好系统Prompt让模型知道自己的职责边界。从架构层面拆解一个基于Spring AI Alibaba的Agent由五个模块组成模块职责类比ChatClient对话编排入口负责与模型的请求/响应交互客服主管ChatModel底层大模型封装通义千问大脑System Prompt给模型的角色设定与行为约束员工手册Tool可执行的具体业务动作手脚OutputParser把模型输出解析成结构化数据翻译官这五个模块凑齐一个最小可用的Agent就跑起来了。2.2 Spring AI Alibaba的核心APIChatClient以前写Java调大模型接口常见姿势是直接用对方SDK的请求对象再手动处理响应字符串。Spring AI Alibaba把这个过程简化成了类似MyBatis写SQL、RestTemplate调接口的体验。ChatClient就是整个交互链路的门面它支持流式与非流式调用支持拼接Prompt也支持把Tool列表传给模型。实际写下来你会发现这个API设计得比较顺手有点“SQL到Java方法映射”的意思声明式的味道很足。2.3 Tool Calling是怎么回事Tool Calling是Agent的灵魂。它并不神秘本质上分三步走第一步你把每个工具的描述、参数schemaJSON格式发给大模型。第二步模型根据用户问题判断需要调用哪个工具并生成对应的参数JSON。注意这时候模型还没真正执行你的工具只是“申请调用”。第三步你收到模型的工具调用请求后在本地执行真正的业务方法再把执行结果回传给模型模型结合结果生成给用户的最终回复。所以Tool Calling就像模型是个只会出主意的军师真正动手打仗的是你的Java方法。它告诉军师“我有哪些兵可以用”军师说“派炮兵连轰炸3号阵地”于是你让炮兵连真去开炮再把战果汇报给它。Spring AI Alibaba里实现一个Tool只需要在一门普通的Service或Component上用Tool注解标注方法即可。框架会把方法名、方法参数和注释自动转换成模型可理解的JSON Schema。3. 新建工程与基础配置3.1 依赖引入与工程结构我用的是Spring Boot 3.2.x Spring AI Alibaba 1.0.0版本组合构建工具选择Maven。需要说明的是Spring AI Alibaba的版本策略和Spring Cloud Alibaba比较像会以start.aliyun.com作为默认的依赖管理地址。如果你用阿里云的初始化器生成工程它会自动处理依赖坐标。在pom.xml中核心依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.4/version relativePath/ /parent dependencies dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version1.0.0/version /dependency dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-tool-calling/artifactId version1.0.0/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 数据库相关 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency /dependencies提示如果你不是使用阿里云初始化的工程模板而是手动搭建务必要在工程的dependencyManagement里加入Spring AI Alibaba的BOM否则版本号对不上启动时会遇到一堆NoSuchMethodError。3.2 application.yml配置详解配置文件里需要配置三组核心内容模型服务商地址、API Key、模型名称。我这里以通义千问为例使用的模型是qwen-plus。spring: ai: alibaba: # 使用兼容OpenAI协议的通义千问服务 base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 api-key: ${DASHSCOPE_API_KEY} chat: client: # 可选设置chat client的默认配置 observations-enabled: true datasource: url: jdbc:mysql://localhost:3306/agent_demo?useUnicodetruecharacterEncodingutf8 username: root password: 123456这里要提醒一个关键点Spring AI Alibaba默认走的是DashScope的OpenAI兼容模式所以base-url必须是compatible-mode这个地址不能填成纯DashScope原生的端点。如果你之前调过通义的SDK容易在这里踩坑。3.3 准备数据库表结构为了让Agent有数据可查我准备了一张简单的订单表。建表语句先贴出来后面所有示例都是针对这张表查询的。CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, order_no varchar(64) DEFAULT NULL COMMENT 订单编号, product_name varchar(128) DEFAULT NULL COMMENT 产品名称, quantity int DEFAULT NULL COMMENT 数量, status tinyint DEFAULT NULL COMMENT 订单状态1待生产2生产中3已完成, created_at datetime DEFAULT NULL COMMENT 下单时间, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;然后塞几条测试数据让Agent查询测试有东西可返回INSERT INTO orders (order_no, product_name, quantity, status, created_at) VALUES (PO202405001, 304不锈钢保温杯, 2000, 1, 2024-05-01 10:00:00), (PO202405002, 陶瓷内胆保温杯, 1500, 2, 2024-05-02 11:30:00), (PO202405003, 儿童卡通保温杯, 800, 3, 2024-05-03 09:20:00), (PO202405004, 304不锈钢保温杯, 500, 1, 2024-05-04 16:45:00);4. 从0到1实现订单查询Agent4.1 先做一个最朴素的“对话接口”在写Agent工具之前按照渐进式开发的习惯我建议先跑通最基础的问答链路启动工程能调通模型再往上面盖Agent能力。创建OrderAgentController先暴露一个问最简单的对话接口RestController RequestMapping(/agent) public class OrderAgentController { Resource private ChatClient chatClient; public OrderAgentController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public String chat(RequestParam(message) String message) { return chatClient.prompt() .user(message) .call() .content(); } }此时你去浏览器访问http://localhost:8080/agent/chat?message你好如果一切正常会收到模型的友好回复。这个阶段的目的很简单确认API Key有效、网络通、依赖装配无误。如果你在这一步都拿不到回复先排查配置和网络环境别急着往下写。4.2 让Agent拥有查询工具定义Tool接下来就是整个Agent的核心环节——写Tool。这里用MyBatis做数据访问先定义Mapper接口Mapper public interface OrderMapper { Select(SELECT COUNT(*) FROM orders) long countOrders(); Select(SELECT IFNULL(SUM(quantity), 0) FROM orders) long sumQuantity(); Select(SELECT product_name, SUM(quantity) AS total_quantity FROM orders GROUP BY product_name) ListMapString, Object groupByProduct(); Select(SELECT * FROM orders WHERE status #{status}) ListMapString, Object selectByStatus(int status); }然后创建OrderQueryTool类每个方法上用Tool注解描述功能。这里有个很重要的细节注解的description一定要写得清晰、准确因为它是模型决定是否调用该工具的唯一依据。Component public class OrderQueryTool { Resource private OrderMapper orderMapper; Tool(description 查询订单总数量) public String countOrders() { return 订单总数 orderMapper.countOrders(); } Tool(description 查询所有订单的商品总件数) public String sumQuantity() { return 商品总件数 orderMapper.sumQuantity(); } Tool(description 按产品名称分组查询各产品的订单数量汇总) public ListMapString, Object groupByProduct() { return orderMapper.groupByProduct(); } Tool(description 根据订单状态查询订单列表状态值说明1待生产2生产中3已完成) public ListMapString, Object selectByStatus(int status) { return orderMapper.selectByStatus(status); } }你可能会好奇这些Tool不是要传给模型吗模型怎么能自动找到这些方法实际上Spring AI Alibaba在启动时会自动扫描容器中所有标注了Tool的Bean把它们的方法描述打包成一个functions列表挂到每次模型请求的上下文中。模型在生成回复时如果发现某个工具描述与用户问题匹配就会在响应里返回一个特殊的toolCalls字段。4.3 升级Agent对话接口把Tool挂载上去光定义好Tool还不够得让ChatClient知道“手里有这些牌可用”。把Controller改成这样RestController RequestMapping(/agent) public class OrderAgentController { private final ChatClient chatClient; public OrderAgentController(ChatClient.Builder builder, OrderQueryTool orderQueryTool) { this.chatClient builder .defaultTools(orderQueryTool) .build(); } GetMapping(/chat) public String chat(RequestParam(message) String message) { return chatClient.prompt() .user(message) .call() .content(); } }注意我用的是defaultTools这表示每次对话都会把OrderQueryTool里的所有方法暴露给模型。如果你有多个不同的工具集合需要根据场景动态选择可以用.prompt().tools(...)来指定当前这轮对话要启用哪些工具。到了这一步我再请求http://localhost:8080/agent/chat?message一共有多少笔订单模型会收到两个东西用户问题和可调用工具的说明。它会自己判断“查订单总数”对应countOrders方法于是生成一个工具调用请求。框架内部帮你执行该方法把结果“订单总数4”回传给模型最后模型生成一句完整回答“目前一共有4笔订单。”这个过程看起来像魔法底层其实是框架把工具的JSON Schema塞给了模型模型又在一轮请求里多返回了一组“工具调用指令”。Spring AI Alibaba把这些都封装好了你从外面看就是一个简单的方法调用但内部已经完成了一轮Agent的关键循环。4.4 加上系统Prompt让Agent更“懂事”没有系统Prompt的Agent像一个没有岗位说明书的员工虽然手里有工具但容易乱用、漏用甚至给用户输出不准确的话。所以给Agent加“员工手册”是必要的。把Controller再调整一下定义一个系统Prompt模板RestController RequestMapping(/agent) public class OrderAgentController { private static final String SYSTEM_PROMPT 你是一个工厂订单管理助手的智能化Agent。 你的职责是帮助用户查询和分析订单数据。 规则 1. 只能使用提供的工具查询数据不能编造数据。 2. 如果用户的问题涉及到订单总数、数量、状态、产品等必须调用对应工具。 3. 回答要简洁、专业使用中文。 ; private final ChatClient chatClient; public OrderAgentController(ChatClient.Builder builder, OrderQueryTool orderQueryTool) { this.chatClient builder .defaultSystem(SYSTEM_PROMPT) .defaultTools(orderQueryTool) .build(); } GetMapping(/chat) public String chat(RequestParam(message) String message) { return chatClient.prompt() .user(message) .call() .content(); } }这里值得多说一句系统Prompt写得越具体Agent的行为就越可控。比如“只能使用提供的工具查询数据不能编造数据”这句话就防止了模型在数据库查不到结果时强行编一个数字出来。这是我在实际项目中踩过大坑后总结的经验——有的模型在没有工具可用或工具返回值为空时会尝试“脑补”答案而这个规则能把行为钳制住。5. 多轮对话与上下文记忆的实现5.1 需求场景用户不想每次重复描述只用单轮问答接口Agent没法记住之前聊了啥。真实业务里用户大概率会连续问多句话比如用户“帮我查一下待生产的订单有哪些。”用户“他们一共多少件”第二句里的“他们”指代第一句说到的“待生产订单”。如果Agent没有记忆模型拿到第二句时就懵了因为它不知道上下文。我先把这个问题简化用Spring AI的Memory接口配置对话记忆RestController RequestMapping(/agent) public class OrderAgentController { private final ChatClient chatClient; private final ChatMemory chatMemory; public OrderAgentController(ChatClient.Builder builder, OrderQueryTool orderQueryTool) { this.chatMemory new InMemoryChatMemory(); this.chatClient builder .defaultSystem(SYSTEM_PROMPT) .defaultTools(orderQueryTool) .build(); } PostMapping(/chat) public String chat(RequestBody ChatRequest request) { return chatClient.prompt() .user(request.message()) .chatMemory(chatMemory) .conversationId(request.conversationId()) .call() .content(); } }对话上下文是依托conversationId来隔离的不同用户传不同的聊天会话ID各自的上下文就不会串门。这里我用的是内存态Memory适合学习阶段生产环境一般会用Redis或数据库做持久化Spring AI提供了一个ChatMemory接口实现一个持久化版本也很快。5.2 对话记忆和Tool冲突时的注意事项加上了ChatMemory之后有一个容易踩坑的地方如果某个Tool执行时依赖上下文动态变化的参数而模型并不一定能从历史里准确提取出来。例如你想让Agent记住“上次查了哪个产品的订单”下一次用户直接说“那这个产品的总件数呢”模型需要结合历史消息推断产品名——这很考验模型能力。我在实测中觉得这类复杂指代处理光靠把历史消息一股脑塞给模型是不够的。更稳的手段是在Tool方法里不依赖模型传参而是自己在Service层维护一个“当前查询上下文”。比如用户第一次问完“304不锈钢保温杯的订单有哪些”代码里就把ProductName存到会话状态里第二次问“总件数呢”直接取会话状态补全查询条件。这种方法虽然听起来没那么“智能”但可靠性远高于让模型自己记住。毕竟Agent的第一原则是正确不是炫技。6. 从示例到生产力的升级路径6.1 从单工具到多工具协同订单查询Agent现在还只是“会查表”的水平。生产级别的Agent往往需要同时挂多个域的工具比如订单查询、库存查询、物流查询、报表生成等。这时候每个工具类最好按领域拆开一个域一个Service内部再细分Tool方法。同时给工具的description起名也更讲究要让模型能一眼区分“查订单”和“查库存”的区别。6.2 引入RAG增强知识边界如果Agent还要回答“保温杯的材质有哪些”“生产周期多长”这类不在数据库里的问题就要靠RAG了。Spring AI Alibaba也提供了向量数据库和文档解析的抽象通常做法是把产品文档切块、向量化后存到DashVector或Redis向量库然后注册一个“知识库查询”的Tool让模型决定何时检索。这么一来Agent就有两个知识来源结构化数据走SQL工具非结构化文档走向量检索工具。两条腿走路覆盖面就上来了。6.3 增加任务编排与人工确认再往上走Agent还需要有“多步任务编排”和“人工确认”机制。比如用户说“帮我把第3号订单状态改一下并且通知仓库那边备货”这就涉及写操作了。写操作的Agent不能像查询一样直接执行正确的姿势是Agent生成一个操作计划返回一个确认页面给用户用户点了确认之后才真正执行。这个场景里Spring AI Alibaba支持把工具返回类型定义成结构化对象由框架解析后走你的业务流程而不是简单的字符串拼接。这类设计需要你从Agent架构层面提前规划好权限边界建议在实体工具被调用前先过一层“意图审批”逻辑。7. 实战中的常见问题与排查技巧7.1 模型始终不调用我定义的工具这是最常遇到的问题。你Tool定义好了用户也按预期问了问题但模型就是不用工具反而用自己的常识硬答。我通常按这个顺序排查工具描述是否清晰如果description含糊其辞模型识别不到“该不该用”。比如“查询订单数量”和“统计商品总数量”如果区别不大模型容易选错或干脆不选。模型能力是否足够qwen-turbo和qwen-plus在Tool Calling上的稳定度有明显差距如果是复杂场景建议直接上qwen-plus或更强型号。系统Prompt是否限制了工具使用某些Prompt里写了“请直接回答”模型可能就不会走工具链路需要调整措辞。工具类是否真的被扫描到检查启动日志如果没看到Tool相关注册信息多半是依赖没引入全。7.2 工具调用成功但结果回答异常这类问题非常典型我看日志工具被调用了返回了正确数据但最终模型的回答还是错的。常见原因是工具返回的数据结构和模型理解之间有偏差。排查办法是把工具返回结果尽量改成“人话”字符串而不是一坨原始JSON。例如我示例里的countOrders方法返回“订单总数4”模型看到这句话就能直接组装答案如果返回的是Map或对象模型也能解析但对小参数模型来说容易出现理解偏差所以工具返回值尽量转成自然语言描述。7.3 版本冲突导致启动失败Spring AI Alibaba目前迭代速度较快不同版本之间API可能有细微波动。我见过最多的报错是NoClassDefFoundError和NoSuchMethodError十有八九是Spring AI Alibaba和Spring Boot、JDK版本没对齐。我实测下来这套组合是稳的组件推荐版本Spring Boot3.2.xJDK17Spring AI Alibaba1.0.0DashScope API兼容OpenAI协议端点如果遇到奇怪报错先做减法把自定义配置去掉用最小配置启动逐步加回依赖很快就能定位是谁导致的冲突。7.4 对话记忆越聊越乱这是做多轮对话Agent时绕不开的体验问题。当上下文长了之后ChatMemory会把全部历史消息发给模型Token消耗变大模型回答的焦点也开始漂移。尤其是用户中途切换了话题模型容易把新话题和旧历史混在一起。我的习惯是给Memory加一个“最近N条消息”的窗口而不是无限累积。另外在关键节点上用Prompt要求模型“忽略与当前问题无关的历史信息”。虽然这不能100%解决焦点漂移但实测下来能明显改善。7.5 本地调试时想细看模型“怎么想的”Spring AI Alibaba可以开启日志级别的调试信息查看模型请求和响应payload。在application.yml里这样配置logging: level: com.alibaba.cloud.ai: DEBUG开了之后你能在日志里看到模型返回的toolCalls详情、每次调用的输入输出参数。对理解Agent决策过程非常有帮助强烈建议刚开始做Agent开发时把日志调高观察几轮。8. 我对Spring AI Alibaba构建Agent的实操感受整套流程走下来我的一个强烈感受是Spring AI Alibaba把做Agent的门槛拉到了“会Spring Boot就能上手”的程度。比起之前在Java里裸调大模型API省掉了太多繁文缛节。特别是Tool注解这套设计把“让模型使用你的方法”这件事变得非常顺手不需要手工维护JSON Schema方法签名变了Tool说明自动跟着变这个体验在开发调试时尤其舒服。不过也要说句公道话Spring AI Alibaba还处于快速演进期版本之间的API变化确实偏快。所以如果你准备在企业项目里正式引入建议锁死版本不要把依赖写成latest或带SNAPSHOT的坐标否则升级一次哭一次。另外生产环境一定要对Tool做权限管控不是所有方法都适合暴露给模型站在安全角度给Agent的工具要尽量窄、尽量专用。最后再分享一个悄悄摸出来的技巧写工具description的时候把“边界”也写进去。比如查询工具里写明“只能查询非脱敏字段”这样就算模型收到一个涉及脱敏字段的问题也不会往你的限制外钻。Agent的安全感往往就是靠这些细节堆出来的。如果你正准备在Java项目里落地Agent或者正纠结选哪个框架我的建议是直接动手拿Spring AI Alibaba写一个最简单的查询Agent。不要光看文档亲手把“模型-工具-数据库”这条链路跑通了你对Agent的理解会往上跳一大截。
返回列表