第 5 篇 Spring AI - Tool Calling 全面解析:从基础到高级应用

🚀 本文内容:详细了解 Spring AI 提供的 Tool Calling 的相关概念、定义方法、工具 API。

相关概念

定义

大语言模型(LLM)在训练后就会被冻结,导致知识陈旧,并且无法访问或修改外部数据。 Function Calling** 机制解决了这些缺点,它允许注册用户自定义函数,以将大模型连接到外部系统的 API。**这些系统可以为 LLM 提供实时数据并代表它们执行数据处理操作。

工具调用(Function Calling,也称之为函数调用)是 AI 应用的常见模式,允许模型与一组 API 或工具进行交互,从而扩展模型的能力。

作用

工具调用主要包括两类作用:

  • 信息检索 :从外部数据源检索信息,比如数据库、Web 服务、文件系统、Web 搜索引起等等。例如,获取天气信息、查询数据库、检索最新新闻等。目标:增强模型的知识
  • 执行操作 :在软件系统中执行特定操作,比如发送 Email、在数据库中执行 SQL、提交表单、触发工作流等。例如,预定航班、填写表单、生成代码等。目标:自动化那些原本需要人工干预或显式编程的任务

实现原理

🚀** 模型调用工具逻辑,是由客户端程序提供的**。模型只能请求执行工具调用并提供入参,而应用程序负责根据入参、工具名来执行工具调用并返回结果。模型绝对不会直接访问工具提供的 API ,这是至关重要的安全性考虑

**Spring AI 提供了方便的 API 来定义工具、解决模型发出的工具调用请求,并执行这些工具调用。**核心原理图如下:

(1)在调用模型请求前,在 Prompt 中定义 Function 信息(何时调用 Function、调用 Function 的参数等信息)。

(2)当模型决定调用 Function 时,它会根据 Function 定义准备好入参 ,并调用该 Function ,并将输出返回给模型

(3)Spring AI 会处理好工具调用,它会将 Function Calling 分派给对应的 Function,并将结果返回给模型。

(4)一旦获取了所需的所有信息,模型就会生成响应。

快速示例

前文提到,工具调用的两个主要作用:信息检索 + 执行操作。

那么,我们来写两个例子,分别是信息检索(获取当前时间)和执行操作(定闹钟)示例。

信息检索 - 获取当前时间

模型无法访问实时信息,比如模型无法知道我所在位置的当前时间。

通过提供一个工具,可以让模型知道我当前所在时区的时间。

代码实现

代码实现非常简单,就两个步骤:

1、定义工具:使用 @Tool注解指定工具方法

2、添加工具:在 ChatClient调用前,添加工具调用信息

java 复制代码
// 关键点1:定义工具
public class DateTimeTools {
    @Tool(description = "获取当前日期时间")
    public String getCurrentDateTime() {
        return LocalDateTime.now().atZone(ZoneId.systemDefault()).toString();
    }
}

// 关键点2:引用工具
@GetMapping
@ResponseBody
public String simpleChat() {
    Prompt prompt = new Prompt("你好,现在几点了?", OpenAiChatOptions.builder()
            .temperature(0.7)
            .build());

    return chatClient.prompt(prompt).tools(new DateTimeTools()).call().content();
}

测试效果

1、没有上述工具的情况下,无法获取当前时间

plain 复制代码
我无法获取实时信息,包括当前时间。你可以查看你的设备(手机、电脑等)上的时钟来获得准确时间。如果你需要其他帮助,比如时间换算或日程安排建议,我很乐意为你提供支持! 😊

2、有工具定义,会调用工具,返回当前时间。

plain 复制代码
现在是北京时间 2026年1月18日 晚上8点12分。

执行操作 - 定闹钟

假设现在我们想要为某个计划设定一个闹钟,那首先需要调用上面的工具知道当前时间,其次调用工具来设置好闹钟。

代码实现

代码实现非常简单,就两个步骤:

1、定义工具:使用 @Tool注解指定工具方法

2、添加工具:在 ChatClient调用前,添加工具调用信息

java 复制代码
public class DateTimeTools {
    @Tool(description = "获取当前日期时间")
    public String getCurrentDateTime() {
        return LocalDateTime.now().atZone(ZoneId.systemDefault()).toString();
    }

    @Tool(description = "设置闹钟")
    public void setAlarm(String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        log.info("闹钟设置:{}", alarmTime);
    }
}

