乐于分享
好东西不私藏

Spring AI 2.0 入门第三课:Tool Calling——让AI真正“动手干活”

Spring AI 2.0 入门第三课:Tool Calling——让AI真正“动手干活”

从“聊天”到“执行”,三行代码让大模型调用你的Java方法

前言:当AI只会“动嘴”的时候

前两篇文章,我们教会了AI说话——它能回答你的问题、按你的要求生成代码。但仔细想想,这跟雇了一个只会纸上谈兵的顾问有什么区别?你问它“北京今天天气怎么样”,它倒是能侃侃而谈“北京属于温带季风气候,四季分明”,但就是给不出今天的实时温度。

为什么?因为大模型的训练数据是有截止日期的。它根本不知道今天是几号,更不知道此时此刻北京是晴是雨。

Tool Calling(工具调用)要解决的问题,就是让AI不再只是“动嘴”,而是能“动手”。当用户问天气时,AI会主动调用你写的天气查询方法,拿到真实数据后再组织成回答返回给用户。

整个过程对用户来说,体验就像AI真的“知道”天气一样——但背后,是你的Java代码在干活。

Spring AI 2.0对Tool Calling机制进行了彻底重构,把它从底层实现提升到了Advisor链中的一等公民。今天这篇文章,我们就从零开始,用阿里百炼(通义千问) 为例,一步步实现一个能查天气的AI应用。

核心概念:Tool Calling到底是怎么工作的?

在动手之前,先搞清楚一件事:大模型本身不执行任何工具——它只负责“决定”要不要调用工具、以及传什么参数。真正干活的,是你的Java代码。

整个流程是这样的:

            用户:北京今天天气怎么样?

                  ↓

         大模型:判断需要调用“天气工具”

                   ↓

     Spring AI:执行你的Java方法(getWeather("北京"))

                    ↓

          大模型:拿到结果“晴,28°C”,组织成自然语言

                    ↓

              用户:收到“北京今天晴,气温28°C”

Tool Calling的核心价值在于:大模型充当“大脑”做决策,你的Java方法充当“手脚”去执行。大脑负责判断“该干什么”,手脚负责“具体怎么干”。

Spring AI 2.0相比1.x最大的变化是:工具调用循环从模型内部实现,提升到了Advisor链,开发者可以观察、拦截、组合每一个中间步骤。ToolCallingAdvisor会负责完整的工具执行生命周期:发现工具、执行、循环直到模型不再请求工具为止。

第一步:项目配置(基于阿里百炼)

沿用前两篇文章的基础配置,在pom.xml中加入Spring AI 2.0的BOM和OpenAI Starter(兼容阿里百炼):

<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>org.springframework.ai</groupId>

            <artifactId>spring-ai-bom</artifactId>

            <version>2.0.0</version>

            <type>pom</type>

            <scope>import</scope>

        </dependency>

    </dependencies>

</dependencyManagement>

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.ai</groupId>

        <artifactId>spring-ai-starter-model-openai</artifactId>

    </dependency>

</dependencies>

application.yml配置阿里百炼的接入地址:

spring:

  ai:

    openai:

      api-key: ${DASH_SCOPE_API_KEY}

      base-url: https://dashscope.aliyuncs.com/compatible-mode

      chat:

        options:

          model: qwen-plus

⚠️ 注意:通义千问(qwen-plus/qwen-max)完全支持Tool Calling功能。如果使用的是其他模型,请确认模型是否支持工具调用。

第二步:定义工具——用@Tool注解

Spring AI 2.0提供了声明式的工具定义方式——@Tool注解。你只需要在一个普通方法上加上@Tool注解,Spring AI会自动生成JSON Schema并注册到模型。

先写一个模拟的天气服务(真实场景中这里会调用第三方天气API):

import org.springframework.ai.tool.annotation.Tool;

import org.springframework.ai.tool.annotation.ToolParam;

import org.springframework.stereotype.Component;

@Component

public class WeatherTools {

    /**

     * 模拟的天气数据源——实际项目中替换为真实API调用

     */

    private static final Map<String, String> WEATHER_MOCK = Map.of(

        "北京", "晴,28°C,湿度45%",

        "上海", "多云,26°C,湿度65%",

        "深圳", "阵雨,30°C,湿度80%",

        "杭州", "晴,27°C,湿度50%"

    );

    @Tool(name = "getWeather", description = "获取指定城市的实时天气信息")

    public String getWeather(

            @ToolParam(description = "城市名称,如:北京、上海、深圳") String city) {

        // 模拟调用外部API——真实场景替换为HTTP请求

        String weather = WEATHER_MOCK.getOrDefault(city, "未知城市,请确认城市名称");

        return String.format("城市:%s,天气:%s", city, weather);

    }

}

代码解析:

  • @Tool(name="getWeather"):工具名称,大模型通过这个名字来调用

  • @ToolParam(description="..."):参数描述,帮助大模型理解该传什么值

  • 方法的返回值会直接作为工具结果返回给大模型

第三步:注册工具到ChatClient

Spring AI 2.0中,工具通过.tools()方法显式注册:

@Configuration

public class AiConfiguration {

    @Bean

    public ChatClient chatClient(

            ChatClient.Builder builder,

            WeatherTools weatherTools) {

        return builder

                .defaultTools(weatherTools)  // 注册工具

                .build();

    }

}

如果你不想全局注册,也可以在每次调用时临时注册:

