本案例展示了 Spring AI Alibaba 中 Tool Calling 功能的四种不同实现方式,通过四个独立的示例来演示如何将外部工具集成到 AI 应用中,增强 AI 的能力。

1. 案例目标

我们将创建一个包含四种不同 Tool Calling 实现方式的 Web 应用:

  1. TimeController:演示如何将方法作为工具使用 (Methods as Tools)
  2. AddressController:演示如何使用 MethodToolCallback 将方法作为工具
  3. BaiduTranslateController:演示如何通过函数名将函数作为工具使用 (Function as Tools - Function Name)
  4. WeatherController:演示如何使用 FunctionCallBack 将函数作为工具

2. 技术栈与核心依赖

  • Spring Boot 3.x
  • Spring AI Alibaba (用于对接阿里云 DashScope 通义大模型)
  • Maven (项目构建工具)
  • 百度翻译 API (提供翻译功能)
  • 百度地图 API (提供地址信息查询功能)
  • Weather API (提供天气信息查询功能)
  • 时间服务 (提供时间查询功能)

在 pom.xml 中,你需要引入以下核心依赖:

<dependencies>
    <!-- Spring Web 用于构建 RESTful API -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring AI Alibaba 核心启动器,集成 DashScope -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
    </dependency>
    
    <!-- 百度翻译工具 -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-tool-calling-baidutranslate</artifactId>
    </dependency>
    
    <!-- 天气查询工具 -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-tool-calling-weather</artifactId>
    </dependency>
    
    <!-- 百度地图工具 -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-tool-calling-baidumap</artifactId>
    </dependency>
    
    <!-- 时间工具 -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-tool-calling-time</artifactId>
    </dependency>
    
    <!-- GitHub 工具包 -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-tool-calling-githubtoolkit</artifactId>
    </dependency>
</dependencies>

3. 项目配置

在 src/main/resources/application.yml 文件中,配置你的 API Key。

spring:
  ai:
    alibaba:
      toolcalling:
        baidu:
          translate:
            enabled: true
            app-id: ${BAIDU_TRANSLATE_APP_ID}
            secret-key: ${BAIDU_TRANSLATE_SECRET_KEY}
          map:
            enabled: true
            apiKey: ${BAIDU_MAP_API_KEY}
        time:
          enabled: true
        weather:
          enabled: true
          api-key: ${WEATHER_API_KEY}
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}

重要提示:请将上述环境变量设置为你从相应服务提供商获取的有效 API Key。你也可以直接将其写在配置文件中,但这不推荐用于生产环境。

4. 编写 Java 代码

4.1 主应用程序类

package com.alibaba.cloud.ai.toolcall;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ToolCallingApplication {
    public static void main(String[] args) {
        SpringApplication.run(ToolCallingApplication.class, args);
    }
}

4.2 TimeController - Methods as Tools

package com.alibaba.cloud.ai.toolcall.controller;

import com.alibaba.cloud.ai.toolcall.component.TimeTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/time")
public class TimeController {
    private final ChatClient dashScopeChatClient;
    private final TimeTools timeTools;

    public TimeController(ChatClient chatClient, TimeTools timeTools) {
        this.dashScopeChatClient = chatClient;
        this.timeTools = timeTools;
    }