@GetMapping("/demo2")
@ResponseBody
public String simpleChat2() {
    Prompt prompt = new Prompt("你好,帮我定个10分钟后的闹钟", OpenAiChatOptions.builder()
            .temperature(0.7)
            .build());

    return chatClient.prompt(prompt).tools(new DateTimeTools()).call().content();
}

测试效果

当前时间:2026/01/18 20:22:16

提示词:你好,帮我定个10分钟后的闹钟

执行效果:好的!闹钟已成功设置。我已经为您设置了10分钟后的闹钟,即在2026年1月18日20:32:26响铃。

工具定义 - 两种方式

ToolCallback接口:工具定义接口,支持从 **Method****Function** 中定义工具ChatModelChatClient都支持传入 ToolCallback来定义工具列表。

:::color1

不太理解 Method 和 Function,见下文娓娓道来。

:::

ChatClient中使用工具时,有如下几类方法:

  • tools使用声明式 @Tool注解声明的工具
  • toolCallbacks使用编程式 ToolCallback来定义工具
java 复制代码
interface ChatClientRequestSpec {
    // 直接使用工具对象
    ChatClientRequestSpec tools(Object... toolObjects);

    // 使用ToolCallback(包括MethodToolCallback和FunctionToolCallback)
    ChatClientRequestSpec toolCallbacks(ToolCallback... toolCallbacks);
    ChatClientRequestSpec toolCallbacks(List<ToolCallback> toolCallbacks);
    ChatClientRequestSpec toolCallbacks(ToolCallbackProvider... toolCallbackProviders);

    // 使用@Bean来寻找工具定义
    ChatClientRequestSpec toolNames(String... toolNames);
    ChatClientRequestSpec toolContext(Map<String, Object> toolContext);

    // ...
}

Method 作为工具

Spring AI 内置了两种从 Method 来定义工具的方法:

  • 声明式:使用 @Tool@ToolParam注解。本质上就是 MethodToolCallback
  • 编程式:使用底层的 MethodToolCallback实现

使用 @Tool定义工具 - 声明式

就像前面的例子一样,使用 @Tool定义工具。

java 复制代码
public class DateTimeTools {
    @Tool(description = "获取当前日期时间")
    public String getCurrentDateTime() {
        return LocalDateTime.now().atZone(ZoneId.systemDefault()).toString();
    }
}
@Tool 注解

字段说明:

1、**name**:工具名称 。如果没写,则自动默认为方法名称。工具名称是唯一标识,不可重复。为了保证各类模型的兼容性,工具名称推荐仅使用字母、数字、下划线、连字符和点。比如 get_weathersearch-docstool.v1。请勿包含空格或(),可能会造成兼容性问题。

2、**description**:工具描述。用于模型理解何时以及如何调用工具。务必填写清楚,以防止模型在应该调用工具的时候却没有调用工具。

3、**returnDirect****:是否直接返回给客户端还是传回给模型。**默认 false,即默认情况会传回给模型。一般模型会加工一下下,而不是将工具返回的原始结果直接返回给客户端。如果设置为 true,则会把工具调用结果直接返回给客户端。

4、**resultConverter**结果转换器 ,将工具结果转换为 String 转发给模型。ToolCallResultConverter 接口的实现类,默认是 DefaultToolCallResultConverter

哪些 Java 方法可以作为工具来定义?静态方法、实例方法均可,任何访问级别(public、protected、default、private)均可。包含工具方法的类可以是顶级类、内部类,该类也可以具有任何访问级别。

入参 - 相关注解

方法参数有啥限制不?入参支持无参、任意参数及大多数的参数类型(基本类型、pojo、enum、list、array、map 等等)。出参也是一样的,需注意的时候返回类型必须能够序列化,因为要发送给模型。

方法入参额外信息提供 :Spring AI 会自动给 @Tool注解方法生成 JSON Schema,这个 Schema 用于模型去理解怎么去调用工具以及如何准备工具的入参。详见 **JsonSchemaGenerator**

1️⃣ **使用 ****@ToolParam**注解可以给入参提供额外的信息,比如描述(description)、是否必填(required,默认 true)。

java 复制代码
@Tool(description = "设置闹钟")
public void setAlarm(@ToolParam(description = "要设置的闹钟的时间", required = true) String time) {
    LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
    log.info("闹钟设置:{}", alarmTime);
}

2️⃣ 如果入参指定了**@Nullable**注解 ,则会被认为是可选参数。除非 @ToolParam注解中标记为 required=true

