🚀 本文内容:详细了解 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** 中定义工具 。ChatModel和 ChatClient都支持传入 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_weather、search-docs、tool.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注解定义的工具到 ChatClient或 ChatModel中。
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 提供了 MethodToolCallback和 FunctionToolCallback实现。
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 接口处理。默认情况下,工具执行的资格是通过检查 ToolCallingChatOptions 的 internalToolExecutionEnabled 属性(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:先设置
internalToolExecutionEnabled为false - 关键点 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,实际就是配置 DefaultToolExecutionExceptionProcessor的 alwaysThrow属性。
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 入门指南