Spring AI 工具调用:函数工具

Spring AI 简化了工具调用的函数定义,支持两种配置方式:一是通过 FunctionToolCallback 编程式配置,精细定制工具逻辑;二是用 @Bean 注解将工具类 / 方法注册为 Bean,框架运行时自动解析并动态注册。这既满足复杂定制需求,又简化常规集成,方便开发者灵活选择。

编程式:FunctionToolCallback

FunctionToolCallback 是处理底层 AI 模型调用工具函数时执行具体逻辑的核心回调接口,负责将 AI 的工具调用请求转换为实际业务操作,并将执行结果返回给 AI 模型。

FunctionToolCallback 的核心作用:

  • 桥接 AI 与业务逻辑:接收 AI 生成的工具调用参数(如函数名、参数列表),触发对应的业务方法执行。

  • 处理参数与结果:负责解析 AI 传来的参数格式,适配业务方法的输入要求;同时将方法执行结果转换为 AI 可理解的格式。

  • 支撑工具调用生命周期:在工具调用的整个过程中(参数解析、方法执行、结果处理)提供标准化的执行逻辑。

你可以通过编程方式构建 FunctionToolCallback 实例,将函数式类型(Function、Supplier、Consumer 或 BiFunction)转换为工具。

📢注意:Function、Supplier、Consumer 或 BiFunction 是在 Java 8 引入的函数式接口,作用如下:

  • Supplier<T>  无参数,返回一个 T 类型的结果(“供给者”,只提供数据)

  • Consumer<T>  接收一个 T 类型的参数,无返回值(“消费者”,只消费数据)

  • Function<T, R>  接收一个 T 类型的参数,返回一个 R 类型的结果(“函数”,输入转输出)

  • BiFunction<T, U, R>  接收两个参数(T 类型和 U 类型),返回一个 R 类型的结果(“双参数函数”)

以下是后续要用到的函数类,实现了 Function 接口:

// 天气服务类,实现 Function 接口,用于处理天气查询请求并返回结果
// Function<WeatherRequest, WeatherResponse> 表示接收 WeatherRequest 类型参数,
// 返回 WeatherResponse 类型结果
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
    
    // 实现Function接口的apply方法,处理天气查询业务逻辑
    // 参数request:包含查询地点和温度单位的请求对象
    // 返回值:包含温度和单位的天气响应对象
    public WeatherResponse apply(WeatherRequest request) {
        // 此处简化实现,实际应用中应包含真实的天气查询逻辑
        // 示例返回固定温度30.0,单位使用请求中指定的单位
        return new WeatherResponse(30.0, request.unit());
    }
}

// 温度单位枚举:摄氏度(C)和华氏度(F)
public enum Unit { C, F }

// 天气查询请求记录(Record),包含查询地点和温度单位
// location:查询的地点名称
// unit:期望返回的温度单位(C或F)
public record WeatherRequest(String location, Unit unit) {}

// 天气查询响应记录(Record),包含温度值和对应的单位
// temp:温度数值
// unit:温度单位(与请求中一致或转换后的值)
public record WeatherResponse(double temp, Unit unit) {}

在正式通过编程方式构建 FunctionToolCallback 之前,先了解几个便捷的 Builder 类:

FunctionToolCallback.Builder

FunctionToolCallback.Builder 是用于构建 FunctionToolCallback 实例的建造类,提供了一种链式编程的方式来灵活配置工具回调的核心参数。它封装了 FunctionToolCallback 所需的关键信息(如目标业务对象、方法、元数据等),让开发者无需直接通过复杂的构造器参数传递,就能快速创建可被 AI 调用的工具回调实例。

FunctionToolCallback.Builder 定义如下:

Spring AI 工具调用:函数工具

字段说明:

  • name:工具名称。AI 模型通过此名称识别调用工具,因此同一上下文中不允许存在同名工具。对于特定聊天请求,模型可用的所有工具名称必须保持全局唯一。(必需项)

  • toolFunction:表示工具方法的函数式对象(Function、Supplier、Consumer 或 BiFunction)。(必需项)

  • description:工具描述,用于帮助模型判断何时及如何调用该工具。若未提供,将使用方法名称作为工具描述。但强烈建议提供详细描述,这对模型理解工具用途及使用方法至关重要。若描述不充分,可能导致模型在该调用工具时未调用,或错误调用工具。

  • inputType:函数输入类型。(必需项)

  • inputSchema:工具输入参数的 JSON Schema。若未提供,将基于 inputType 自动生成 Schema。你可使用 @ToolParam 注解提供输入参数的额外信息(如描述、是否必需等),默认情况下所有输入参数均为必需参数。

  • toolMetadata:定义额外设置的 ToolMetadata 实例(如是否将结果直接返回客户端、使用的结果转换器等),可通过 ToolMetadata.Builder 类构建。

  • toolCallResultConverter:用于将工具调用结果转换为 String 对象并返回 AI 模型的 ToolCallResultConverter 实例(未配置时默认使用 DefaultToolCallResultConverter)。