3️⃣ Swagger 的 @Schema注解、Jackson 的 @JsonProperty都会被读取,以生成 JSON Schema。还有 @JsonClassDescription@JsonPropertyDescription

如何添加工具?

有好几种方式,来添加 @Tool注解定义的工具到 ChatClientChatModel中。

java 复制代码
// 方式1:tools或defaultTools方法
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultTools(new DateTimeTools())
    .build();

ChatClient.create(chatModel)
    .prompt("What day is tomorrow?")
    .tools(new DateTimeTools())
    .call()
    .content();


// 方式2:在defaultOptions或options上
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatModel chatModel = OllamaChatModel.builder()
    .ollamaApi(OllamaApi.builder().build())
    .defaultOptions(ToolCallingChatOptions.builder()
            .toolCallbacks(dateTimeTools)
            .build())
    .build();

ChatOptions chatOptions = ToolCallingChatOptions.builder()
    .toolCallbacks(dateTimeTools)
    .build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);

:::color1

defaultTools 适用于什么场景?每个 ChatClient/ChatModel 都通用的工具。

:::

使用**<font style="color:rgb(20, 24, 24);">MethodToolCallback</font>**定义工具 - 编程式

下面是一个使用 MethodToolback编程式定义工具的例子。

  • 关键点 1:ToolDefinition工具定义,包括 name、description 和 inputSchema。inputSchema 是工具入参的 JSON Schema。如果没有提供,则会自动基于方法入参生成。也可以使用 @ToolParam 注解为入参提供额外信息,比如 description 或 required。
  • 关键点 2:ToolMetadata工具元数据,包括 returnDirect。
  • 关键点 3:toolObject、toolMethod要调用的工具对象及方法。如果调用的是静态方法,则不需要传入toolObject 对象。
  • 关键点 4:toolCallResultConverter工具调用的结果转换器,必须转换为 String。默认值为 DefaultToolCallResultConverter
  • 关键点 3:使用 MethodToolCallback的 builder 方法构造 ToolCallback实例。
  • 关键点 4:在 ChatClient中使用 toolCallbacks()方法引入工具定义。
java 复制代码
@GetMapping("/demo3")
@ResponseBody
public String simpleChat3() throws NoSuchMethodException {
    Prompt prompt = new Prompt("你好,帮我定个10分钟后的闹钟", OpenAiChatOptions.builder()
            .temperature(0.7)
            .build());

    ToolDefinition toolDefinition = ToolDefinition.builder()
            .name("getCurrentDateTime")
            .description("获取当前日期时间")
            .inputSchema("""
                    {
                      "$schema" : "https://json-schema.org/draft/2020-12/schema",
                      "type" : "object",
                      "properties" : { },
                      "required" : [ ],
                      "additionalProperties" : false
                    }
                    """)
            .build();
    ToolMetadata toolMetadata = ToolMetadata.builder()
            .returnDirect(false)
            .build();
    MethodToolCallback methodToolCallback = MethodToolCallback.builder()
            .toolDefinition(toolDefinition)
            .toolMetadata(toolMetadata)
            .toolObject(new DateTimeTools())
            .toolMethod(DateTimeTools.class.getMethod("getCurrentDateTime"))
            .toolCallResultConverter(new DefaultToolCallResultConverter())
            .build();

    return chatClient.prompt(prompt).toolCallbacks(methodToolCallback).call().content();
}

有没有发现,上面的例子很啰嗦?🚀 **直接使用 ****MethodToolCallbackProvider****即可快速构造 **ToolCallback** 。**其实,这就是 @Tool注解的本质实现。

java 复制代码
Prompt prompt = new Prompt("你好,帮我定个10分钟后的闹钟", OpenAiChatOptions.builder()
                .temperature(0.7)
                .build());

MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder()
        .toolObjects(new DateTimeTools())
        .build();
return chatClient.prompt(prompt).toolCallbacks(provider.getToolCallbacks()).call().content();

同样地,可以在 ChatClient的 toolCallbacks 或 defaultToolCallbacks 方法上设置工具定义。或者在ChatModel上使用 chatOptions 或 defaultChatOptions 中设置工具定义。

Method 定义的工具限制

