Spring AI 工具调用基础概念

什么是工具调用

定义工具调用在 Spring AI 中的概念,即通过语言模型触发对外部工具(如搜索引擎、数据库查询、文件操作工具等)的使用,以获取额外信息或执行特定任务,从而增强语言模型的响应能力。
强调工具调用并非简单的函数调用,而是一种智能的、上下文感知的交互方式,语言模型能够根据任务需求动态选择和使用工具。

工具调用的优势

提升回答准确性:通过调用外部工具获取实时、准确的数据,避免语言模型生成错误或过时的信息。例如,在回答与金融数据、时事新闻相关问题时,调用最新数据来源工具。
增强功能多样性:允许应用借助各种专业工具完成复杂任务,如数据分析、图像生成、文本翻译等,丰富应用的功能场景。
提高用户体验:提供更全面、深入的回答,满足用户多样化的需求,增强用户对应用的信任和满意度。

工作流程

spring ai 工具调用流程

  1. 当使用某个工具时,需在聊天请求中纳入该工具的定义。每个工具定义涵盖工具名称、工具描述以及输入参数的架构。
  2. 当模型判定要调用某个工具时,它会返回一个响应,其中包含工具名称以及依照既定架构构建的输入参数。
  3. 应用程序负责依据工具名称来识别该工具,并利用提供的输入参数执行此工具。
  4. 应用程序对工具调用的结果进行处理。
  5. 应用程序将工具调用的结果反馈给模型。
  6. 模型把工具调用的结果作为额外上下文,进而生成最终的响应。

一、工具定义的两种核心方式

Spring AI 支持通过「声明式」和「编程式」将普通方法转化为 AI 可调用的工具,二者核心差异在于配置方式(注解 vs 代码构建),但最终目标都是生成 ToolCallback 实例供 AI 模型使用。

1. 声明式定义:@Tool 注解

通过在方法上标注 @Tool 注解,直接将方法转化为工具,无需手动构建 ToolCallback,是更简洁的主流方式。

  • 核心配置@Tool 注解支持 4 个关键属性,用于定义工具的核心信息(AI 模型依赖这些信息判断是否/如何调用工具):
    属性名 作用 默认值 关键说明
    name 工具唯一标识(AI 调用时的名称) 方法名 同一类/同一请求中工具名不可重复
    description 工具用途说明(帮助 AI 理解适用场景) 方法名 强烈建议手动配置,否则可能导致 AI 误用/不调用工具
    returnDirect 工具结果是否直接返回给客户端(而非回传 AI) - 控制结果流向,需结合业务场景配置
    resultConverter 工具结果转 String 的转换器 默认转换器 自定义结果格式时使用
  • 参数配置:通过 @ToolParam 注解补充方法参数信息(AI 依赖此生成调用参数):
    • description:参数格式/取值说明(如“时间需为 ISO-8601 格式”);
    • required:参数是否必填(默认必填,@Nullable 标注的参数默认可选,可通过此属性强制必填);
    • 额外支持 Swagger 的 @Schema、Jackson 的 @JsonProperty 注解扩展参数信息。
  • 方法/类要求
    • 方法:支持静态/实例方法,任意访问权限(public/private 等),返回值需可序列化(结果需回传 AI);
    • 类:支持顶层类/嵌套类,若需 AOT 编译,需将类声明为 Spring Bean(如加 @Component),否则需手动配置 GraalVM 反射(如 @RegisterReflection)。
2. 编程式定义:MethodToolCallback

通过 MethodToolCallback.Builder 手动构建工具实例,适用于需动态调整工具配置的场景(灵活性更高,但代码更繁琐)。

  • 核心构建要素(需手动指定,缺一不可):
    1. toolDefinition:工具的名称、描述、输入 JSON Schema(通过 ToolDefinitions.builder() 构建,可复用 @ToolParam 注解的参数信息);
    2. toolMethod:工具对应的方法实例(通过反射获取,如 ReflectionUtils.findMethod());
    3. toolObject:方法所属的实例对象(静态方法可省略);
  • 额外配置
    • toolMetadata:补充 returnDirect 等结果流向配置;
    • toolCallResultConverter:自定义结果转换器(同声明式的 resultConverter)。
  • 方法/类要求:与声明式完全一致(支持任意权限/静态方法,AOT 编译需 Spring Bean 或手动反射配置)。

二、两种实现方式及完整实例代码

(一)声明式实现:@Tool 注解驱动

通过在方法上添加 @Tool 注解(方法级)和 @ToolParam 注解(参数级),自动完成工具注册和 JSON Schema 生成,是最简洁的方式。

1. 核心注解说明
  • @Tool:定义工具名称、功能描述、结果处理规则(如是否直接返回客户端);
  • @ToolParam:补充参数描述(如格式要求)和必填性,帮助 AI 生成正确参数。