ToolMetadata.Builder

ToolMetadata.Builder 是用于构建 ToolMetadata 实例的建造者类,主要用于定义工具的元数据信息。ToolMetadata 包含了工具的核心描述信息,帮助 AI 模型理解工具的用途、参数要求和调用方式,是工具调用(Tool Calling)机制中连接 AI 模型与实际工具逻辑的重要桥梁。

ToolMetadata.Builder 定义如下:

Spring AI 工具调用:函数工具

字段说明:

  • returnDirect:控制是否将工具结果直接返回客户端(true)还是传回模型(false)。

下面是一个通过编程方式定义 FunctionToolCallback 的完整例子:

ToolCallback toolCallback = FunctionToolCallback
    .builder("currentWeather", new WeatherService()) // 前面的 WeatherService 服务
    .description("获取指定地点的天气情况")
    .inputType(WeatherRequest.class)
    .build();

注意,函数输入和输出可以是 Void 或 POJO。输入和输出的 POJO 必须是可序列化的,因为结果将被序列化并发送回模型。函数及输入输出类型必须是 public 的。

为 ChatClient 添加工具

可以将 FunctionToolCallback 实例传递给 ChatClient 的 toolCallbacks() 方法,该工具仅在添加它的特定聊天请求中可用。例如:

@RestController
class MyController {

    @Autowired
    private ChatModel chatModel;

    // 模型使用 gpt-4-turbo
    // GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
    // http://localhost:8080/ai
    @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
    public String ai() {
        ToolCallback toolCallback = FunctionToolCallback
                .builder("currentWeather", new WeatherService())
                .description("获取指定地点的天气情况")
                .inputType(WeatherRequest.class)
                .build();

        return ChatClient.create(chatModel)
                .prompt("哥本哈根的天气怎么样?")
                .toolCallbacks(toolCallback) // 将 ToolCallback 设置给 ChatClient
                .call()
                .content();
    }

}

为 ChatClient 添加默认工具

可以通过将 FunctionToolCallback 实例传递给 ChatClient.Builder 的 defaultToolCallbacks() 方法来添加默认工具。若同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。

默认工具在所有基于同一 ChatClient.Builder 构建的 ChatClient 实例执行的聊天请求间共享。此类工具适用于跨聊天请求的通用功能,但若使用不当,可能导致工具在不该出现的场景中被调用。例如:

@RestController
class MyController {

    @Autowired
    private ChatModel chatModel;

    // 模型使用 gpt-4-turbo
    // GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
    // http://localhost:8080/ai
    @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
    public String ai() {
        ToolCallback toolCallback = FunctionToolCallback
                .builder("currentWeather", new WeatherService())
                .description("获取指定地点的天气情况")
                .inputType(WeatherRequest.class)
                .build();

        ChatClient chatClient = ChatClient.builder(chatModel)
                .defaultToolCallbacks(toolCallback)
                .build();

        return chatClient.prompt("哥本哈根的天气怎么样?").call().content();
    }

}

为 ChatModel 添加工具

可以将 FunctionToolCallback 实例传递给 ToolCallingChatOptions 的 toolCallbacks() 方法。该工具仅在添加它的特定聊天请求中可用。例如:

@RestController
class MyController {

    @Autowired
    private ChatModel chatModel;

    // 模型使用 gpt-4-turbo
    // GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
    // http://localhost:8080/ai
    @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
    public String ai() {
        ToolCallback toolCallback = FunctionToolCallback
                .builder("currentWeather", new WeatherService())
                .description("获取指定地点的天气情况")
                .inputType(WeatherRequest.class)
                .build();

        ChatOptions chatOptions = ToolCallingChatOptions.builder()
                .toolCallbacks(toolCallback)
                .build();
        Prompt prompt = new Prompt("哥本哈根的天气怎么样?", chatOptions);
        return chatModel.call(prompt).getResult().getOutput().getText();
    }

}

为 ChatModel 添加默认工具

