Spring AI 1.x 系列【17】函数型工具开发与使用

文章目录

  • [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 函数(如 FunctionBiFunctionSupplierConsumer 等)包装成大模型能识别、调用的工具。

核心源码如下:

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 函数式接口

首先,我们需要实现函数式接口,定义工具的业务处理逻辑。

FunctionBiFunctionSupplierConsumer 这四个接口是 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.BuilderdefaultToolCallbacks() 方法添加默认工具,所有由该 Builder 创建的 ChatClient 实例共享这些工具。

简单示例:

java 复制代码
ChatModel chatModel = ...
ToolCallback toolCallback = ...
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultToolCallbacks(toolCallback) // 添加默认工具
    .build();

2.4.2 运行时配置

FunctionToolCallback 实例传入 ChatClienttoolCallbacks() 方法,该工具仅对当前聊天请求生效。

简单示例:

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,核心依赖「动态解析机制」:

  1. 应用启动后,SpringBeanToolCallbackResolver 会扫描 Spring 容器中所有类型为 Function/Supplier/Consumer/BiFunction@Bean
  2. 自动为每个符合规则的 Bean 生成 FunctionToolCallback 实例(封装工具名称、描述、输入类型等)。
  3. 将生成的 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.BuilderdefaultToolNames() 方法添加默认工具,所有由该 Builder 创建的 ChatClient 实例共享这些工具。

简单示例:

java 复制代码
ChatModel chatModel = ...
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultToolNames("currentWeather") // 添加默认工具名称
    .build();

3.1.2 运行时配置

将工具名称(即函数 Bean 名称)传入 ChatClienttoolNames() 方法,该工具仅对当前聊天请求生效。

简单示例:

java 复制代码
ChatClient.create(chatModel)
    .prompt("哥本哈根的天气怎么样?")
    .toolNames("currentWeather") // 指定工具名称
    .call()
    .content();

4. @ToolParam

工具输入参数的 JSON Schme会自动生成,可通过 @ToolParam 注解补充参数说明(描述、是否必填),默认所有参数均为必填。

简单示例:

java 复制代码
// 给请求参数添加描述:location是城市/国家名称
record WeatherRequest(@ToolParam(description = "城市或国家名称") String location, Unit unit) {}
相关推荐
江畔柳前堤3 小时前
roLabelImg 详细安装教程
开发语言·人工智能·后端·云原生
阿里云大数据AI技术3 小时前
分链路差异化设计的DSP准实时数仓|钛动科技基于阿里云实时计算 Flink 版 + DLF Paimon + EMR Serverless StarRocks 的实践
人工智能·flink
陕西企来客3 小时前
2026年7月AI智能搜索曝光趋势研判
大数据·人工智能·机器学习·ai智能搜索曝光
白鸽(二般)4 小时前
MinIO Java Client API
java·开发语言
阿里云大数据AI技术4 小时前
从算力到智能体,面向 Agentic AI 的基础设施演进
人工智能·agent
hangyuekejiGEO4 小时前
GEO技术服务选型指南
大数据·人工智能·python
阿里云大数据AI技术4 小时前
EMR Serverless Spark AI Function 的双维降本实践
人工智能·sql·spark
软萌萌的14 小时前
Java Spring Boot 修改yml配置&加载顺序规则
java·spring boot·python
维基框架5 小时前
GitHub源码处理提速 一趟扫描反而更慢
人工智能·github
冬奇Lab5 小时前
代码库知识库系列(05):向量检索 vs 知识图谱——加了调用图并没有变更好
人工智能