Method 定义的工具,不支持如下类型作为参数或返回值。

  • <font style="color:rgb(25, 30, 30);">Optional</font>
  • 异步类型(比如<font style="color:rgb(25, 30, 30);">CompletableFuture</font>, <font style="color:rgb(25, 30, 30);">Future</font>
  • Reactive 类型(比如<font style="color:rgb(25, 30, 30);">Flow</font>, <font style="color:rgb(25, 30, 30);">Mono</font>, <font style="color:rgb(25, 30, 30);">Flux</font>
  • Functional 类型(比如<font style="color:rgb(25, 30, 30);">Function</font>, <font style="color:rgb(25, 30, 30);">Supplier</font>, <font style="color:rgb(25, 30, 30);">Consumer</font>)=> 使用基于 Function 的工具定义方法,详见下面描述。

Function 作为工具

Spring AI 支持通过底层的FunctionToolCallback实现、动态定义的 @Bean 实现 Function 工具定义。

:::danger

Function,指的是 Java 的函数式接口,包括如下:

  • Function<T, R> - 单参数函数:接收一个入参 T,返回一个出参 R。
  • BiFunction<T, U, R> - 双参数函数:接收两个入参 T 和 U,返回一个出参 R。
  • Supplier - 提供者:无入参,返回一个结果 T。
  • Consumer - 消费者:一个入参 T,无出参。

:::

FunctionToolCallback定义工具

可以将 Java 中的函数式接口,通过<font style="color:rgb(25, 30, 30);">FunctionToolCallback</font> 转换为工具定义。

java 复制代码
// 定义工具
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
    public WeatherResponse apply(WeatherRequest request) {
        return new WeatherResponse(30.0, Unit.C);
    }
}

public enum Unit { C, F }
public record WeatherRequest(@ToolParam(description = "The name of a city or a country") String location, Unit unit) {}
public record WeatherResponse(double temp, Unit unit) {}

// 工具定义
ToolCallback toolCallback = FunctionToolCallback
    .builder("currentWeather", new WeatherService())
    .description("Get the weather in location")
    .inputType(WeatherRequest.class)
    .build();

// 工具引用
chatClient.prompt(prompt).toolCallbacks(toolCallback).call().content();

本质上来说,要构造的工具定义的相关属性与之前都是一样的。

使用 @Bean 动态定义工具

前面都是编程方式定义,Spring AI 支持在运行时动态获取 Bean,以 Bean 作为工具定义 。其会使用 ToolCallbackResolver 接口的实现类 SpringBeanToolCallbackResolver 来从 Spring 容器中按名称找到 Bean,然后将其转化为 FunctionToolCallback 的实现类。

使用 @Description注解来定义工具描述信息,使得模型能够理解合适调用工具以及如何准备工具入参。

使用 toolNames方法来指定 Bean 名称。剩下的交给SpringBeanToolCallbackResolver来处理。

java 复制代码
@Configuration(proxyBeanMethods = false)
class WeatherTools {

    WeatherService weatherService = new WeatherService();

    @Bean
    @Description("获取指定位置的天气")
    Function<WeatherRequest, WeatherResponse> currentWeather() {
        return weatherService;
    }
}

@GetMapping("/demo6")
@ResponseBody
public String simpleChat6() throws NoSuchMethodException {
    Prompt prompt = new Prompt("你好,现在合肥天气怎么样?", OpenAiChatOptions.builder()
            .temperature(0.7)
            .build());

    return chatClient.prompt(prompt).toolNames("currentWeather").call().content();
}

Function 工具限制

使用 Function 工具时,不支持以下类型作为入参和出参:

  • 基本数据类型:Primitive types =》 Method 定义工具时是支持的哦。
  • Optional
  • Collection 类型(比如List, Map, Array, Set
  • Asynchronous 类型(比如CompletableFuture, Future
  • Reactive 类型(比如Flow, Mono, Flux

工具 API 规范

Spring AI 工具通过<font style="color:rgb(25, 30, 30);">ToolCallback</font>接口与模型交互。通过了解 Spring AI 的工具规范,可以让我们了解如何自定义和扩展工具以支持更多的场景。

ToolCallback 接口

<font style="color:rgb(25, 30, 30);">ToolCallback</font>接口:提供可以被 AI 模型调用的工具定义及执行逻辑。

java 复制代码
public interface ToolCallback {

	/**
	 * Definition used by the AI model to determine when and how to call the tool.
	 */
	ToolDefinition getToolDefinition();

	/**
	 * Metadata providing additional information on how to handle the tool.
	 */
	ToolMetadata getToolMetadata();

    /**
	 * Execute tool with the given input and return the result to send back to the AI model.
	 */
	String call(String toolInput);

    /**
	 * Execute tool with the given input and context, and return the result to send back to the AI model.
	 */
	String call(String toolInput, ToolContext tooContext);

}

针对 ToolCallback接口,Spring AI 提供了 MethodToolCallbackFunctionToolCallback实现。

ToolDefinition 接口

ToolDefinition 接口:提供 AI 模型需要知道的工具的基本信息,包括工具名称、描述、输入 Schema。

java 复制代码
public interface ToolDefinition {

	/**
	 * The tool name. Unique within the tool set provided to a model.
	 */
	String name();

	/**
	 * The tool description, used by the AI model to determine what the tool does.
	 */
	String description();

	/**
	 * The schema of the parameters used to call the tool.
	 */
	String inputSchema();

}

使用 <font style="color:rgb(25, 30, 30);">ToolDefinition.Builder</font>可以快速构建一个使用<font style="color:rgb(25, 30, 30);">DefaultToolDefinition</font>实现的<font style="color:rgb(25, 30, 30);">ToolDefinition</font>实例。

java 复制代码
// 方式1:使用ToolDefinition.builder()
ToolDefinition toolDefinition = ToolDefinition.builder()
    .name("currentWeather")
    .description("Get the weather in location")
    .inputSchema("""
        {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string"
                },
                "unit": {
                    "type": "string",
                    "enum": ["C", "F"]
                }
            },
            "required": ["location", "unit"]
        }
    """)
    .build();

// 方式2:使用 ToolDefinitions.from
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinitions.from(method);

// 方式3:使用 ToolDefinitions.builder
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinitions.builder(method)
    .name("currentDateTime")
    .description("Get the current date and time in the user's timezone")
    .inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
    .build();

JSON Schema

提供工具给 AI 模型时,需要告知入参的 JSON Schema 信息,其用于告知模型怎么样调用工具以及如何构造工具入参 。Spring AI 提供内置了一个 <font style="color:rgb(25, 30, 30);">JsonSchemaGenerator</font>,用于给工具入参生成 JSON Schema。

工具入参的 description 和 required 选项,支持自定义传入。

description 自定义注解:

  • <font style="color:rgb(25, 30, 30);">@ToolParam(description = "...")</font> from Spring AI
  • <font style="color:rgb(25, 30, 30);">@JsonClassDescription(description = "...")</font> from Jackson
  • <font style="color:rgb(25, 30, 30);">@JsonPropertyDescription(description = "...")</font> from Jackson
  • <font style="color:rgb(25, 30, 30);">@Schema(description = "...")</font> from Swagger

required 自定义注解:

  • <font style="color:rgb(25, 30, 30);">@ToolParam(required = false)</font> from Spring AI
  • <font style="color:rgb(25, 30, 30);">@JsonProperty(required = false)</font> from Jackson
  • <font style="color:rgb(25, 30, 30);">@Schema(required = false)</font> from Swagger
  • <font style="color:rgb(25, 30, 30);">@Nullable</font> from Spring Framework
java 复制代码
class CustomerTools {
    @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);
    }
    
    @Tool(description = "Update customer information")
    void updateCustomerInfo(Long id, String name, @ToolParam(required = false) String email) {
        System.out.println("Updated info for customer with id: " + id);
    }
}

ToolCallResultConverter 结果转换

工具调用的结果,需要支持使用<font style="color:rgb(25, 30, 30);">ToolCallResultConverter</font>将其序列化为一个字符串对象。

java 复制代码
@FunctionalInterface
public interface ToolCallResultConverter {

	/**
	 * Given an Object returned by a tool, convert it to a String compatible with the
	 * given class type.
	 */
	String convert(@Nullable Object result, @Nullable Type returnType);

}

默认实现类<font style="color:rgb(25, 30, 30);">DefaultToolCallResultConverter</font>,会将结果序列化为 JSON 字符串。支持提供自定义实现,实现自定义的序列化。

  • 如果使用 Method 工具定义,则直接在 <font style="color:rgb(25, 30, 30);">@Tool</font>注解中使用 <font style="color:rgb(25, 30, 30);">resultConverter</font>指定结果转换器。或者在MethodToolCallback.Builder 中使用 resultConverter 方法指定即可。
  • 如果使用 Function 工具定义,则直接在 FunctionToolCallback.Builder 中使用 resultConverter 方法指定即可。

ToolContext 工具上下文

Spring AI 支持通过<font style="color:rgb(25, 30, 30);">ToolContext</font> 想工具传递额外的上下文信息。使得能够在工具执行过程中使用,但不会发送给 AI 模型。

示例:

java 复制代码
// 工具定义中使用ToolContext获取上下文信息
class CustomerTools {
    @Tool(description = "Retrieve customer information")
    Customer getCustomerInfo(Long id, ToolContext toolContext) {
        return customerRepository.findById(id, toolContext.getContext().get("tenantId"));
    }
}

// 在ChatClient中使用toolContext传入上下文信息
ChatModel chatModel = ...
String response = ChatClient.create(chatModel)
        .prompt("Tell me more about the customer with ID 42")
        .tools(new CustomerTools())
        .toolContext(Map.of("tenantId", "acme"))
        .call()
        .content();

System.out.println(response);

ReturnDirect 工具直接返回

默认情况下,工具调用的结果会发送给模型,然后模型使用工具结果继续对话

某些场景下, 想要直接将工具调用信息返回给调用方,而不是返回给模型继续处理。

  • 比如,Agent 中依赖 RAG 工具,那肯定希望直接返回 RAG 工具的结果,而不是模型加工后的结果。
  • 如果同时请求多个工具调用,则必须将 returnDirect 属性设置为true,以便所有工具直接将结果返回给调用者。否则,结果将被发送回模型。

每一个 **<font style="color:rgb(25, 30, 30);">ToolCallback</font>**实现类都支持指定 **<font style="color:rgb(25, 30, 30);">returnDirect</font>**字段 。默认情况下(值为 false),会发送给模型,而不是直接返回给客户端。如下图所示,如果设置 <font style="color:rgb(25, 30, 30);">returnDirect=true</font>,则流程到达 4 后,<font style="color:rgb(25, 30, 30);">ToolCallingManager</font>会直接将工具结果转发给客户端。

ToolCallingManager 全生命周期管理

<font style="color:rgb(25, 30, 30);">ToolCallingManager</font>负责工具调用的全生命周期管理 。如果使用 Spring AI Spring Boot Starter,则会自动配置<font style="color:rgb(25, 30, 30);">DefaultToolCallingManager</font> 作为<font style="color:rgb(25, 30, 30);">ToolCallingManager</font>接口的实现。

java 复制代码
public interface ToolCallingManager {

	/**
	 * Resolve the tool definitions from the model's tool calling options.
	 */
	List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);

	/**
	 * Execute the tool calls requested by the model.
	 */
	ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);

}

