Spring AI 1.x 系列【19】工具执行生命周期与核心组件设计
1. 概述
Spring AI 工具执行(Tool Execution)就是用 AI 模型传过来的参数,调用具体的工具方法,执行业务逻辑并返回结果的过程。
2. 工具执行管理器
2.1 ToolCallingManager
ToolCallingManager 是工具执行的总控接口,负责管理工具执行生命周期。
接口定义:
public interface ToolCallingManager {
// 解析工具定义(从配置选项中)
List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);
// 执行工具调用(从模型响应中)
ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
// 创建构建器
static DefaultToolCallingManager.Builder builder() {
return DefaultToolCallingManager.builder();
}
}
两个核心能力:
resolveToolDefinitions:解析工具定义(告诉AI有哪些工具可用)executeToolCalls:执行AI模型要求的工具调用
类图:
2.2 DefaultToolCallingManager
DefaultToolCallingManager 是其默认实现类,Spring AI Spring Boot Starters 已经自动配置了一个 Bean ,也可以自定义实现。
核心属性:
// 可观测性注册表
private final ObservationRegistry observationRegistry;
// 工具回调解析器(按名称解析 ToolCallback)
private final ToolCallbackResolver toolCallbackResolver;
// 工具执行异常处理器
private final ToolExecutionExceptionProcessor toolExecutionExceptionProcessor;
// 可观测性约定
private ToolCallingObservationConvention observationConvention;
2.2.1 resolveToolDefinitions()
解析工具定义方法执行流程:
ToolCallingChatOptions
│
├── toolCallbacks (直接使用)
│
└── toolNames
│
▼
ToolCallbackResolver.resolve(name)
│
▼
ToolCallback
│
▼
ToolDefinition
流程说明:
- 获取已有的
ToolCallback列表 - 遍历所有的工具名称,通过
ToolCallbackResolver解析出ToolCallback实例对象 - 返回解析到的
ToolDefinition列表
源码:
@Override
public List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions) {
// 1. 获取已有的 ToolCallback 列表
List<ToolCallback> toolCallbacks = new ArrayList<>(chatOptions.getToolCallbacks());
// 2. 遍历 toolNames,通过 Resolver 解析
for (String toolName : chatOptions.getToolNames()) {
// 跳过已存在的工具(避免重复)
if (chatOptions.getToolCallbacks().stream()
.anyMatch(tool -> tool.getToolDefinition().name().equals(toolName))) {
continue;
}
// 通过 Resolver 解析
ToolCallback toolCallback = this.toolCallbackResolver.resolve(toolName);
if (toolCallback == null) {
throw new IllegalStateException("No ToolCallback found for tool name: " + toolName);
}
toolCallbacks.add(toolCallback);
}
// 3. 提取 ToolDefinition 列表
return toolCallbacks.stream().map(ToolCallback::getToolDefinition).toList();
}
2.2.1 executeToolCalls()
执行工具调用方法流程说明:
- 获取模型返回的
toolcalls信息 - 构建工具上下文
ToolContext - 执行工具调用
executeToolCall方法,根据名称查找所有对应的ToolCallback实例 - 执行
ToolCallback#call()方法 - 构建对话历史返回工具执行结果
executeToolCalls 方法源码:
@Override
public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {
// 1. 查找包含工具调用的 Generation
Optional<Generation> toolCallGeneration = chatResponse.getResults()
.stream()
.filter(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls()))
.findFirst();
if (toolCallGeneration.isEmpty()) {
throw new IllegalStateException("No tool call requested by the chat model");
}
// 2. 获取 AssistantMessage
AssistantMessage assistantMessage = toolCallGeneration.get().getOutput();
// 3. 构建 ToolContext
ToolContext toolContext = buildToolContext(prompt, assistantMessage);
// 4. 执行工具调用
InternalToolExecutionResult internalResult = executeToolCall(prompt, assistantMessage, toolContext);
// 5. 构建对话历史
List<Message> conversationHistory = buildConversationHistoryAfterToolExecution(
prompt.getInstructions(), assistantMessage, internalResult.toolResponseMessage());
// 6. 返回结果
return ToolExecutionResult.builder()
.conversationHistory(conversationHistory)
.returnDirect(internalResult.returnDirect())
.build();
}
executeToolCall 方法源码:
private InternalToolExecutionResult executeToolCall(
Prompt prompt, AssistantMessage assistantMessage, ToolContext toolContext) {
// 1. 获取配置的 ToolCallbacks
List<ToolCallback> toolCallbacks = List.of();
if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions) {
toolCallbacks = toolCallingChatOptions.getToolCallbacks();
}
List<ToolResponseMessage.ToolResponse> toolResponses = new ArrayList<>();
Boolean returnDirect = null;
// 2. 遍历所有工具调用
for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {
String toolName = toolCall.name();
String toolInputArguments = toolCall.arguments();
// 3. 查找对应的 ToolCallback
ToolCallback toolCallback = toolCallbacks.stream()
.filter(tool -> toolName.equals(tool.getToolDefinition().name()))
.findFirst()
.orElseGet(() -> this.toolCallbackResolver.resolve(toolName));
if (toolCallback == null) {
throw new IllegalStateException("No ToolCallback found for tool name: " + toolName);
}
// 4. 计算 returnDirect 标志
if (returnDirect == null) {
returnDirect = toolCallback.getToolMetadata().returnDirect();
} else {
returnDirect = returnDirect && toolCallback.getToolMetadata().returnDirect();
}
// 5. 执行工具调用(带可观测性)
String toolCallResult = ToolCallingObservationDocumentation.TOOL_CALL
.observation(...)
.observe(() -> {
try {
return toolCallback.call(finalToolInputArguments, toolContext);
} catch (ToolExecutionException ex) {
return this.toolExecutionExceptionProcessor.process(ex);
}
});
// 6. 收集响应
toolResponses.add(new ToolResponseMessage.ToolResponse(
toolCall.id(), toolName, toolCallResult != null ? toolCallResult : ""));
}
return new InternalToolExecutionResult(
ToolResponseMessage.builder().responses(toolResponses).build(),
returnDirect);
}
3. 工具异常处理器
3.1 ToolExecutionException
工具执行失败报错时,会抛出ToolExecutionException :
public class ToolExecutionException extends RuntimeException {
private final ToolDefinition toolDefinition;
public ToolExecutionException(ToolDefinition toolDefinition, Throwable cause) {
super(cause.getMessage(), cause);
this.toolDefinition = toolDefinition;
}
public ToolDefinition getToolDefinition() {
return this.toolDefinition;
}
}
如果你自己写 ToolCallback 工具实现,执行出错时必须抛 ToolExecutionException,否则框架无法捕获处理:
// 自定义工具示例
public class MyTool implements ToolCallback {
@Override
public String call(String input) {
try {
// 业务逻辑
} catch (Exception e) {
// 必须抛这个异常!
throw new ToolExecutionException("工具执行失败", e);
}
}
}
异常处理器由 DefaultToolCallingManager 内部自动调用,无缝对接工具执行全流程,无需手动调用。
3.2 ToolExecutionExceptionProcessor
ToolExecutionExceptionProcessor 是异常的「决策器」,决定把错误消息发给 AI 模型(让模型生成友好回答),还是直接抛出异常给调用者(程序自己处理)。
函数式接口,只有一个核心方法:
// 输入:工具执行抛出的异常
// 输出:二选一 → 要么返回错误文本(给模型),要么直接抛异常(给调用者)
String process(ToolExecutionException exception);
DefaultToolExecutionExceptionProcessor 是 Spring Boot Starter 自动配置的默认实现,是开箱即用的异常处理规则:
- 运行时异常(如空指针、业务报错)→ 把错误消息返回给
AI模型 - 检查异常 / 系统错误(如
IOException、内存溢出)→ 直接抛出给调用者
核心源码:
@Override
public String process(ToolExecutionException exception) {
Assert.notNull(exception, "exception cannot be null");
Throwable cause = exception.getCause();
if (cause instanceof RuntimeException runtimeException) {
if (this.rethrownExceptions.stream().anyMatch(rethrown -> rethrown.isAssignableFrom(cause.getClass()))) {
throw runtimeException;
}
}
else {
// If the cause is not a RuntimeException (e.g., IOException,
// OutOfMemoryError), rethrow the tool exception.
throw exception;
}
if (this.alwaysThrow) {
throw exception;
}
String message = exception.getMessage();
if (message == null || message.isBlank()) {
message = "Exception occurred in tool: " + exception.getToolDefinition().name() + " ("
+ cause.getClass().getSimpleName() + ")";
}
logger.debug("Exception thrown by tool: {}. Message: {}", exception.getToolDefinition().name(), message,
exception);
return message;
}
提供一个核心配置项 alwaysThrow :
true:所有错误直接抛出,不发给模型false:遵循上述默认规则(默认值)
可以通过配置项直接控制默认处理器的行为
spring:
ai:
tools:
throw-exception-on-error: true
也可以手动创建处理器,覆盖默认配置:
@Bean
public ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
// true = 所有工具异常直接抛出,不发给模型
return new DefaultToolExecutionExceptionProcessor(true);
}
4. 工具执行谓词
4.1 ToolExecutionEligibilityPredicate
Spring AI 自动执行工具的资格谓词类,它的作用只有一个:判断框架是否可以自动执行 AI 模型发起的工具调用。
接口定义:
import java.util.function.BiPredicate;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.util.Assert;
/**
* Interface for determining when tool execution should be performed based on model
* responses.
*
* @author Christian Tzolov
*/
public interface ToolExecutionEligibilityPredicate extends BiPredicate<ChatOptions, ChatResponse> {
/**
* Determines if tool execution should be performed based on the prompt options and
* chat response.
* @param promptOptions The options from the prompt
* @param chatResponse The response from the chat model
* @return true if tool execution should be performed, false otherwise
*/
default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse) {
Assert.notNull(promptOptions, "promptOptions cannot be null");
Assert.notNull(chatResponse, "chatResponse cannot be null");
return test(promptOptions, chatResponse);
}
}
DefaultToolExecutionEligibilityPredicate 是自带的默认判断规则实现,必须同时满足 3 个条件,框架才会自动执行工具:
@Override
public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
// 三个条件 必须全部成立,才返回true(自动执行)
return
// 条件1:开启了框架自动执行工具
ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions)
// 条件2:AI模型的响应不为空
&& chatResponse != null
// 条件3:模型响应中包含工具调用指令
&& chatResponse.hasToolCalls();
}
条件说明:
- 条件
1,检查是否开启了框架自动执行工具,默认值是true(框架全自动);如果手动设为false,直接不满足,框架不执行。 - 条件
2,校验模型返回的响应不能是null,避免空指针报错。 - 条件
3,校验模型是否包含需要调用工具的信息,不需要自然不用执行。
是 「框架控制执行」和「用户控制执行」的分界线:
3个条件都满足 → 框架自动执行工具- 任意一个不满足 → 框架放弃自动执行,工具调用交给你手动处理
4.2 自定义实现
可以自己写规则,替换默认的判断逻辑,比如:
- 只允许特定工具自动执行;
- 只有管理员请求才自动执行工具;
- 加限流、加权限判断。
自定义实现 ToolExecutionEligibilityPredicate 接口,重写 test() 方法:
// 自定义工具执行资格判断器
public class MyToolEligibilityPredicate implements ToolExecutionEligibilityPredicate {
@Override
public boolean test(ChatOptions options, ChatResponse response) {
// 自定义规则:开启自动执行 + 模型有工具调用 + 只允许查询客户工具执行
return ToolCallingChatOptions.isInternalToolExecutionEnabled(options)
&& response != null
&& response.hasToolCalls()
&& response.getToolCalls().get(0).name().equals("getCustomerInfo");
}
}
在创建 ChatModel Bean 时,注入你的自定义实现:
// 注入到ChatModel中
@Bean
public ChatModel chatModel(ModelClient client) {
return new OpenAiChatModel(client, OpenAiChatOptions.builder()
.toolExecutionEligibilityPredicate(new MyToolEligibilityPredicate()) // 注入自定义裁判
.build());
}
5. 执行模式
5.1 全自动
默认由框架控制工具执行,全程自动化、无感知,Spring AI 帮你完成所有操作,你只需要定义工具,不用写执行代码。
执行流程:

流程说明:
- 当我们需要让模型使用某个工具时,会将该工具的定义包含在聊天请求(提示词)中,并调用聊天模型
API将请求发送至AI模型。 - 当模型决定调用工具时,会返回一个聊天响应(
ChatResponse),其中包含工具名称以及遵循预定义模式的输入参数。 - 聊天模型将工具调用请求发送至工具调用管理器
API。 - 工具调用管理器负责识别待调用的工具,并使用传入的输入参数执行该工具。
- 工具调用的执行结果会返回给工具调用管理器。
- 工具调用管理器将工具执行结果回传给聊天模型。
- 聊天模型将工具执行结果发送给
AI模型(以工具响应消息形式)。 AI模型将工具调用结果作为额外上下文,生成最终响应,并通过聊天客户端将其返回给调用方(ChatResponse)。
提示:目前,与模型交换的关于工具执行的内部消息不会向用户开放。如果你需要访问这些消息,应该使用用户控制的工具执行方法。
5.2 基于 ToolCallAdvisor 的可控工具执行
可以使用 ToolCallAdvisor 将工具调用集成到通知器链中。该方案具备以下优势:
- 可观测性:通知器链中的其他通知器可拦截并观测每一次工具调用流程
- 与聊天记忆集成:可与聊天记忆通知器无缝配合,实现对话历史管理
- 可扩展性:可通过扩展通知器自定义工具调用行为
ToolCallAdvisor 实现了工具调用循环逻辑,并禁用模型的内部工具执行功能。当模型请求调用工具时,通知器会执行该工具并将结果回传给模型,持续循环直至无需再调用工具。
var toolCallAdvisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(toolCallAdvisor)
.build();
String response = chatClient.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
ToolCallAdvisor.Builder 支持以下配置项:
toolCallingManager:用于执行工具调用的工具调用管理器实例。若未指定,将使用默认实例。advisorOrder:通知器在链中的执行优先级,取值范围必须在BaseAdvisor.HIGHEST_PRECEDENCE(最高优先级)和BaseAdvisor.LOWEST_PRECEDENCE(最低优先级)之间。conversationHistoryEnabled:控制通知器是否在工具调用循环中内部维护对话历史,默认值为true。
默认情况下(conversationHistoryEnabled=true),ToolCallAdvisor 会在工具调用循环中内部维护完整的对话历史,后续每一次大语言模型调用都会包含所有历史消息。
可通过 .disableMemory() 方法禁用内部对话历史管理。禁用后,仅会将最后一条工具响应消息传递至下一轮循环。该配置适用于已集成聊天记忆通知器(由其负责对话历史管理)的场景:
var toolCallAdvisor = ToolCallAdvisor.builder()
.toolCallingManager(toolCallingManager)
.disableMemory() // 由聊天记忆组件管理历史
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
.build();
var chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory)
.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 200) // 优先级高于ToolCallAdvisor
.build();
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(chatMemoryAdvisor, toolCallAdvisor)
.build();
5.3 手动控制
框架默认的自动执行很方便,但满足不了复杂场景,比如:
- 多轮工具循环调用(模型需要多次调用工具);
- 结合对话记忆(保存历史聊天记录);
- 自定义工具执行顺序、加权限 / 限流 / 日志;
- 手动处理工具结果、干预模型对话流程。
我们可以关闭框架自动执行工具,自己写代码手动管理工具执行生命周期,实现步骤:
- 检查模型是否需要调用工具;
- 手动调用
ToolCallingManager执行工具; - 手动拼接对话历史,重新请求模型;
- 循环处理直到模型不需要工具。
最简实现,适合单次 / 简单多轮工具调用:
// 1. 初始化核心组件
ChatModel chatModel = ...; // AI模型(通义/OpenAI等)
ToolCallingManager toolCallingManager = ToolCallingManager.builder().build(); // 工具执行管理器
// 2. 关键配置:关闭框架自动执行工具!!!
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(new CustomerTools()) // 注册你的工具
.internalToolExecutionEnabled(false) // 核心:关闭自动执行
.build();
// 3. 构建用户请求
Prompt prompt = new Prompt("查询ID为42的客户信息", chatOptions);
// 4. 第一次请求模型,获取初始响应
ChatResponse chatResponse = chatModel.call(prompt);
// 5. 核心循环:只要模型需要调用工具,就手动执行
while (chatResponse.hasToolCalls()) {
// 手动执行工具(框架不再管,这行是你自己写的)
ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);
// 用工具执行结果,构建新的对话请求(拼接历史)
prompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions);
// 重新请求模型,让模型根据工具结果生成回答
chatResponse = chatModel.call(prompt);
}
// 6. 模型不再需要调用工具,输出最终回答
System.out.println(chatResponse.getResult().getOutput().getText());
生产级实现,结合 ChatMemory 保存完整对话历史,模型能记住之前的所有对话和工具调用结果:
// 1. 初始化工具管理器 + 对话记忆器(保存聊天历史)
ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder().build();
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
String conversationId = UUID.randomUUID().toString(); // 对话唯一ID
// 2. 关闭自动执行,注册工具
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(ToolCallbacks.from(new MathTools()))
.internalToolExecutionEnabled(false)
.build();
// 3. 构建初始对话(系统提示词+用户问题)
Prompt prompt = new Prompt(
List.of(new SystemMessage("你是得力助手"), new UserMessage("6*8等于多少?")),
chatOptions);
// 把初始消息存入记忆
chatMemory.add(conversationId, prompt.getInstructions());
// 4. 带记忆请求模型
Prompt promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
ChatResponse chatResponse = chatModel.call(promptWithMemory);
chatMemory.add(conversationId, chatResponse.getResult().getOutput());
// 5. 循环执行工具(全程同步记忆)
while (chatResponse.hasToolCalls()) {
// 手动执行工具
ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(promptWithMemory, chatResponse);
// 把工具执行结果存入记忆
chatMemory.add(conversationId, toolExecutionResult.conversationHistory().getLast());
// 带最新记忆重新请求模型
promptWithMemory = new Prompt(chatMemory.get(conversationId), chatOptions);
chatResponse = chatModel.call(promptWithMemory);
// 把模型新响应存入记忆
chatMemory.add(conversationId, chatResponse.getResult().getOutput());
}
// 6. 多轮对话:用户问历史问题,模型能记住(因为有记忆)
UserMessage newUserMessage = new UserMessage("我刚才问了你什么?");
chatMemory.add(conversationId, newUserMessage);
ChatResponse newResponse = chatModel.call(new Prompt(chatMemory.get(conversationId)));
6. 自动配置类
在模块 spring-ai-autoconfigure-model-tool 中,提供了 AI 聊天模型(ChatModel)提供工具调用(Tool Calling)功能的 Spring Boot 自动配置类 ToolCallingAutoConfiguration 。
工具调用管理器:
/**
* 注册【工具调用管理器】:工具调用核心中枢
* 整合:工具解析器 + 异常处理器 + 监控系统,统筹整个工具调用流程
*
* 条件:容器中不存在自定义管理器时加载
*
* @param toolCallbackResolver 工具解析器
* @param toolExecutionExceptionProcessor 异常处理器
* @param observationRegistry 监控注册中心(Micrometer)
* @param observationConvention 监控约定
* @return 工具调用管理器
*/
@Bean
@ConditionalOnMissingBean
ToolCallingManager toolCallingManager(ToolCallbackResolver toolCallbackResolver,
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ToolCallingObservationConvention> observationConvention) {
// 构建工具调用管理器,注入核心依赖组件
var toolCallingManager = ToolCallingManager.builder()
// 无监控注册中心时,使用空实现
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
.toolCallbackResolver(toolCallbackResolver)
.toolExecutionExceptionProcessor(toolExecutionExceptionProcessor)
.build();
// 注入自定义监控约定(如果存在)
observationConvention.ifAvailable(toolCallingManager::setObservationConvention);
return toolCallingManager;
}
工具执行异常处理器:
/**
* 注册【工具执行异常处理器】:统一处理工具调用过程中的异常
* 支持配置化控制是否抛出异常,并自动兼容 Spring Security OAuth2 授权异常
*
* 条件:容器中不存在自定义处理器时加载
*
* @param properties 工具调用配置属性
* @return 默认工具执行异常处理器
*/
@Bean
@ConditionalOnMissingBean
ToolExecutionExceptionProcessor toolExecutionExceptionProcessor(ToolCallingProperties properties) {
ArrayList<Class<? extends RuntimeException>> rethrownExceptions = new ArrayList<>();
// 兼容 Spring Security OAuth2 授权异常,直接抛出不捕获
Class<? extends RuntimeException> oauth2Exception = getClassOrNull(
"org.springframework.security.oauth2.client.ClientAuthorizationException");
if (oauth2Exception != null) {
rethrownExceptions.add(oauth2Exception);
}
// 构建异常处理器:支持配置是否抛出异常 + 指定需要直接抛出的异常类型
return DefaultToolExecutionExceptionProcessor.builder()
.alwaysThrow(properties.isThrowExceptionOnError())
.rethrowExceptions(rethrownExceptions)
.build();
}
工具调用内容监控过滤器:
/**
* 注册【工具调用内容监控过滤器】
* 作用:将工具调用的入参、返回结果写入监控日志,用于调试
*
* 触发条件:配置文件开启 spring.ai.model.tool.observations.include-content=true
* 安全提醒:开启后可能泄露敏感数据
*
* @return 监控过滤器
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = ToolCallingProperties.CONFIG_PREFIX + ".observations", name = "include-content",
havingValue = "true")
ToolCallingContentObservationFilter toolCallingContentObservationFilter() {
logger.warn(
"You have enabled the inclusion of the tool call arguments and result in the observations, with the risk of exposing sensitive or private information. Please, be careful!");
return new ToolCallingContentObservationFilter();
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)