2. 实例代码 1:基础工具类(无参数+带参数方法)
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.context.i18n.LocaleContextHolder;

// 时间工具类:包含两个工具方法
class DateTimeTools {

    // 工具1:获取用户时区的当前日期时间(无参数)
    @Tool(description = "Get the current date and time in the user's timezone")
    String getCurrentDateTime() {
        return LocalDateTime.now()
                .atZone(LocaleContextHolder.getTimeZone().toZoneId())
                .toString();
    }

    // 工具2:设置闹钟(带参数,用@ToolParam说明格式)
    @Tool(description = "Set a user alarm for the given time")
    void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }

}
3. 实例代码 2:工具集成到 ChatClient(单次请求工具)

将工具类实例传入 ChatClient.tools() 方法,工具仅对当前请求生效:

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;

// 假设已初始化 ChatModel(如 OpenAiChatModel、OllamaChatModel)
ChatModel chatModel = ...;

// 调用工具查询明天日期
String result = ChatClient.create(chatModel)
    .prompt("What day is tomorrow?")
    .tools(new DateTimeTools()) // 传入工具类实例
    .call()
    .content();

System.out.println(result); // 输出工具执行结果或 AI 基于结果的回答
4. 实例代码 3:ChatClient 配置默认工具

通过 ChatClient.Builder.defaultTools() 设置默认工具,所有从该 Builder 创建的 ChatClient 实例共享工具(单次请求工具会覆盖默认工具):

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;

ChatModel chatModel = ...;

// 构建带默认工具的 ChatClient
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(new DateTimeTools()) // 设置默认工具
    .build();

// 后续所有请求自动携带默认工具
String timeResult = chatClient.prompt("Get current time").call().content();
5. 实例代码 4:工具集成到 ChatModel(单次请求工具)

通过 ToolCallingChatOptions 将工具传入 ChatModel,工具仅对当前 call() 请求生效:

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatOptions;
import org.springframework.ai.chat.model.Prompt;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;

ChatModel chatModel = ...;

// 将工具类转为 ToolCallback 数组
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());

// 配置工具选项
ChatOptions chatOptions = org.springframework.ai.tool.ToolCallingChatOptions.builder()
    .toolCallbacks(dateTimeTools)
    .build();

// 发起带工具的请求
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
String modelResult = chatModel.call(prompt).getResult().getOutput().getContent();
6. 实例代码 5:ChatModel 配置默认工具

ChatModel 初始化时通过 defaultOptions 设置默认工具,该模型所有请求共享工具(单次请求工具会覆盖默认工具):

import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;

// 将工具类转为 ToolCallback 数组
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());

// 构建带默认工具的 ChatModel(以 OllamaChatModel 为例)
ChatModel chatModel = OllamaChatModel.builder()
    .ollamaApi(OllamaApi.builder().build())
    .defaultOptions(org.springframework.ai.tool.ToolCallingChatOptions.builder()
            .toolCallbacks(dateTimeTools) // 默认工具
            .build())
    .build();

// 发起请求(自动携带默认工具)
String modelResult = chatModel.call(new Prompt("Get current time")).getResult().getOutput().getContent();

(二)编程式实现:MethodToolCallback

通过手动构建 MethodToolCallback 封装方法,需显式获取 Method 实例、配置工具定义(ToolDefinition),适合动态调整工具信息的场景。

1. 核心组件说明
  • Method 实例:通过反射获取目标方法;
  • ToolDefinition:定义工具名称、描述、输入 Schema;
  • MethodToolCallback:绑定 MethodToolDefinition 和工具实例(非静态方法)。
2. 实例代码 1:基础工具类(实例方法+静态方法)
import java.time.LocalDateTime;
import org.springframework.context.i18n.LocaleContextHolder;

// 时间工具类:无注解,通过编程式封装
class DateTimeTools {

    // 实例方法:需工具实例才能调用
    String getCurrentDateTime() {
        return LocalDateTime.now()
                .atZone(LocaleContextHolder.getTimeZone().toZoneId())
                .toString();
    }

    // 静态方法:无需工具实例,直接通过类调用
    static String getStaticCurrentDateTime() {
        return LocalDateTime.now()
                .atZone(LocaleContextHolder.getTimeZone().toZoneId())
                .toString();
    }

    // 带参数的实例方法
    void setAlarm(String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }

}
3. 实例代码 2:构建实例方法的 ToolCallback
import org.springframework.util.ReflectionUtils;
import org.springframework.ai.tool.MethodToolCallback;
import org.springframework.ai.tool.model.ToolDefinitions;
import java.lang.reflect.Method;

// 1. 反射获取实例方法(getCurrentDateTime,无参数)
Method instanceMethod = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");

// 2. 构建 ToolDefinition(工具描述、名称)
var toolDefinition = ToolDefinitions.builder(instanceMethod)
        .description("Get the current date and time in the user's timezone")
        .build();