框架控制的工具执行(默认)

默认情况下,使用 ToolCallingManager控制工具执行。其会拦截所有来自模型的工具请求,调用工具,然后将工具结果返回给模型。

**如何决定工具是否要调用呢?**由 ToolExecutionEligibilityPredicate 接口处理。默认情况下,工具执行的资格是通过检查 ToolCallingChatOptionsinternalToolExecutionEnabled 属性(true 表示由 ChatModel 负责模型请求的工具调用执行,false 表示由调用者负责模型请求的工具调用执行)是否设置为 true(这是默认值),以及 ChatResponse 是否包含任何工具调用来确定的。**可在 ****CheModel**上设定自定义的 **toolExecutionEligibilityPredicate**,来决定工具是否调用。

java 复制代码
public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {

	@Override
	public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
		return ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions) 
            && chatResponse != null && chatResponse.hasToolCalls();
	}

}

用户控制的工具执行(自定义)

框架控制的工具调用,是不会把工具调用结果暴露给用户的。如果想要访问这些 tool message,则应该使用用户控制的工具调用。

示例 1 - 最简单

比如,下面的例子:

  • 关键点 1:先设置 internalToolExecutionEnabledfalse
  • 关键点 2:使用 ChatModel调用模型,并返回 ChatResponse
  • 关键点 3:自行判断ChatResponse是否需要工具调用,如果需要,就调用工具。然后将工具调用结果,再次发送给模型。最后模型给出响应结果。