    /**
     * 不使用工具的普通聊天
     */
    @GetMapping("/chat")
    public String simpleChat(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {
        return dashScopeChatClient.prompt(query).call().content();
    }

    /**
     * 使用方法作为工具
     */
    @GetMapping("/chat-tool-method")
    public String chatWithTimeFunction(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {
        return dashScopeChatClient.prompt(query).tools(timeTools).call().content();
    }
}

4.3 TimeTools 组件

package com.alibaba.cloud.ai.toolcall.component;

import com.alibaba.cloud.ai.toolcalling.time.GetTimeByZoneIdService;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;

public class TimeTools {
    private final GetTimeByZoneIdService timeService;

    public TimeTools(GetTimeByZoneIdService timeService) {
        this.timeService = timeService;
    }

    @Tool(description = "Get the time of a specified city.")
    public String getCityTime(@ToolParam(description = "Time zone id, such as Asia/Shanghai") String timeZoneId) {
        return timeService.apply(new GetTimeByZoneIdService.Request(timeZoneId)).description();
    }
}

4.4 AddressController - Methods as Tools with MethodToolCallback

package com.alibaba.cloud.ai.toolcall.controller;

import com.alibaba.cloud.ai.toolcall.component.AddressInformationTools;
import com.alibaba.cloud.ai.toolcalling.baidumap.BaiduMapSearchInfoService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.ai.util.json.schema.JsonSchemaGenerator;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;

@RestController
@RequestMapping("/address")
public class AddressController {
    private final ChatClient dashScopeChatClient;
    private final AddressInformationTools addressTools;

    public AddressController(ChatClient chatClient, AddressInformationTools addressTools) {
        this.dashScopeChatClient = chatClient;
        this.addressTools = addressTools;
    }

    /**
     * 不使用工具的普通聊天
     */
    @GetMapping("/chat")
    public String chat(@RequestParam(value = "address", defaultValue = "北京") String address) throws JsonProcessingException {
        BaiduMapSearchInfoService.Request query = new BaiduMapSearchInfoService.Request(address);
        return dashScopeChatClient.prompt(new ObjectMapper().writeValueAsString(query))
                .call()
                .content();
    }

    /**
     * 使用 MethodToolCallback 将方法作为工具
     */
    @GetMapping("/chat-method-tool-callback")
    public String chatWithBaiduMap(@RequestParam(value = "address", defaultValue = "北京") String address) throws JsonProcessingException {
        Method method = ReflectionUtils.findMethod(AddressInformationTools.class, "getAddressInformation", String.class);
        
        if (method == null) {
            throw new RuntimeException("Method not found");
        }

        return dashScopeChatClient.prompt(address)
                .toolCallbacks(MethodToolCallback.builder()
                        .toolDefinition(ToolDefinition.builder()
                                .description("Search for places using Baidu Maps API "
                                        + "or Get detail information of a address and facility query with baidu map or "
                                        + "Get address information of a place with baidu map or "
                                        + "Get detailed information about a specific place with baidu map")
                                .name("getAddressInformation")
                                .inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
                                .build())
                        .toolMethod(method)
                        .toolObject(addressTools)
                        .build())
                .call()
                .content();
    }
}

4.5 AddressInformationTools 组件

package com.alibaba.cloud.ai.toolcall.component;

import com.alibaba.cloud.ai.toolcalling.baidumap.BaiduMapSearchInfoService;

public class AddressInformationTools {
    private final BaiduMapSearchInfoService service;

    public AddressInformationTools(BaiduMapSearchInfoService service) {
        this.service = service;
    }

    public String getAddressInformation(String address) {
        return service.apply(new BaiduMapSearchInfoService.Request(address)).message();
    }
}

4.6 BaiduTranslateController - Function as Tools with Function Name

package com.alibaba.cloud.ai.toolcall.controller;

import com.alibaba.cloud.ai.toolcalling.baidutranslate.BaiduTranslateService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/translate")
public class BaiduTranslateController {
    private final ChatClient dashScopeChatClient;

    public BaiduTranslateController(ChatClient chatClient, BaiduTranslateService baiduTranslateService) {
        this.dashScopeChatClient = chatClient;
    }

    /**
     * 不使用工具的普通聊天
     */
    @GetMapping("/chat")
    public String simpleChat(@RequestParam(value = "query", defaultValue = "帮我把以下内容翻译成英文:你好,世界。") String query) {
        return dashScopeChatClient.prompt(query).call().content();
    }

    /**
     * 使用函数名将函数作为工具
     */
    @GetMapping("/chat-tool-function-callback")
    public String chatTranslateFunction(@RequestParam(value = "query", defaultValue = "帮我把以下内容翻译成英文:你好,世界。") String query) {
        return dashScopeChatClient.prompt(query)
                .toolNames("baiduTranslate")
                .call()
                .content();
    }
}

4.7 WeatherController - Function as Tools with FunctionCallBack

package com.alibaba.cloud.ai.toolcall.controller;

import com.alibaba.cloud.ai.toolcalling.weather.WeatherService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/weather")
public class WeatherController {
    private final ChatClient dashScopeChatClient;
    private final WeatherService weatherService;

    public WeatherController(ChatClient chatClient, WeatherService weatherService) {
        this.dashScopeChatClient = chatClient;
        this.weatherService = weatherService;
    }

    /**
     * 不使用工具的普通聊天
     */
    @GetMapping("/chat")
    public String simpleChat(@RequestParam(value = "query", defaultValue = "请告诉我北京1天以后的天气") String query) {
        return dashScopeChatClient.prompt(query).call().content();
    }

    /**
     * 使用 FunctionCallBack 将函数作为工具
     */
    @GetMapping("/chat-tool-function-name")
    public String chatWithWeatherFunction(@RequestParam(value = "query", defaultValue = "请告诉我北京1天以后的天气") String query) {
        return dashScopeChatClient.prompt(query).toolCallbacks(
                FunctionToolCallback.builder("getWeather", weatherService)
                        .description("Use api.weather to get weather information.")
                        .inputType(WeatherService.Request.class)
                        .build()
        ).call().content();
    }
}

5. 运行与测试

  1. 启动应用:运行你的 Spring Boot 主程序。
  2. 使用浏览器或 API 工具(如 Postman, curl)进行测试

测试 1:时间工具 - 不使用工具

访问以下 URL,询问当前时间,但不使用工具。

GET http://127.0.0.1:8080/time/chat

预期响应

AI 会基于其内置知识回答,可能无法提供准确的当前时间。

测试 2:时间工具 - 使用方法作为工具

访问以下 URL,询问当前时间,并使用方法作为工具。

GET http://127.0.0.1:8080/time/chat-tool-method

预期响应

AI 会调用 TimeTools 中的 getCityTime 方法获取准确的当前时间并返回。

测试 3:地址工具 - 不使用工具

访问以下 URL,查询北京的信息,但不使用工具。

GET http://127.0.0.1:8080/address/chat?address=北京

预期响应

AI 会基于其内置知识回答,可能无法提供详细的地址信息。

测试 4:地址工具 - 使用 MethodToolCallback

访问以下 URL,查询北京的信息,并使用 MethodToolCallback。

GET http://127.0.0.1:8080/address/chat-method-tool-callback?address=北京

预期响应

AI 会调用 AddressInformationTools 中的 getAddressInformation 方法,通过百度地图 API 获取北京的详细信息并返回。

测试 5:翻译工具 - 不使用工具

访问以下 URL,请求翻译,但不使用工具。

GET http://127.0.0.1:8080/translate/chat?query=帮我把以下内容翻译成英文:你好,世界。

预期响应

AI 会基于其语言能力尝试翻译,但可能不够准确。

测试 6:翻译工具 - 使用函数名

访问以下 URL,请求翻译,并使用函数名作为工具。

GET http://127.0.0.1:8080/translate/chat-tool-function-callback?query=帮我把以下内容翻译成英文:你好,世界。

预期响应

AI 会调用百度翻译 API 进行准确的翻译,并返回结果。

测试 7:天气工具 - 不使用工具

访问以下 URL,查询天气,但不使用工具。

GET http://127.0.0.1:8080/weather/chat?query=请告诉我北京1天以后的天气

预期响应

AI 会基于其内置知识回答,可能无法提供准确的天气预报。

测试 8:天气工具 - 使用 FunctionCallBack

访问以下 URL,查询天气,并使用 FunctionCallBack。

GET http://127.0.0.1:8080/weather/chat-tool-function-name?query=请告诉我北京1天以后的天气

预期响应

AI 会调用天气 API 获取准确的天气预报信息并返回。

6. 实现思路与扩展建议

实现思路

本案例的核心思想是"工具集成"。通过将外部服务(如时间服务、地图服务、翻译服务、天气服务)作为工具集成到 AI 应用中,扩展 AI 的能力边界。这使得:

  • 功能增强:AI 可以访问实时数据和服务,提供更准确、更及时的回答。
  • 灵活性高:可以根据需要选择不同的工具集成方式,适应不同的使用场景。
  • 可扩展性强:可以轻松添加新的工具,不断扩展 AI 的能力。

四种 Tool Calling 方式对比

方式 适用场景 优点 缺点
Methods as Tools 简单方法调用 实现简单,代码直观 灵活性较低
MethodToolCallback 需要自定义工具定义 可以自定义工具描述和输入模式 实现稍复杂
Function as Tools - Function Name 已注册的函数 简单易用,只需函数名 需要预先注册函数
FunctionCallBack 需要自定义函数回调 灵活性高,可自定义描述和输入类型 实现最复杂

扩展建议

  • 工具注册中心:可以设计一个工具注册中心,统一管理和发现可用的工具。
  • 工具权限控制:为不同用户或角色分配不同的工具访问权限,增强安全性。
  • 工具调用链:支持多个工具的链式调用,实现更复杂的业务逻辑。
  • 工具调用监控:添加工具调用的监控和日志记录,便于问题排查和性能优化。
  • 工具版本管理:支持工具的版本管理,便于工具的升级和回滚。
  • 自定义工具开发:根据业务需求开发自定义工具,扩展 AI 的专业领域能力。

7. 相关资源

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