// 3. 构建 MethodToolCallback(需绑定工具实例)
DateTimeTools toolInstance = new DateTimeTools();
MethodToolCallback instanceToolCallback = MethodToolCallback.builder()
        .toolDefinition(toolDefinition)
        .toolMethod(instanceMethod)
        .toolObject(toolInstance) // 实例方法必需:绑定工具实例
        .build();
4. 实例代码 3:构建静态方法的 ToolCallback
import org.springframework.util.ReflectionUtils;
import org.springframework.ai.tool.MethodToolCallback;
import org.springframework.ai.tool.model.ToolDefinitions;
import java.lang.reflect.Method;

// 1. 反射获取静态方法(getStaticCurrentDateTime,无参数)
Method staticMethod = ReflectionUtils.findMethod(DateTimeTools.class, "getStaticCurrentDateTime");

// 2. 构建 ToolDefinition
var staticToolDefinition = ToolDefinitions.builder(staticMethod)
        .description("Get static current date and time in the user's timezone")
        .build();

// 3. 构建 MethodToolCallback(静态方法无需 toolObject)
MethodToolCallback staticToolCallback = MethodToolCallback.builder()
        .toolDefinition(staticToolDefinition)
        .toolMethod(staticMethod)
        .build(); // 静态方法:省略 toolObject
5. 实例代码 4:带参数方法的 ToolCallback(结合 @ToolParam)
import org.springframework.util.ReflectionUtils;
import org.springframework.ai.tool.MethodToolCallback;
import org.springframework.ai.tool.model.ToolDefinitions;
import org.springframework.ai.tool.annotation.ToolParam;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

// 带 @ToolParam 注解的参数方法(补充参数描述)
class DateTimeTools {
    void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }
}

// 构建带参数方法的 ToolCallback
Method setAlarmMethod = ReflectionUtils.findMethod(DateTimeTools.class, "setAlarm", String.class);
DateTimeTools toolInstance = new DateTimeTools();

MethodToolCallback setAlarmToolCallback = MethodToolCallback.builder()
        .toolDefinition(ToolDefinitions.builder(setAlarmMethod)
                .description("Set a user alarm for the given time")
                .build()) // 自动识别 @ToolParam 生成 Schema
        .toolMethod(setAlarmMethod)
        .toolObject(toolInstance)
        .build();
6. 实例代码 5:编程式工具集成到 ChatClient
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;

ChatModel chatModel = ...;
MethodToolCallback toolCallback = ...; // 前面构建的 ToolCallback

// 单次请求工具
String result = ChatClient.create(chatModel)
    .prompt("What day is tomorrow?")
    .toolCallbacks(toolCallback) // 传入编程式工具
    .call()
    .content();

// 配置默认工具
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultToolCallbacks(toolCallback) // 默认编程式工具
    .build();
7. 实例代码 6:编程式工具集成到 ChatModel
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.Prompt;
import org.springframework.ai.tool.MethodToolCallback;

ChatModel chatModel = ...;
MethodToolCallback toolCallback = ...; // 前面构建的 ToolCallback

// 单次请求工具
var chatOptions = org.springframework.ai.tool.ToolCallingChatOptions.builder()
        .toolCallbacks(toolCallback)
        .build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
String modelResult = chatModel.call(prompt).getResult().getOutput().getContent();

// 配置默认工具(以 OllamaChatModel 为例)
ChatModel defaultToolModel = OllamaChatModel.builder()
        .ollamaApi(OllamaApi.builder().build())
        .defaultOptions(org.springframework.ai.tool.ToolCallingChatOptions.builder()
                .toolCallbacks(toolCallback)
                .build())
        .build();

三、关键规则与限制

  1. 方法约束
    • 支持任意可见性(public/protected/private/包级私有)、实例/静态方法;
    • 参数/返回值支持:基本类型、POJO、枚举、List/Map/数组;
    • 不支持类型:Optional、异步类型(CompletableFuture)、响应式类型(Mono/Flux)、函数式类型(Function);
    • 返回值需可序列化(传递给 AI 模型)。
  2. AOT 编译支持
    • 工具类为 Spring Bean(如 @Component)时,自动支持 AOT;
    • 非 Bean 类需添加 @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS)
  3. 工具名称唯一性:同一请求中工具名称不可重复,否则 AI 模型无法识别。

四、核心总结

  • 声明式:通过 @Tool/@ToolParam 注解快速定义工具,适合固定功能场景,代码简洁;
  • 编程式:通过 MethodToolCallback 手动构建工具,适合动态配置或第三方类方法,灵活性高;
  • 集成方式:两种工具均可集成到 ChatClient(单次/默认)或 ChatModel(单次/默认),单次工具优先级高于默认工具。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