java 复制代码
@GetMapping("/demo7")
@ResponseBody
public String simpleChat7() throws NoSuchMethodException {
    ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();

    ChatOptions chatOptions = ToolCallingChatOptions.builder()
            .toolCallbacks(MethodToolCallbackProvider.builder().toolObjects(new DateTimeTools()).build().getToolCallbacks())
            .internalToolExecutionEnabled(false)
            .build();
    Prompt prompt = new Prompt("你好,现在几点了?", chatOptions);

    ChatResponse chatResponse = chatModel.call(prompt);
    while (chatResponse.hasToolCalls()) {
        ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);
        prompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions);
        chatResponse = chatModel.call(prompt);
    }

    return chatResponse.getResult().getOutput().getText();
}

在上述示例中,使用 toolExecutionResult.conversationHistory()即可获取完整对话历史。

  • UserMassage:用户问"你好,现在几点了"
  • AssistantMessage:模型回复,我要调用工具,工具名是 getCurrentDateTime,无参
  • ToolResponseMessage:工具调用响应消息,返回结果 "2026-01-20T19:25:32.539413500+08:00[Asia/Shanghai]"
示例 2 - 添加记忆

下面的例子,稍微复杂一些,会在用户控制工具执行时,记录完整的对话记忆。

java 复制代码
@GetMapping("/demo8")
@ResponseBody
public List<Message> simpleChat8() {
    // 设定工具调用选项及Prompt提示词
    ChatOptions chatOptions = ToolCallingChatOptions.builder()
            .toolCallbacks(ToolCallbacks.from(new MathTools()))
            .internalToolExecutionEnabled(false)
            .build();
    Prompt prompt = new Prompt(List.of(new SystemMessage("你是一个厉害的小助手"),
            new UserMessage("6 * 8 = ?")), chatOptions);

    // 添加对话记忆(第一次,用户消息)
    ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
    String conversationId = UUID.randomUUID().toString();
    chatMemory.add(conversationId, prompt.getInstructions());

    // 添加对话记忆(第二次,模型消息)
    Prompt promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
    ChatResponse chatResponse = chatModel.call(promptWithMemory);
    chatMemory.add(conversationId, chatResponse.getResult().getOutput());

    ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder().build();
    while (chatResponse.hasToolCalls()) {
        // 添加对话记忆(第三次,工具调用消息)
        ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(promptWithMemory, chatResponse);
        chatMemory.add(conversationId, toolExecutionResult.conversationHistory().get(toolExecutionResult.conversationHistory().size() - 1));

        // 添加对话记忆(第四次,模型消息)
        promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
        chatResponse = chatModel.call(promptWithMemory);
        chatMemory.add(conversationId, chatResponse.getResult().getOutput());
    }

    // 添加对话记忆(第五次,用户消息)
    UserMessage newUserMessage = new UserMessage("我前面问的啥问题来着?");
    chatMemory.add(conversationId, newUserMessage);

    ChatResponse newResponse = chatModel.call(new Prompt(chatMemory.get(conversationId)));
    chatMemory.add(conversationId, newResponse.getResult().getOutput());

    return chatMemory.get(conversationId);
}