@RestController

public class ChatController {

    private final ChatClient chatClient;

    private final WeatherTools weatherTools;

    public ChatController(ChatClient chatClient, WeatherTools weatherTools) {

        this.chatClient = chatClient;

        this.weatherTools = weatherTools;

    }

    @GetMapping("/chat")

    public String chat(@RequestParam String message) {

        return chatClient.prompt()

                .user(message)

                .tools(weatherTools)  // 本次调用注册工具

                .call()

                .content();

    }

}

第四步:测试——让AI调用天气工具

启动应用,访问:

http://localhost:8080/api/ai/chat?message=北京今天天气怎么样

控制台会输出:
Tool called: getWeather("北京")

返回结果:

北京今天晴,气温28°C,湿度45%。天气不错,适合出门活动。

发生了什么:

  1. 大模型收到“北京今天天气怎么样”

  2. 模型判断需要调用getWeather工具,参数city="北京"

  3. Spring AI执行WeatherTools.getWeather("北京")

  4. 工具结果返回给模型

  5. 模型基于结果生成最终回答

如果问一个没注册工具的问题,比如“讲个笑话”,模型不会调用任何工具,直接回答——工具是按需调用的,不是每次必调。

实战进阶:支持多参数的工具

天气查询只需要一个参数。但真实场景中,工具往往需要多个参数——比如订票:

@Component

public class BookingTools {

    @Tool(description = "预订机票,根据出发地、目的地和日期查询航班并预订")

    public String bookFlight(

            @ToolParam(description = "出发城市") String origin,

            @ToolParam(description = "目的地城市") String destination,

            @ToolParam(description = "出行日期,格式:YYYY-MM-DD") String date) {

        // 模拟订票逻辑——真实场景调用航司API

        return String.format("已为您预订 %s → %s 的机票,日期:%s,订单号:FL-%d",

                origin, destination, date, System.currentTimeMillis() % 100000);

    }

}

多个参数时,Spring AI会自动生成对应的JSON Schema,大模型会根据用户的问题自动提取所有参数值。用户问“帮我订一张从北京到上海的机票,明天出发”,模型会自动提取origin=北京、destination=上海、date=明天(转换为具体日期)。

进阶:理解ToolCallingAdvisor

在Spring AI 2.0中,工具调用是由ToolCallingAdvisor驱动的。它是一个递归Advisor——会反复进入下游Advisor链,直到模型不再请求工具为止。

DefaultChatClient会自动将ToolCallingAdvisor添加到Advisor链中,它负责完整的工具执行生命周期:

  1. 提取工具定义:从@Tool注解中提取工具的名称、描述、输入参数的JSON Schema

  2. 注入上下文:将工具定义与用户问题、系统提示一起发送给LLM

  3. 循环执行:

    • LLM返回包含工具调用的响应 → 执行工具 → 将结果追加到对话历史 → 继续

    • LLM返回不含工具调用的响应 → 结束循环,返回最终答案

阻塞模式(.call())和流式模式(.stream())都完全支持。

💡 了解这个机制的意义:当你需要观察工具调用的中间步骤、记录日志、或者实现自定义的循环逻辑时,就知道该从哪里介入了。

⚠️ 避坑指南

坑1:用旧版的FunctionCallback API

Spring AI 2.0已经废弃了FunctionCallback,全面转向ToolCallback。如果你从1.x迁移过来,注意以下变化:

  • FunctionCallback → ToolCallback

  • .functions() → .tools()

  • @Function → @Tool

坑2:工具参数用复杂对象

工具方法的参数尽量用简单类型(String、int、double等)。复杂对象容易导致JSON解析问题。如果确实需要多参数,用多个简单类型参数,每个都加上@ToolParam(description="...")。

坑3:忘记给工具加描述

@Tool的description和@ToolParam的description不是注释,是给大模型看的指令。描述越清晰,模型判断“什么时候该调用这个工具”就越准确。

坑4:认为工具是“自动发现”的

Spring AI 2.0中,工具必须显式注册——通过defaultTools()或.tools()传入。光在类上写@Tool注解不够,还得告诉ChatClient这个工具的存在。

坑5:工具执行时间过长

大模型等待工具返回时有超时限制。如果工具方法执行时间过长(比如调用外部API超时),整个请求会失败。建议:

  • 给外部调用设置合理的超时时间

  • 考虑异步处理 + 轮询的模式

小结

  • Tool Calling让大模型从“聊天”升级到“执行” ——模型决策,Java代码执行

  • @Tool注解是定义工具的最简单方式,Spring AI自动生成JSON Schema

  • @ToolParam 为每个参数添加描述,帮助模型准确提取参数值

  • 工具通过.tools()或defaultTools()显式注册到ChatClient

  • ToolCallingAdvisor 驱动整个工具调用循环,自动处理多轮调用

  • 从1.x迁移注意:FunctionCallback → ToolCallback,.functions() → .tools()

工具调用是构建AI Agent的基石——有了它,AI才能真正“动手”帮你做事。不只是查天气,查数据库、发邮件、创建订单、执行代码——任何你能用Java做的事,现在都可以让AI来“指挥”完成。


下一篇预告:RAG(检索增强生成)——让AI基于你的私有文档回答问题。当用户问“我们公司的报销流程是什么”,AI不再胡说八道,而是从你上传的《员工手册》中检索答案。敬请期待!

相关学习资料