文章目录
- [1. 概述](#1. 概述)
- [2. 编程式](#2. 编程式)
-
- [2.1 FunctionToolCallback](#2.1 FunctionToolCallback)
- [2.2 函数式接口](#2.2 函数式接口)
- [2.3 构建函数工具](#2.3 构建函数工具)
- [2.4 ChatClient 添加工具](#2.4 ChatClient 添加工具)
-
- [2.4.1 默认配置](#2.4.1 默认配置)
- [2.4.2 运行时配置](#2.4.2 运行时配置)
- [3. 声明式](#3. 声明式)
-
- [3.1 @Bean](#3.1 @Bean)
- [3.2 动态解析](#3.2 动态解析)
- [3.3 ChatClient 添加工具](#3.3 ChatClient 添加工具)
-
- [3.3.1 默认配置](#3.3.1 默认配置)
- [3.1.2 运行时配置](#3.1.2 运行时配置)
- [4. @ToolParam](#4. @ToolParam)
1. 概述
除了方法型工具外,Spring AI 也支持将「函数」定义为可被 AI 调用的工具,有两种实现方式:
- 通过底层的
FunctionToolCallback实现类编程式定义。 - 通过
@Bean注解将函数声明为的Spring组件,在运行时通过解析器动态解析。
2. 编程式
2.1 FunctionToolCallback
FunctionToolCallback<I, O> 是用于封装函数式工具调用逻辑的核心回调类,继承 ToolCallback(工具回调接口),遵循 Spring AI 工具调用的生命周期规范。它可以将普通的 Java 函数(如 Function、BiFunction 、Supplier、Consumer 等)包装成大模型能识别、调用的工具。
核心源码如下:
java
public class FunctionToolCallback<I, O> implements ToolCallback {
private static final Logger logger = LoggerFactory.getLogger(FunctionToolCallback.class);
private static final ToolCallResultConverter DEFAULT_RESULT_CONVERTER = new DefaultToolCallResultConverter();
private static final ToolMetadata DEFAULT_TOOL_METADATA = ToolMetadata.builder().build();
private final ToolDefinition toolDefinition;
private final ToolMetadata toolMetadata;
private final Type toolInputType;
private final BiFunction<I, ToolContext, O> toolFunction;
private final ToolCallResultConverter toolCallResultConverter;
其中, <I, O> 分别代表工具输入类型和工具输出类型,支持强类型的输入 / 输出处理,避免手动类型转换的繁琐和错误。
2.2 函数式接口
首先,我们需要实现函数式接口,定义工具的业务处理逻辑。
Function、BiFunction 、Supplier、Consumer 这四个接口是 Java 8 引入的核心函数式接口,设计目标是支持函数式编程:
| 接口名称 | 接口类型 | 输入输出特征 | 通用适用场景 | Spring AI 工具场景 |
|---|---|---|---|---|
Supplier<T> |
供给型接口 | 无输入,有输出(返回 T 类型结果) | 无参数的查询、数据生成 | 适配无参数的工具调用(如"获取当前系统时间""生成随机验证码"工具) |
Consumer<T> |
消费型接口 | 有输入(接收 T 类型参数),无输出 | 执行操作但无需返回结果 | 适配仅执行操作、无返回值的工具调用(如"保存用户反馈""记录操作日志""发送短信通知"工具) |
Function<T, R> |
函数型接口 | 单输入(接收 T 类型参数),单输出(返回 R 类型结果) | 单参数的查询、转换 | 最常用的基础工具类型(如"根据城市名查询天气""根据用户ID查询用户等级"工具) |
BiFunction<T, U, R> |
双参数函数型接口 | 双输入(接收 T、U 类型参数),单输出(返回 R 类型结果) | 需要两个参数的处理逻辑 | 适配带上下文的工具调用,固定第二个参数为 ToolContext(工具上下文,含会话 ID、用户信息等),如"带会话信息的订单查询""关联用户与商品的查询"工具 |
示例 1 ,实现 Function 接口:
java
// 天气服务类(实现Function接口)
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
public WeatherResponse apply(WeatherRequest request) {
// 返回模拟的天气数据:温度30.0,单位摄氏度
return new WeatherResponse(30.0, Unit.C);
}
}
// 温度单位枚举
public enum Unit { C, F }
// 天气请求参数(记录类)
public record WeatherRequest(String location, Unit unit) {}
// 天气响应结果(记录类)
public record WeatherResponse(double temp, Unit unit) {}
示例 2 ,实现 Supplier 接口:
java
// 系统时间响应结果(记录类)
public record SystemTimeResponse(String currentTime, String timeZone, String date) {}
// 系统时间服务类(实现Supplier接口)
public class SystemTimeService implements Supplier<SystemTimeResponse> {
@Override
public SystemTimeResponse get() {
// 核心逻辑:获取系统当前时间,返回结构化结果
String currentTime = java.time.LocalTime.now().toString();
String timeZone = java.time.ZoneId.systemDefault().getId();
String date = java.time.LocalDate.now().toString();
return new SystemTimeResponse(currentTime, timeZone, date);
}
}
示例 3 ,实现 Consumer 接口:
java
// 反馈类型枚举
public enum FeedbackType { USER_EXPERIENCE, FUNCTION_SUGGEST, BUG_REPORT }
// 用户反馈请求参数(记录类)
public record UserFeedbackRequest(String userId, String content, FeedbackType type, int score) {}
// 用户反馈服务类(实现Consumer接口)
public class UserFeedbackService implements Consumer<UserFeedbackRequest> {
@Override
public void accept(UserFeedbackRequest request) {
// 核心逻辑:接收反馈参数,执行保存操作(模拟数据库存储)
System.out.printf("[反馈保存] 用户ID:%s | 类型:%s | 评分:%d | 内容:%s%n",
request.userId(), request.type(), request.score(), request.content());
// 实际场景可调用DAO层写入数据库
}
}
示例 4 ,实现 BiFunction 接口:
java
import org.springframework.ai.chat.model.ToolContext;
// 订单状态枚举
public enum OrderStatus { UNPAID, PAID, SHIPPED, COMPLETED, CANCELLED }
// 订单查询请求参数(记录类)
public record OrderQueryRequest(Long orderId, String userId) {}
// 订单查询响应结果(记录类)
public record OrderQueryResponse(
Long orderId,
String userId,
OrderStatus status,
Double amount,
String querySessionId // 从上下文获取的会话ID
) {}
// 订单查询服务类(实现BiFunction接口)
public class OrderQueryService implements BiFunction<OrderQueryRequest, ToolContext, OrderQueryResponse> {
@Override
public OrderQueryResponse apply(OrderQueryRequest request, ToolContext context) {
// 核心逻辑1:模拟查询订单基础信息
Double amount = request.orderId() % 2 == 0 ? 99.9 : 199.9;
OrderStatus status = request.orderId() % 3 == 0 ? OrderStatus.PAID : OrderStatus.UNPAID;
// 核心逻辑2:从ToolContext获取会话ID(上下文信息)
String sessionId = context.getSessionId();
// 返回包含上下文的订单结果
return new OrderQueryResponse(
request.orderId(),
request.userId(),
status,
amount,
sessionId
);
}
}
2.3 构建函数工具
FunctionToolCallback.Builder 用于构建 FunctionToolCallback 实例,配置工具的关键信息,具体如下:
| 配置项 | 说明 | 是否必填 |
|---|---|---|
| name | 工具名称 | 是 |
| toolFunction | 表示工具的函数式对象(Function/Supplier/Consumer/BiFunction) |
是 |
| description | 工具描述 | 否 |
| inputType | 函数输入参数的类型 | 是 |
| inputSchema | 工具输入参数的 JSON Schema | 否 |
| toolMetadata | 工具元数据(ToolMetadata)实例 | 否 |
| toolCallResultConverter | 工具调用结果转换器(ToolCallResultConverter)实例。 |
否 |
简单示例,定义一个函数式天气查询工具:
java
ToolCallback toolCallback = FunctionToolCallback
// 指定工具名称和函数实例
.builder("currentWeather", new WeatherService())
// 配置工具描述
.description("查询指定地区的天气信息")
// 指定输入参数类型
.inputType(WeatherRequest.class)
.build();
提示 :和方法型工具一样,也可以通过 ToolMetadata.Builder 控制工具结果是直接返回给客户端,还是回传给模型。
2.4 ChatClient 添加工具
提示:若同时配置,运行时工具会完全覆盖默认工具,默认工具适用于通用场景,但需谨慎使用(避免非预期调用)。
2.4.1 默认配置
通过 ChatClient.Builder 的 defaultToolCallbacks() 方法添加默认工具,所有由该 Builder 创建的 ChatClient 实例共享这些工具。
简单示例:
java
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(toolCallback) // 添加默认工具
.build();
2.4.2 运行时配置
将 FunctionToolCallback 实例传入 ChatClient 的 toolCallbacks() 方法,该工具仅对当前聊天请求生效。
简单示例:
java
ToolCallback toolCallback = ... // 构建好的函数工具回调
ChatClient.create(chatModel)
.prompt("哥本哈根的天气怎么样?")
.toolCallbacks(toolCallback) // 绑定工具到本次请求
.call()
.content();
3. 声明式
3.1 @Bean
Spring AI 支持通过 @Bean 注解声明式定义函数工具,无需手动编程创建 FunctionToolCallback 实例,框架会自动完成工具的解析、注册。内置了 ToolCallbackResolver 接口(默认实现为 SpringBeanToolCallbackResolver)在运行时动态解析。
这种方式支持将任意 Function/Supplier/Consumer/BiFunction 类型的 Bean 作为工具:
Bean名称会被用作工具名称。- 可通过
Spring框架的@Description注解配置工具描述。若未配置,默认使用方法名(强烈建议显式配置详细描述)。
基础示例:
java
@Configuration(proxyBeanMethods = false)
class WeatherTools {
// 实例化天气服务
WeatherService weatherService = new WeatherService();
// 声明为Bean,作为AI工具
@Bean
@Description("查询指定地区的天气信息")
Function<WeatherRequest, WeatherResponse> currentWeather() {
return weatherService;
}
}
3.2 动态解析
用 @Bean 声明函数工具时,无需手动创建 FunctionToolCallback,核心依赖「动态解析机制」:
- 应用启动后,
SpringBeanToolCallbackResolver会扫描Spring容器中所有类型为Function/Supplier/Consumer/BiFunction的@Bean。 - 自动为每个符合规则的
Bean生成FunctionToolCallback实例(封装工具名称、描述、输入类型等)。 - 将生成的
ToolCallback注册到ChatClient,大模型可直接通过Bean名称调用对应的工具。
这种动态解析方式的缺点是无法保证类型安全,动态解析是运行时行为,解析逻辑在应用启动后 / 工具调用时执行,而非编译时检查,导致两类典型的 "类型不安全" 问题:
- 工具名称硬编码风险:如果在代码中直接用字符串指定 / 调用工具名称,拼写错误时,编译期无任何报错,只有运行时模型调用工具时才会抛出异常。
- 类型匹配延迟报错 :函数输入 / 输出类型不符合规则,也只能在运行时解析
Bean时才会抛出ToolExecutionException,无法提前发现。
可通过以下方式规避:
- 显式指定
Bean名称并存储为常量,避免硬编码工具名称。
简单示例:
java
@Configuration(proxyBeanMethods = false)
class WeatherTools {
// 定义工具名称常量
public static final String CURRENT_WEATHER_TOOL = "currentWeather";
// 显式指定Bean名称
@Bean(CURRENT_WEATHER_TOOL)
@Description("查询指定地区的天气信息")
Function<WeatherRequest, WeatherResponse> currentWeather() {
// 业务逻辑
...
}
}
3.3 ChatClient 添加工具
提示:若同时配置,运行时工具会完全覆盖默认工具,默认工具适用于通用场景,但需谨慎使用(避免非预期调用)。
3.3.1 默认配置
通过 ChatClient.Builder 的 defaultToolNames() 方法添加默认工具,所有由该 Builder 创建的 ChatClient 实例共享这些工具。
简单示例:
java
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolNames("currentWeather") // 添加默认工具名称
.build();
3.1.2 运行时配置
将工具名称(即函数 Bean 名称)传入 ChatClient 的 toolNames() 方法,该工具仅对当前聊天请求生效。
简单示例:
java
ChatClient.create(chatModel)
.prompt("哥本哈根的天气怎么样?")
.toolNames("currentWeather") // 指定工具名称
.call()
.content();
4. @ToolParam
工具输入参数的 JSON Schme会自动生成,可通过 @ToolParam 注解补充参数说明(描述、是否必填),默认所有参数均为必填。
简单示例:
java
// 给请求参数添加描述:location是城市/国家名称
record WeatherRequest(@ToolParam(description = "城市或国家名称") String location, Unit unit) {}