public class MathTools {
    @Tool(description = "计算两个数相乘的结果")
    public String multiply(BigInteger a, BigInteger b) {
        BigInteger result = a.multiply(b);
        log.info("{} * {} = {}", a, b, result);
        return result.toString();
    }
}

执行结果如下:

java 复制代码
[
// 第一个:系统消息
{"messageType":"SYSTEM","metadata":{"messageType":"SYSTEM"},"text":"你是一个厉害的小助手"},

// 第二个:用户消息
{"messageType":"USER","metadata":{"messageType":"USER"},"media":[],"text":"6 * 8 = ?"},

// 第三个:模型消息(想调用工具)
{"messageType":"ASSISTANT","metadata":{"role":"ASSISTANT","messageType":"ASSISTANT","finishReason":"TOOL_CALLS","refusal":"","index":0,"annotations":[{}],"id":"019bdb4097452b4187cef535e065d43e"},"toolCalls":[{"id":"019bdb40b10d4c9ae2f321670548f728","type":"function","name":"multiply","arguments":"{\"a\": 6, \"b\": 8}"}],"media":[],"text":"我来帮你计算 6 * 8 的结果。\n\n"},

// 第四个:工具消息(返回工具调用结果)
{"responses":[{"id":"019bdb40b10d4c9ae2f321670548f728","name":"multiply","responseData":"48"}],"metadata":{"messageType":"TOOL"},"messageType":"TOOL","text":""},

// 第五个:模型消息(返回结果给用户)
{"messageType":"ASSISTANT","metadata":{"role":"ASSISTANT","messageType":"ASSISTANT","finishReason":"STOP","refusal":"","index":0,"annotations":[{}],"id":"019bdb40b1bbd14ddabee08dfc0d75a5"},"toolCalls":[],"media":[],"text":"6 * 8 = 48"},

// 第六个:用户继续提问
{"messageType":"USER","metadata":{"messageType":"USER"},"media":[],"text":"我前面问的啥问题来着?"},

// 第七个:模型消息
{"messageType":"ASSISTANT","metadata":{"role":"ASSISTANT","messageType":"ASSISTANT","finishReason":"STOP","refusal":"","index":0,"annotations":[{}],"id":"019bdb40b908673cf67e1fd07f7fbffd"},"toolCalls":[],"media":[],"text":"你一开始问的是:**6 * 8 = ?**  \n\n然后我帮你计算得到了结果是 **48**。"}

]

