新闻详情

新闻详情

首页 / 资讯中心 / 详情

Spring AI Alibaba工具调用实战:让大模型从能聊到能干

发布时间:2026/10/2 9:00:31来源:尧图网络
Spring AI Alibaba工具调用实战:让大模型从能聊到能干
前四篇我们把 Spring AI Alibaba 的“地基”打完了从最基础的 ChatClient 起步到 Prompt 模板、结构化输出再到多轮对话和向量检索。但很多同学卡在一个地方模型聊得很溜一让它“干点活”就露馅——查不了订单、算不了折扣、调不了内部接口。这一篇正好把最后一块拼图补上工具调用Tool Calling和函数计算让模型从“能聊”变成“能干”。如果你正在用 Spring AI Alibaba 搭客服机器人、内部问答助手或者流程自动化脚本这篇就是给你准备的。我会把原理、代码、踩坑一次性说透代码全部基于 Spring AI Alibaba 实际可跑的写法你照着复制就能用。1. 为什么第五篇才聊工具调用1.1 前四篇的“地基”到底搭了什么先快速回顾一下这个系列走过的路免得有同学从中间插入看不懂。第一篇我们做了环境搭建和 ChatClient 的最小调用第二篇把 Prompt 模板和结构化输出理清了第三篇处理多轮对话和 Memory 机制第四篇搞了向量化和 RAG 的基本链路。到这一步模型其实已经具备“记忆”和“知识库”的能力但它仍然是纯文本进、纯文本出。什么概念呢你问它“帮我查一下订单 xxxx 的状态”它要么说“我无法访问外部系统”要么就瞎编一个状态。这不是模型笨而是它没有“手”。工具调用就是给模型装上这只手。这个系列一路写到第五篇我觉得顺序是有讲究的先解决“模型说什么”再解决“怎么说得准”现在才轮到“怎么做到”。如果一上来就讲工具调用你大概率会被参数格式、方法注册、函数结果回传这些概念绕晕。现在基础打好了再来看 Tool Calling你会发现它本质上就是一个“模型决定调哪个方法、你负责执行、再把结果喂回去”的循环。1.2 工具调用到底解决了什么问题传统基于规则的机器人写起来很痛苦你要定义几百个意图每个意图写一套关键词匹配还要想尽办法处理用户的各种奇怪说法。模型对话能力强但它不具备实时数据和业务操作能力。工具调用恰好把两者缝合起来。举个例子用户说“我想取消昨天买的那件衬衫能退多少钱”。这句话里有三个隐含动作查订单、判断退款规则、计算退款金额。如果纯靠模型它一个都做不了。如果用工具调用模型会自主地把这句话拆解成几次函数调用先查出订单信息再调退款计算规则最后把结果组织成自然语言回复给你。对业务方来说工具调用的价值还不止于此。它是让大模型安全地触达内部系统的方式之一因为你可以把数据库查询、订单操作、库存扣减这些敏感功能封装成受控的工具模型只能调用你暴露出来的那些方法不能自由发挥。这种“能力边界”的划定在实际生产环境里极其重要。1.3 Spring AI Alibaba 在这条路上做了什么Spring AI Alibaba 的工具调用能力建立在 Spring AI 的 Tool 注解体系之上但它做了一些符合国内场景的适配。最直观的一点是你不需要写一堆 JSON Schema。在原生 OpenAI Function Calling 里你得手写每个函数的 parameters 定义字段类型、必填项、描述信息都要自己维护一旦函数参数变了还要同步改 Schema非常容易漏。Spring AI Alibaba 里你只需要定义一个普通 Java 方法加上 Tool 注解框架会自动帮你生成模型需要的 JSON Schema。另外它对 Spring Cloud 生态很友好。你可以直接把已有的 Service、Mapper、FeignClient 作为工具暴露给模型不用专门为了模型重写一套接口。我在实际项目里就是直接把订单服务的查询方法暴露出去的改动量很小。2. 动手前先搞懂工具调用原理2.1 从 Tool 注解说起我先给你看一段最小的工具代码Service public class OrderService { Tool(description 根据订单ID查询订单状态) public String getOrderStatus(String orderId) { // 这里可以是数据库查询、HTTP调用等等 return 订单[ orderId ]状态为已发货物流单号 SF1234567890; } }看到没有一个普通的 Spring Bean 方法加一个 Tool 注解就能被模型识别。description 字段特别关键它是模型判断“什么时候调用这个方法”的依据。你写“根据订单ID查询订单状态”模型就知道用户提到订单时该来这里找数据。这里有个细节值得注意返回值尽量用 String 或者序列化友好的对象。因为框架要把方法的返回值拼装成消息再回传给模型如果返回的是复杂对象框架会尝试序列化成 JSON如果序列化失败整个链路就断了。我在项目里一般让工具方法返回 JSON 字符串省去很多麻烦。2.2 模型侧的工作机制虽然你写起来只是个注解但底层实际走了一个完整的“请求-决策-调用-反馈”循环。大致流程是这样的你把用户消息发给模型时框架会把所有注册过的工具定义转成模型能读的 JSON Schema一起塞进请求里。模型读一遍用户消息和工具清单判断“这个问题我需要调用工具吗”如果需要就返回一个 tool_calls 指令而不是直接回答用户。框架拦截到 tool_calls根据工具名找到对应的 Spring Bean 方法解析参数并执行。方法执行结果被包装成 ToolResult 消息再次发给模型。模型看到工具返回的结果组织成最终的回复返回给用户。所以你在代码里看到的只有“调用 ChatClient 一次”但底层可能已经悄悄地来回了两三次。这就是为什么工具调用比普通聊天慢一点的原因之一网络请求次数变多了。明白了这个循环你就能理解很多问题的根源。比如模型不调用工具多半是工具描述写得太模糊模型不知道什么时候该用。再比如模型调用了错误的参数大概率是参数名和描述写得不好模型没法正确理解。后面排查部分我会细讲。3. 完整实操给 AI 配一套“查天气 查订单 算优惠”的工具组3.1 工程准备与依赖引入这一篇的代码承接第四篇的工程。如果你是从头开始建议先建一个 Spring Boot 3.x 项目JDK 17 以上然后引入这几个依赖dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version最新版本号/version /dependency如果你要用通义千问的模型还需要配置对应的模型 endpoint。这个系列第一篇已经有完整配置说明我这里直接给一个可用的最小配置spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus注意这里我用的还是 DashScope 作为模型通道。Spring AI Alibaba 本身支持多家模型工具调用的逻辑是通用的换模型通道不影响你写工具方法。3.2 定义订单查询工具现在我来定义一个稍微真实一点的工具组包含查订单、查天气、计算优惠三个能力。别笑这组合在电商客服场景里真的很常见用户一句话里可能同时包含“天气影响配送”和“订单优惠”两个意图。先看订单查询Component public class OrderTools { Tool(description 根据用户提供的订单号查询订单详情返回订单状态、商品名称、金额、收货地址) public String queryOrder(String orderId) { // 模拟查询数据库 MapString, Object order Map.of( orderId, orderId, status, 已付款, productName, 春季轻薄款风衣, amount, 399.00, address, 浙江省杭州市西湖区xx路xx号 ); return JSON.toJSONString(order); } Tool(description 根据订单号查询退款金额退款金额 订单金额 - 已使用的优惠金额) public String queryRefundAmount(String orderId, Double couponAmount) { // 模拟从订单服务获取金额 double orderAmount 399.00; double refund orderAmount - (couponAmount null ? 0 : couponAmount); return 该订单可退款金额为 refund 元; } }这里我用了 Fastjson 的 JSON.toJSONString实际上你用 Jackson 也可以只要最终是合法 JSON 字符串就行。为什么工具方法返回值要统一成 String因为聊天模型处理的都是文本你返回一个 Java 对象框架确实可以帮你序列化但那一步多多少少会有一些不确定性不如自己在方法里控制。还有个点需要注意方法参数名一定要和实际业务含义一致。模型虽然是聪明的但它没见过你的 Java 源码只能通过参数名和描述来猜测每个参数填什么。我把参数命名为 orderId 而不是 id就是为了让模型更容易理解。3.3 把工具注册进 ChatClient工具方法定义好之后注册过程比你想的简单。在 Spring AI Alibaba 里你只需要在构建 ChatClient 的时候把工具类的 Bean 传进去即可。Component public class ToolCallingService { private final ChatClient chatClient; public ToolCallingService(ChatClient.Builder builder, OrderTools orderTools) { this.chatClient builder .defaultTools(orderTools) .build(); } public String chat(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } }就这么几行orderTools 里的所有 Tool 方法都变成模型可调用的工具了。你用起来和普通聊天一模一样但模型内部已经有能力去调用 queryOrder 和 queryRefundAmount 了。写个简单的测试入口RestController public class ChatController { private final ToolCallingService toolCallingService; public ChatController(ToolCallingService toolCallingService) { this.toolCallingService toolCallingService; } GetMapping(/chat) public String chat(RequestParam String message) { return toolCallingService.chat(message); } }启动项目浏览器访问http://localhost:8080/chat?message帮我查一下订单20240511001的状态你会看到模型返回订单信息。日志里能看到模型实际上先返回了一个 tool_calls然后框架自动执行了 queryOrder 方法把结果回传给模型后才生成最终回答。3.4 加一个计算优惠的工具看看模型怎么组合多个工具单工具调用很多人都会但实际业务往往需要多个工具配合。我在订单工具旁边加一个优惠计算Tool(description 根据订单金额和用户会员等级计算优惠金额会员等级分为普通、银卡、金卡折扣分别为0%、5%、10%) public String calculateDiscount(Double orderAmount, String memberLevel) { double discountRate switch (memberLevel) { case 银卡 - 0.05; case 金卡 - 0.10; default - 0.0; }; double discount orderAmount * discountRate; return 优惠金额为 discount 元; }注意我这里的 description 写得特别具体把会员等级的可选值和对应折扣都写进去了。这种细节决定模型调用工具的准确性别偷懒。然后你问它“我是金卡会员订单 20240511001 金额 399能优惠多少”模型会分两步走先调 queryOrder 拿到实际订单金额再调 calculateDiscount 计算出优惠金额。这个链路不需要你写任何编排代码模型自己会决策。我在本地实测下来qwen-plus 对多工具组合的准确率还不错但偶尔也会出现“只调用第一个工具就不继续”的情况。这时候可以把模型切换成 qwen-max复杂工具链路的成功率会高不少当然响应也会更慢一点。生产环境建议根据业务复杂度做模型分级。3.5 多轮对话让模型记住工具结果工具调用和流式输出一起用时最容易踩坑。我先说多轮对话场景。默认情况下ChatClient 的每次 prompt 都是独立的工具调用结果只在当前这一轮生效。如果你的业务是“用户先问订单状态再接着问那这个订单能退多少”第二句话其实丢了第一轮的订单信息。这时需要用到 Memory。Spring AI Alibaba 里可以给 ChatClient 挂一个内存级别的 ChatMemoryService public class MemoryToolCallingService { private final ChatClient chatClient; public MemoryToolCallingService(ChatClient.Builder builder, OrderTools orderTools) { ChatMemory chatMemory MessageWindowChatMemory.builder() .maxMessages(20) .build(); this.chatClient builder .defaultTools(orderTools) .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) .build(); } }配置了 Memory 之后工具调用的结果会作为历史消息的一部分保留下来后续用户追问时模型能拿到上文数据。这一点太重要了真实业务不会有人每句话都完整描述一遍。我见过不少团队做工具调用 Demo 时很顺利一上生产就发现对话驴唇不对马嘴多半是没接 Memory。这里再多说一句Memory 的容量要控制好。工具返回结果往往是一大段 JSON如果存太多轮Token 消耗会非常快。我习惯把 maxMessages 控制在 10 到 20 之间再配合按用户维度做 session 隔离避免不同用户之间的上下文串线。4. 常见问题与排查实录4.1 模型就是不调用工具怎么办这是遇到最多的问题。你工具方法写得没毛病但模型回答“我暂时无法查询订单信息”摆明了没走工具。我把这类问题分成三个层面来排查。第一层工具有没有被注册上。检查 ChatClient 构建时是否传了 defaultTools或者你是否用了手动配置的 ToolCallingManager。很多人复制代码时会漏掉这一步。第二层工具描述是否清晰。我遇到过一个同学给工具起的名字是 processData描述写“处理用户数据”结果模型永远不知道该在什么时候调它。工具描述就是给模型看的“使用说明书”你要像教新同事一样写清楚什么场景用、入参是什么、返回值是什么。第三层模型本身是否支持工具调用。虽然现在主流模型都支持 Function Calling但个别开源模型或者能力较弱的模型可能不稳定。排查方法很简单换个支持度好的模型试试比如通义千问的 qwen-plus 以上版本立刻能定位是不是模型能力问题。4.2 工具方法参数绑定报错这类错误通常表现为“argument type mismatch”或者“无法将 x 转换为 y”。原因多半是模型返回的参数和你的方法签名对不上。比如我定义了一个方法参数是 int 类型的 quantity模型返回的 JSON 里却是字符串 3就会出问题。Spring AI Alibaba 框架会做类型转换但不是所有场景都能转换成功。我的建议是工具方法参数尽量用 String、Integer、Double 这类基础类型参数数量不要超过4个。如果必须传复杂对象把它拆成多个基础类型参数。另外每个参数最好加上 Parameter(description xxx) 注解给模型更多提示。Tool(description 根据商品ID和数量计算总价) public String calculateTotalPrice( Parameter(description 商品ID例如1001) String productId, Parameter(description 购买数量必须是数字) Integer quantity) { // 业务逻辑 return ; }4.3 流式输出时工具调用失效这条我必须重点说因为太容易被忽视了。如果你用 streaming 方式输出内容并且工具调用过程比较慢客户端可能已经超时断开或者框架在流式上下文里没有正确等待 tool_calls 执行完成。我之前遇到的情况是普通 call() 方式一切正常换成 stream() 之后模型直接跳过工具调用开始胡说。查了半天发现问题出在我把超时时间设置得太短工具调用链路实际要走两到三次模型请求总耗时超过了我设置的 10 秒超时框架直接把请求断掉了。解决方案是给流式调用设置合理的超时时间同时做好前端的“等待状态”提示。如果是内部服务间的调用我甚至建议先走同步 call()把工具链路跑通后再考虑流式优化。工具链路都没跑通就追求流式体验只会让排查难度翻倍。4.4 工具调用返回结果太长导致 Token 爆炸这个坑其实很多生产项目会踩。你让工具查一个订单工具很实诚地返回了 2KB 的 JSON 完整字段模型拿着这堆东西再总结一遍Token 消耗很惊人。我做过一个库存查询工具一开始直接返回数据库里全部字段几十个商品属性全塞进去。结果每次工具调用的 Token 消耗翻了十倍都不止一天下来成本肉眼可见地涨。后来我把工具返回值做成精简版只返回模型真正需要参与回答的字段。同时把大段列表类数据截断比如最多返回前 20 条并提示模型“如需查看更多数据请再次调用”。这个优化做完成本降了 60%回答质量反而更稳定。工具调用不是把所有数据都倒给模型而是只给模型完成当前任务所需的最小信息集。5. 工具调用与 RAG 的搭配实践5.1 为什么要让工具和 RAG 协同工作第四篇我们做了 RAG让模型能从知识库里找答案。但 RAG 有一个天然的短板知识库是静态的你不可能把订单状态、实时库存、用户会员等级这些动态数据提前索引进去。所以你会发现很多复杂的业务提问光靠 RAG 或者光靠工具调用都搞不定。比如“根据我们的退货政策我这个订单能退多少钱”这里一半是知识库内容退货政策一半是实时数据订单金额、优惠信息。这就是典型的 RAG 工具调用协同场景。Spring AI Alibaba 支持在一个请求里同时使用向量检索和工具调用你不需要自己排优先级模型会根据问题内容自动决定先走哪一条路甚至两者并行。5.2 一个典型的协同场景拆解我实际做过一个售后助手流程是这样的用户提问进来后先做向量检索找到知识库里最相关的政策文本然后把政策文本和用户问题一起传给模型同时系统里已经注册好了订单查询工具和退款计算工具。模型看到政策里提到“七天无理由退货”之后会主动去调订单工具核实订单是否在有效期内。这个方案的好处是知识库给了模型“该不该做”的判断依据工具调用给了模型“能不能做”的实时验证。两者互相补充回答的可靠性比单用任何一个都高很多。具体实现上你只需要在 ChatClient 里同时配置 Advisor 和 ToolSpring AI Alibaba 会帮你组合好this.chatClient builder .defaultTools(orderTools) .defaultAdvisors( new MessageChatMemoryAdvisor(chatMemory), new QuestionAnswerAdvisor(vectorStore) ) .build();注意 RAG 的向量存储依赖我们在第四篇已经配置过这里直接复用就行。实际测试下来混合链路确实会比纯工具调用慢一些因为向量检索本身也有耗时。但回答质量是值得的。5.3 给工具调用加一层“权限闸门”既然工具调用能触达你的真实业务系统就不得不提安全问题。生产环境里我强烈建议你在工具方法内部再加一道权限校验而不是只靠框架层面做限制。最简单的做法是工具方法接收当前用户的标识在执行前先确认该用户是否有权限查询这个订单。Spring AI Alibaba 在工具调用时可以从上下文中拿到当前会话信息但不同版本 API 略有差异。保守起见我把用户标识直接作为工具方法的参数传进去让模型在调用时带上然后方法内部再做校验。Tool(description 根据用户ID和订单ID查询订单用户只能查询自己的订单) public String queryOrderWithAuth( Parameter(description 当前登录用户ID) String userId, Parameter(description 订单ID) String orderId) { // 校验 userId 是否有权限查看该订单 if (!orderPermissionService.canAccess(userId, orderId)) { return 无权限访问该订单; } return orderService.queryOrder(orderId); }这个方法看起来多传了一个参数有点啰嗦但它在多租户场景下几乎是必需的。否则任何用户都可以诱导模型去查别人的订单这属于安全红线。你还应该在工具方法里做好日志审计谁的账号、在什么时间、调用了什么工具、返回了什么结果全都要记录。大模型应用的审计比传统应用更重要因为你无法完全预测模型会以什么方式触发工具。6. 写在最后的实操心得工具调用这个功能代码量其实不大但把它用“稳”才是真正的考验。我折腾了半个月才理清一条自己的套路分享出来供你参考。不要一上来就暴露所有工具。很多团队第一期就把十几个 Service 方法全部加上 Tool结果模型选择困难调用准确率直线下降。我会控制在三到五个核心工具起步跑通之后再加。工具越多模型的选择负担越大你的测试成本也越大。工具描述一定要经过多轮打磨。第一次写的描述和实际效果之间一定有差距不要嫌麻烦。我通常在项目里放一个“工具调用测试集”大概二十条典型问题每次修改描述后全部跑一遍确保没有回归。测试集里一定要包含“不该调用工具”的问题比如用户闲聊时模型不应该乱查数据。保持工具方法无状态。工具方法内部不要依赖调用之间的共享状态每次调用都是独立地“输入到输出”这样最可靠。如果多个工具之间确实需要传递数据让模型通过参数来协调而不是在 Java 层偷偷保存临时状态。后面排查问题时你会感谢这个设计的。最后想提醒你的是工具调用打开了一扇门让大模型从聊天工具变成了业务系统的一部分。能力越强责任越大。请一定做好权限控制、参数校验、日志审计这三件事之后再上生产。这也算是我在多次踩坑之后最想让你避开的那个坑。下一篇我打算写 Spring AI Alibaba 的 Agent 编排如果你在这篇文章的工具调用环节遇到任何问题欢迎按照我上面给的排查思路先自查一遍。如果还解决不了带着你的模型版本、完整配置和工具定义来交流光说“不调用工具”真的没法定位问题。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

mysql_mcp_server quickstart:把 MCP 配置改到 TaoToken 的完整跑通记录 2026/10/2 12:24:00

mysql_mcp_server quickstart:把 MCP 配置改到 TaoToken 的完整跑通记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
透过招聘读懂3D视觉公司:图漾科技求职与职业成长指南 2026/10/2 12:23:59

透过招聘读懂3D视觉公司:图漾科技求职与职业成长指南

1. 从一条招聘标题里,读出这家公司的真实信号先别急着划过这条标题。很多人在刷到“招贤纳士,某某科技等待您的加入”这类信息时,第一反应是“哦,一家公司要招人”,然后就直接关掉了。但作为一个在科技行业里待了十几年…

阅读更多 →
GitHub周榜项目怎么跑起来?从热词看真实需求与落地路径 2026/10/2 12:23:59

GitHub周榜项目怎么跑起来?从热词看真实需求与落地路径

1. 周榜热榜到底在热什么:从关键词反推真实需求每周刷一次热榜,已经成了我固定的信息摄入习惯。2026年9月27日这一期的周榜,表面上看是一串项目名字的排列,但把相关热搜词摊开来看,会发现一个很有意思的现象&#xff1…

阅读更多 →
告别漏洞型加班:SAST静态扫描从原理到CI/CD落地 2026/10/2 12:23:59

告别漏洞型加班:SAST静态扫描从原理到CI/CD落地

1. 谁动了你的下班时间“漏洞型加班”,这个词我见过太多次。所谓“漏洞型加班”,不是指每天都有新漏洞爆发,而是指——你辛辛苦苦写完代码、上线跑了一段时间,某天临下班前,测试或安全同事突然丢来一个链接&#xff0c…

阅读更多 →
零门槛AI桌面换装视频教程:原理、流程与避坑指南 2026/10/2 12:23:52

零门槛AI桌面换装视频教程:原理、流程与避坑指南

1. 先把话说清楚:AI 桌面换装视频到底是什么东西最近刷短视频,应该都刷到过那种“桌面视角”的换装视频:屏幕上是电脑桌面,桌面上放着一杯咖啡、几本书、一个手机支架,镜头对着桌面上的一小面镜子,镜子里映…

阅读更多 →
本地部署的8B/14B开源大模型,function call能力到底行不行?TaoToken实测清单 2026/10/2 12:23:52

本地部署的8B/14B开源大模型,function call能力到底行不行?TaoToken实测清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