可以在构建 ChatModel 时,通过用于创建 ChatModel 的 OpenAiChatOptions 实例的 toolCallbacks() 方法传入 FunctionToolCallback 实例来添加默认工具。若同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。

默认工具在该 ChatModel 实例处理的所有聊天请求间共享。此类工具适用于跨聊天请求的通用功能,但若使用不当,可能导致工具在不该出现的场景中被调用。

简单示例:

@RestController
class MyController {

    @Value("${spring.ai.openai.base-url}")
    private String baseUrl;

    @Value("${spring.ai.openai.api-key}")
    private String apiKey;

    // 模型使用 gpt-4-turbo
    // GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
    // http://localhost:8080/ai
    @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
    public String ai() {
        ToolCallback toolCallback = FunctionToolCallback
                .builder("currentWeather", new WeatherService())
                .description("获取指定地点的天气情况")
                .inputType(WeatherRequest.class)
                .build();

        ChatModel chatModel = OpenAiChatModel.builder()
                .openAiApi(OpenAiApi.builder().baseUrl(baseUrl).apiKey(apiKey).build())
                .defaultOptions(OpenAiChatOptions.builder()
                        .model(OpenAiApi.ChatModel.GPT_4_TURBO) // 模型名
                        .toolCallbacks(toolCallback)
                        .build()
                ).build();

        return chatModel.call("哥本哈根的天气怎么样?");
    }

}

  

动态规范:@Bean

动态规范允许我们无需编程式配置工具,你可将工具定义为 Spring Bean,由 Spring AI 通过 ToolCallbackResolver 接口(具体实现为 SpringBeanToolCallbackResolver)在运行时动态解析。

此方案支持将任意 Function、Supplier、Consumer 或 BiFunction 类型的 Bean 作为工具使用。Bean 名称将作为工具名称,可通过 Spring Framework 的 @Description 注解提供工具描述(用于指导模型判断调用时机及方式)。如果未提供描述,则使用方法名称作为工具描述。但强烈建议提供详细描述,这对模型理解工具用途及使用方法至关重要。如果描述不充分,可能导致模型在该调用工具时未调用,或错误调用工具。

例如:

@Configuration(proxyBeanMethods = false)
class WeatherTools {

    WeatherService weatherService = new WeatherService();

    @Bean
    @Description("获取指定地点的天气情况") // 巩固描述
    Function<WeatherRequest, WeatherResponse> currentWeather() {
        return weatherService;
    }

}

工具输入参数的 JSON Schema 将自动生成。你可使用 @ToolParam 注解提供输入参数的额外信息(如描述、是否必需等),默认情况下所有输入参数均为必需参数。如下:

// 通过 @ToolParam 注解为 location 参数指定详细描述信息
record WeatherRequest(@ToolParam(description = "一个城市或一个国家的名称") String location, Unit unit) {
    //...
}

注意,该工具规范方案存在无法保证类型安全的缺点(因工具解析是在运行时完成,根据工具名称进行解析)。要解决此问题,你可通过 @Bean 注解显式指定工具名称,并将名称存储在常量中,以便在聊天请求中使用该常量而非硬编码工具名称。例如:

@Configuration(proxyBeanMethods = false)
class WeatherTools {
    // 工具名称常量
    public static final String CURRENT_WEATHER_TOOL = "currentWeather";

    @Bean(CURRENT_WEATHER_TOOL) // 为工具指定名称
    @Description("获取指定地点的天气情况")
    Function<WeatherRequest, WeatherResponse> currentWeather() {
        //...
    }

}

为 ChatClient 添加工具

可以将工具名称(即函数 bean 名称)传递给 ChatClient 的 toolNames() 方法。该工具仅在添加它的特定聊天请求中可用。

(1)使用 @Configuration 注解创建配置类,配置一个名为“currentWeather”的 WeatherService 工具,如下:

@Configuration(proxyBeanMethods = false)
public class Config {

    @Bean("currentWeather")
    @Description("获取指定地点的天气情况")
    Function<WeatherRequest, WeatherResponse> weatherService() {
        System.out.println("->> weatherService()");
        return new WeatherService();
    }

}

(2)创建一个名为 MyController 的控制器,如下:

@RestController
class MyController {

    @Autowired
    private ChatModel chatModel;

    // 模型使用 gpt-4-turbo
    // GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
    // http://localhost:8080/ai
    @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
    public String ai() {
        return ChatClient.create(chatModel)
                .prompt("哥本哈根的天气怎么样?")
                // 工具名称,使用 SpringBeanToolCallbackResolver 将名称解析为 ToolCallback
                .toolNames("currentWeather")
                .call()
                .content();
    }

}