异常处理

当调用工具失败时,会抛出<font style="color:rgb(25, 30, 30);">ToolExecutionException</font> 异常。

ToolExecutionExceptionProcessor接口会处理ToolExecutionException 异常。如果使用了 Spring AI Spring Boot Starter,则会自动配置DefaultToolExecutionExceptionProcessor作为工具执行异常处理器(会在上图所示的调用逻辑中使用)。

java 复制代码
@FunctionalInterface
public interface ToolExecutionExceptionProcessor {
    // 将异常转换为String,以便于能够直接发送给模型或者抛出一个异常让调用方处理
	String process(ToolExecutionException exception);
}

@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
    return new DefaultToolExecutionExceptionProcessor(true);
}

DefaultToolExecutionExceptionProcessor的异常处理逻辑:

1、默认情况下,如果是<font style="color:rgb(25, 30, 30);">RuntimeException</font>,则会发回给 AI 模型。如果是检查型异常和 Error 类型(Checked Exception,比如 SQLExcepion,IOException,OutOfMemoryError 等),则会抛出异常。

2、如果配置了alwaysThrow 属性为 true,则异常会一直抛出让调用方来处理。

spring.ai.tools.throw-exception-on-error,实际就是配置 DefaultToolExecutionExceptionProcessoralwaysThrow属性。

ToolCallbackResolver 工具解析

ToolCallbackResolver就是工具解析的接口,包括两个实现类:

  • 动态解析 SpringBean,即从 Bean 实例中解析 ToolCallback。
  • 静态解析 Static,即创建解析器的时候传入 ToolCallback,后续按照 ToolCallback 中的 ToolDefinition 中的 name 作为 key,ToolCallback 作为 value 形成一个 Map,后续直接根据 name 到 Map 里查询。
  • 委派解析 Delegating,可以包括动态解析 + 静态解析。

支持,工具调用,相关内容,介绍完毕!🚀🚀🚀

参考

1.Tool Calling 使用指南 | Spring AI Alibaba

2.https://docs.spring.io/spring-ai/reference/api/tools.html

相关博文

1.第 1 篇 Spring AI Aliaba - AI 快速体验

2.第 2 篇 Spring AI Alibaba 初体验:原来 Java 也能轻松玩转 AI Agent

3.第 3 篇 Spring AI - Model API 入门指南

4.第 4 篇 深入理解 Spring AI ChatClient:一篇就够了,比官方文档更友好

5.第 5 篇 Spring AI - Tool Calling 全面解析:从基础到高级应用

相关推荐
酉鬼女又兒2 小时前
SQL113+114 更新记录(一)(二)+更新数据知识总结
java·服务器·前端
zuozewei2 小时前
零基础 | AI应用记忆管理:从短期到长期的完整实践指南
运维·服务器·人工智能
数说星榆1812 小时前
小型工厂工艺流程图制作_在线设计装配/焊接/冲压工艺流程模板
大数据·论文阅读·人工智能·流程图·论文笔记
老蒋每日coding2 小时前
AI Agent 设计模式系列(十九)—— 评估和监控模式
人工智能·设计模式
AI浩2 小时前
用于自动驾驶的ApolloScape数据集
人工智能·机器学习·自动驾驶
毅炼2 小时前
Netty 常见问题总结
java·网络·数据结构·算法·哈希算法
Anastasiozzzz2 小时前
leetcodehot100--最小栈 MinStack
java·javascript·算法
weixin_421585012 小时前
无监督配准
人工智能
救救孩子把2 小时前
56-机器学习与大模型开发数学教程-5-3 最速下降法与动量法(Momentum)
人工智能·机器学习