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) {}
相关推荐
YoanAILab1 小时前
Dify 是怎么工作的?一篇讲清 AI 应用平台架构(工程视角)
人工智能·dify·rag·技术成长·ai平台·ai工程
陪你步步前行1 小时前
关于dice, miou, loss计算的细节
人工智能·深度学习·机器学习
老刘说AI1 小时前
WorkFlow Agent案例:auto_document_agent(文件自动处理)
开发语言·数据库·人工智能·python·神经网络·自然语言处理
AI成长日志2 小时前
【强化学习专栏】深度强化学习技术演进:DQN、PPO、SAC的架构设计与训练优化
人工智能·算法·架构
云烟成雨TD2 小时前
Spring AI 1.x 系列【15】AI Agent 基石:Tool Calling 标准与 Spring AI 集成
java·人工智能·spring
科学创新前沿2 小时前
逆向设计新范式:深度学习驱动的声学超材料智能优化!
人工智能·python·深度学习·声学·逆向设计·声学超材料
咸鱼2.02 小时前
【java入门到放弃】杂记
java·开发语言
铮铭2 小时前
上海交大 RoboClaw VS EmbodiedAgentsSys 两个框架对比分析
人工智能·机器人·ai编程·具身智能·vla
rgb2gray2 小时前
论文详解:基于POI数据的城市功能区动态演化分析——以北京为例
人工智能·算法·机器学习·回归·gwr