Spring AI 简化了工具调用的函数定义,支持两种配置方式:一是通过 FunctionToolCallback 编程式配置,精细定制工具逻辑;二是用 @Bean 注解将工具类 / 方法注册为 Bean,框架运行时自动解析并动态注册。这既满足复杂定制需求,又简化常规集成,方便开发者灵活选择。
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 实例的建造类,提供了一种链式编程的方式来灵活配置工具回调的核心参数。它封装了 FunctionToolCallback 所需的关键信息(如目标业务对象、方法、元数据等),让开发者无需直接通过复杂的构造器参数传递,就能快速创建可被 AI 调用的工具回调实例。
FunctionToolCallback.Builder 定义如下:
字段说明:
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 实例的建造者类,主要用于定义工具的元数据信息。ToolMetadata 包含了工具的核心描述信息,帮助 AI 模型理解工具的用途、参数要求和调用方式,是工具调用(Tool Calling)机制中连接 AI 模型与实际工具逻辑的重要桥梁。
ToolMetadata.Builder 定义如下:
字段说明:
returnDirect:控制是否将工具结果直接返回客户端(true)还是传回模型(false)。
下面是一个通过编程方式定义 FunctionToolCallback 的完整例子:
ToolCallback toolCallback = FunctionToolCallback .builder("currentWeather", new WeatherService()) // 前面的 WeatherService 服务 .description("获取指定地点的天气情况") .inputType(WeatherRequest.class) .build();
注意,函数输入和输出可以是 Void 或 POJO。输入和输出的 POJO 必须是可序列化的,因为结果将被序列化并发送回模型。函数及输入输出类型必须是 public 的。
可以将 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(); } }
可以通过将 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(); } }
可以将 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 的 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("哥本哈根的天气怎么样?"); } }
动态规范允许我们无需编程式配置工具,你可将工具定义为 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() { //... } }
可以将工具名称(即函数 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.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 时使用的 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 的 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)
基本类型和集合在使用基于方法的工具规范方案时受支持。