为 ChatClient 添加默认工具

可以通过将工具名称传递给 ChatClient.Builder 的 defaultToolNames() 方法来添加默认工具。若同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。

默认工具在所有基于同一 ChatClient.Builder 构建的 ChatClient 实例执行的聊天请求间共享。此类工具适用于跨聊天请求的通用功能,但若使用不当,可能导致工具在不该出现的场景中被调用。

(1)使用 @Configuration 注解创建配置类,配置一个名为“currentWeather”的 WeatherService 工具,如下:

@Configuration(proxyBeanMethods = false)
public class Config {

    @Bean("currentWeather")
    @Description("获取指定地点的天气情况")
    Function<WeatherRequest, WeatherResponse> weatherService() {
        System.out.println("->> weatherService()");
        return new WeatherService();
    }

}

(2)创建一个名为 MyController 的控制器,如下:

@RestController
class MyController {

    @Autowired
    private ChatModel chatModel;

    // 模型使用 gpt-4-turbo
    // GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
    // http://localhost:8080/ai
    @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
    public String ai() {
        ChatClient chatClient = ChatClient.builder(chatModel)
                .defaultToolNames("currentWeather")
                .build();

        return chatClient.prompt("哥本哈根的天气怎么样?")
                .call()
                .content();
    }

}

为 ChatModel 添加工具

可以通过调用 ChatModel 时使用的 ToolCallingChatOptions 中的 toolNames() 方法传入工具名称。该工具仅在添加它的特定聊天请求中可用。例如:

(1)使用 @Configuration 注解创建配置类,配置一个名为“currentWeather”的 WeatherService 工具,如下:

@Configuration(proxyBeanMethods = false)
public class Config {

    @Bean("currentWeather")
    @Description("获取指定地点的天气情况")
    Function<WeatherRequest, WeatherResponse> weatherService() {
        System.out.println("->> weatherService()");
        return new WeatherService();
    }

}

(2)创建一个名为 MyController 的控制器,如下:

@RestController
class MyController {

    @Autowired
    private ChatModel chatModel;

    // 模型使用 gpt-4-turbo
    // GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
    // http://localhost:8080/ai
    @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
    public String ai() {
        ChatOptions chatOptions = ToolCallingChatOptions.builder()
                .toolNames("currentWeather")
                .build();
        Prompt prompt = new Prompt("哥本哈根的天气怎么样?", chatOptions);
        return chatModel.call(prompt).getResult().getOutput().getText();
    }

}

为 ChatModel 添加默认工具

在构建 ChatModel 时,通过用于创建 ChatModel 的 OpenAiChatOptions 实例的 toolNames() 方法传入工具名称来添加默认工具。若同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。

默认工具在该 ChatModel 实例处理的所有聊天请求间共享。此类工具适用于跨聊天请求的通用功能,但若使用不当,可能导致工具在不该出现的场景中被调用。

简单示例:

@RestController
class MyController {

    @Value("${spring.ai.openai.base-url}")
    private String baseUrl;

    @Value("${spring.ai.openai.api-key}")
    private String apiKey;

    @Autowired
    private ToolCallbackResolver toolCallbackResolver;

    // 模型使用 gpt-4-turbo
    // GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
    // http://localhost:8080/ai
    @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
    public String ai() {
        ChatModel chatModel = OpenAiChatModel.builder()
                // 需要自己配置工具名称解析器,否则 currentWeather 不会被解析
                .toolCallingManager(ToolCallingManager.builder().toolCallbackResolver(toolCallbackResolver).build())
                .openAiApi(OpenAiApi.builder().baseUrl(baseUrl).apiKey(apiKey).build())
                .defaultOptions(OpenAiChatOptions.builder()
                        .model(OpenAiApi.ChatModel.GPT_4_TURBO) // 模型名
                        .toolNames("currentWeather")
                        .build()
                )
                // 也可以在这里添加解析器
                //.toolCallingManager(ToolCallingManager.builder().toolCallbackResolver(toolCallbackResolver).build())
                .build();

        return chatModel.call("哥本哈根的天气怎么样?");
    }

}

函数式工具的限制

以下类型目前不支持作为工具函数的输入或输出类型:

  • 基本类型

  • Optional

  • 集合类型 (如 List、Map、Array、Set)

  • 异步类型(如 CompletableFuture、Future)

  • 响应式类型(如 Flow、Mono、Flux)

基本类型和集合在使用基于方法的工具规范方案时受支持。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号