提示:如果不能访问 OpenAI,请点击 AiCode API 注册账号,通过代理访问。
前面章节介绍了 Spring AI 中工具调用的核心概念,以及如何通过方法和函数定义工具。下面将介绍工具是如何被执行?为了后续演示方便,先使用 @Tool 注解定义一个工具,代码如下:
package com.hxstrive.springai.springai_openai.tools.tools21;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
import java.time.LocalDateTime;
public class DateTimeTools {
@Tool(description = "获取用户所在时区的当前日期和时间")
public String getCurrentDateTime() {
System.out.println("->> getCurrentDateTime() 获取用户所在时区的当前日期和时间");
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}上面提供的名为 getCurrentDateTime() 工具将返回用户所在时区的当前日期和时间。
工具执行是指使用提供的输入参数调用工具并返回结果的过程。该过程由 ToolCallingManager 接口处理,该接口负责管理工具执行的完整生命周期。
ToolCallingManager 接口定义如下:
package org.springframework.ai.model.tool;
import java.util.List;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.tool.definition.ToolDefinition;
/**
* 工具调用管理器接口,负责协调AI模型与工具之间的交互流程
* 主要功能包括解析可用工具定义和执行工具调用
*/
public interface ToolCallingManager {
/**
* 解析工具定义列表
* 根据聊天选项确定当前可用的工具集合,供AI模型选择调用
*
* @param chatOptions 聊天选项,可能包含工具调用相关的配置参数
* @return 可用的工具定义列表,每个定义描述了工具的基本信息和调用方式
*/
List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);
/**
* 执行工具调用
* 根据AI模型的响应结果(包含工具调用指令)执行相应的工具,并返回执行结果
*
* @param prompt 原始的提示信息,包含用户的查询内容
* @param chatResponse AI模型的响应,可能包含工具调用请求
* @return 工具执行结果,包含执行状态和输出数据
*/
ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
/**
* 创建默认工具调用管理器的构建器
* 提供一种便捷的方式来配置和实例化 DefaultToolCallingManager
*
* @return DefaultToolCallingManager 的构建器实例
*/
static DefaultToolCallingManager.Builder builder() {
return DefaultToolCallingManager.builder();
}
}上面源码中,ToolDefinition 表示工具定义,工具调用需要用到。工具定义接口,用于描述AI可调用工具的基本信息和输入规范,该接口定义了工具的核心元数据,使AI模型能够理解工具的功能和使用方式。
ToolDefinition 接口定义如下:
package org.springframework.ai.tool.definition;
public interface ToolDefinition {
/**
* 获取工具的名称
* 名称用于唯一标识工具,是AI模型指定调用哪个工具的关键标识
*
* @return 工具的名称字符串
*/
String name();
/**
* 获取工具的描述信息
* 描述详细说明工具的功能和用途,帮助AI模型判断该工具是否适合解决当前问题
*
* @return 工具的描述字符串
*/
String description();
/**
* 获取工具的输入数据模式(Schema)
* 通常以JSON Schema格式定义,描述工具所需输入参数的结构、类型和约束条件,
* 确保AI模型能够生成符合要求的输入数据
*
* @return 输入模式的字符串表示(通常为JSON Schema)
*/
String inputSchema();
/**
* 创建默认工具定义的构建器
* 提供便捷的方式来构建DefaultToolDefinition实例
*
* @return DefaultToolDefinition的构建器对象
*/
static DefaultToolDefinition.Builder builder() {
return DefaultToolDefinition.builder();
}
}如果使用 Spring AI Spring Boot Starter,DefaultToolCallingManager 将作为 ToolCallingManager 接口的自动配置实现。你可通过提供自定义的 ToolCallingManager Bean 来定制工具执行行为。例如:
@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder().build();
}默认情况下,Spring AI 在每个 ChatModel 实现中透明地管理工具执行生命周期。但你可选择退出此行为,自行控制工具执行。下面将描述这两种场景。
使用默认配置时,Spring AI 会自动拦截模型的工具调用请求,执行工具并将结果返回模型。这些操作均由各 ChatModel 实现通过 ToolCallingManager 透明完成。对于开发人员是完全透明的,不需要进行任何干预。如下图:

上图说明:
① 如果要为 AI 模型提供工具,需把工具定义(含名称、描述、输入模式等信息)包含在聊天请求(Prompt),随后调用 ChatModel API,将该请求发送至 AI 模型。
② 当 AI 模型判定需要调用工具时,会返回包含工具名称以及符合工具输入模式参数的响应(ChatResponse)。
③ ChatModel 把工具调用请求传递给 ToolCallingManager API。
④ ToolCallingManager 依据请求,识别出要调用的工具,并使用给定的输入参数去执行该工具。
⑤ 工具执行完毕后,结果会返回给 ToolCallingManager。
⑥ ToolCallingManager 将工具执行结果回传给 ChatModel。
⑦ ChatModel 把工具执行结果以 ToolResponseMessage 的形式返回给 AI 模型。
⑧ AI 模型以工具调用结果作为额外上下文,生成最终的响应,再通过 ChatClient 将该响应返回给调用方。
注意:目前与模型之间就工具执行所交换的内部消息不会向用户公开。如果您需要访问这些消息,应当采用用户控制的工具执行方式。
问题:Spring AI 是如何判断一个工具调用是否可以执行的呢?
工具调用是否具备执行资格的逻辑由 ToolExecutionEligibilityPredicate 接口处理。默认情况下,通过检查 ToolCallingChatOptions 的 internalToolExecutionEnabled 属性(默认值为 true)及 ChatResponse 是否包含工具调用来判定执行资格。
ToolExecutionEligibilityPredicate 接口定义:
package org.springframework.ai.model.tool;
import java.util.function.BiPredicate;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.util.Assert;
/**
* 用于决定是否需要执行工具调用
* 继承自BiPredicate,支持对聊天选项和模型响应进行条件判断
*/
public interface ToolExecutionEligibilityPredicate extends BiPredicate<ChatOptions, ChatResponse> {
/**
* 判断是否需要执行工具调用
* 该默认方法对输入参数进行非空校验后,调用test方法执行具体的判断逻辑
*
* @param promptOptions 聊天选项,包含与本次对话相关的配置信息
* @param chatResponse AI模型的响应结果,可能包含工具调用请求
* @return 如果需要执行工具调用则返回true,否则返回false
* @throws IllegalArgumentException 如果任何参数为null时抛出
*/
default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse) {
// 校验输入参数非空,确保后续逻辑的安全性
Assert.notNull(promptOptions, "promptOptions cannot be null");
Assert.notNull(chatResponse, "chatResponse cannot be null");
// 调用BiPredicate的test方法执行具体的判断逻辑
return this.test(promptOptions, chatResponse);
}
}Spring AI 为 ToolExecutionEligibilityPredicate 接口提供了一个默认实现 DefaultToolExecutionEligibilityPredicate,默认实现中,仅仅实现了 test() 方法,如下:
package org.springframework.ai.model.tool;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
/**
* 工具执行资质的默认判断实现类
* 实现了ToolExecutionEligibilityPredicate接口,提供默认的工具执行必要性判断逻辑
*/
public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {
/**
* 默认构造函数
*/
public DefaultToolExecutionEligibilityPredicate() {
}
/**
* 判断是否需要执行工具调用的核心逻辑
* 当且仅当满足以下所有条件时,才需要执行工具调用:
* 1. 工具执行功能已通过聊天选项启用
* 2. 模型响应不为空
* 3. 模型响应中包含工具调用请求
*
* @param promptOptions 聊天选项,包含工具执行的配置信息
* @param chatResponse AI模型的响应结果,可能包含工具调用指令
* @return 如果满足所有工具执行条件则返回true,否则返回false
*/
public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
// 检查工具执行是否启用,且响应不为空且包含工具调用
return ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions)
&& chatResponse != null
&& chatResponse.hasToolCalls();
}
}注意:创建 ChatModel Bean 时,你可提供自定义的 ToolExecutionEligibilityPredicate 实现。
某些情况下,你可能希望自行控制工具执行生命周期。此时可将 ToolCallingChatOptions 的 internalToolExecutionEnabled 属性设为 false。
当使用此选项调用 ChatModel 时,工具执行将委托给调用方,由你完全控制工具执行生命周期。你需要检查 ChatResponse 中的工具调用,并使用 ToolCallingManager 执行它们。
以下示例演示了用户控制工具执行方案的最小实现:
package com.hxstrive.springai.springai_openai.tools.tools21.controller;
import com.hxstrive.springai.springai_openai.tools.tools21.DateTimeTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.model.tool.ToolExecutionResult;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
class MyController {
@Autowired
private ChatModel chatModel;
// 模型使用 gpt-4-turbo
// GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
// http://localhost:8080/ai
@GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
public String ai() {
// 创建 ToolCallingManager
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();
// 创建聊天选项,禁止自动处理工具调用
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(ToolCallbacks.from(new DateTimeTools()))
.internalToolExecutionEnabled(false)
.build();
// 创建提示词 Prompt 对象
Prompt prompt = new Prompt("明天是星期几?", chatOptions);
ChatResponse chatResponse = chatModel.call(prompt);
// 循环调用,直到完成所有工具调用
while (chatResponse.hasToolCalls()) {
// 执行工具
ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);
// 将工具执行结果拼装到提示词 Prompt 中
prompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions);
// 继续调用AI,AI大模型可能返回需要调用另一个工具的响应,这就是为什么需要用 while 循环
chatResponse = chatModel.call(prompt);
}
// 响应结果
return chatResponse.getResult().getOutput().getText();
}
}运行示例,效果如下图:

选择用户控制的工具执行方案时,建议使用 ToolCallingManager 管理工具调用操作。这样可充分利用 Spring AI 内置的工具执行支持。当然,你也可完全自行实现工具执行逻辑。
下面的示例展示了结合使用 ChatMemory API 的用户控制工具执行方案的最小实现:
package com.hxstrive.springai.springai_openai.tools.tools21.controller;
import com.hxstrive.springai.springai_openai.tools.tools21.DateTimeTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.DefaultToolCallingManager;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.model.tool.ToolExecutionResult;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
@RestController
class MyController {
@Autowired
private ChatModel chatModel;
// 模型使用 gpt-4-turbo
// GPT-4 全面支持工具调用功能,且在处理复杂度、准确性和灵活性上显著优于 GPT-3.5-turbo
// http://localhost:8080/ai
@GetMapping(value = "/ai",produces = "text/html; charset=UTF-8")
public String ai() {
// 创建默认的工具调用管理器,用于处理工具调用的相关逻辑
ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder().build();
// 创建聊天记忆对象,使用消息窗口模式管理对话历史
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
// 生成唯一的对话ID,用于标识当前对话会话
String conversationId = UUID.randomUUID().toString();
// 配置聊天选项,设置工具调用相关参数
ChatOptions chatOptions = ToolCallingChatOptions.builder()
// 注册工具回调,这里添加了日期时间工具
.toolCallbacks(ToolCallbacks.from(new DateTimeTools()))
// 禁用内部工具执行(由用户自定义工具调用)
.internalToolExecutionEnabled(false)
.build();
// 创建初始提示信息,包含系统消息和用户问题
Prompt prompt = new Prompt(
List.of(new SystemMessage("你是AI助手小粒,所有回复均使用中文。"),
new UserMessage("明天是星期几?")),
chatOptions);
// 将初始提示信息添加到聊天记忆,保存对话历史
chatMemory.add(conversationId, prompt.getInstructions());
// 创建包含对话历史的新提示词,用于后续模型调用
Prompt promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
ChatResponse chatResponse = chatModel.call(promptWithMemory);
// 将模型响应结果添加到聊天记忆
chatMemory.add(conversationId, chatResponse.getResult().getOutput());
// 循环处理工具调用:如果模型响应包含工具调用请求,则执行工具并继续对话
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);
// 再次调用AI模型,传入包含工具执行结果的新提示词
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)));
// 返回模型对新问题的回答文本
return newResponse.getResult().getOutput().getText();
}
}运行示例,效果如下图:

当工具调用失败时,异常会以 ToolExecutionException 形式传播,可捕获该异常进行错误处理。

通过 ToolExecutionExceptionProcessor 可处理 ToolExecutionException 异常,异常处理会产生两种结果:要么生成错误消息发送回 AI 模型,要么抛出异常由调用者处理。
ToolExecutionExceptionProcessor 接口定义:
package org.springframework.ai.tool.execution;
/**
* 用于定义工具执行过程中发生异常时的处理逻辑,将异常转换为合适的字符串表示
*/
@FunctionalInterface
public interface ToolExecutionExceptionProcessor {
/**
* 处理工具执行异常
* 当工具调用过程中发生异常时,通过此方法对异常进行处理,返回处理后的字符串结果
* 通常用于生成传递给AI模型的错误信息,以便模型理解工具调用失败的原因。
*
* @param exception 工具执行过程中抛出的异常
* @return 处理后的异常信息字符串,通常包含异常详情或友好提示
*/
String process(ToolExecutionException exception);
}若使用 Spring AI Spring Boot Starter,DefaultToolExecutionExceptionProcessor 将作为 ToolExecutionExceptionProcessor 接口的自动配置实现。

默认会将错误信息返回 AI 模型。但是,可以通过 DefaultToolExecutionExceptionProcessor 构造函数的 alwaysThrow 参数修改此行为。如果将 alwaysThrow 设置为 true 时,将直接抛出异常而非返回错误信息。例如:
@Bean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
return new DefaultToolExecutionExceptionProcessor(true);
}源码如下:
public class DefaultToolExecutionExceptionProcessor implements ToolExecutionExceptionProcessor {
private static final Logger logger = LoggerFactory.getLogger(DefaultToolExecutionExceptionProcessor.class);
private static final boolean DEFAULT_ALWAYS_THROW = false;
private final boolean alwaysThrow; // 控制参数
public DefaultToolExecutionExceptionProcessor(boolean alwaysThrow) {
this.alwaysThrow = alwaysThrow;
}
public String process(ToolExecutionException exception) {
Assert.notNull(exception, "exception cannot be null");
if (this.alwaysThrow) {
throw exception; // 抛出异常
} else {
logger.debug("Exception thrown by tool: {}. Message: {}", exception.getToolDefinition().name(), exception.getMessage());
return exception.getMessage(); // 返回给AI模型的内容
}
}
//....
}注意,除了通过代码控制外,你还可通过 spring.ai.tools.throw-exception-on-error 属性控制 DefaultToolExecutionExceptionProcessor Bean 的行为,例如:
属性 | 说明 | 默认值 |
spring.ai.tools.throw-exception-on-error | 若为 true,工具调用错误将作为异常抛出供调用方处理;若为 false,错误将转为消息返回 AI 模型由其处理响应。 | false |
如果要自定义 ToolCallback 实现,请确保在 call() 方法的工具执行逻辑中发生错误时抛出 ToolExecutionException。
ToolExecutionExceptionProcessor 由默认的 ToolCallingManager(DefaultToolCallingManager)内部使用,用于处理工具执行期间的异常。
提示:如果不能访问 OpenAI,请点击 AiCode API 注册账号,通过代理访问。