文章目录
- [1. 核心组件](#1. 核心组件)
-
- [1.1 StateAwareToolCallback](#1.1 StateAwareToolCallback)
- [1.2 AsyncToolCallback](#1.2 AsyncToolCallback)
- [1.3 AsyncToolCallbackAdapter](#1.3 AsyncToolCallbackAdapter)
- [1.4 CancellableAsyncToolCallback](#1.4 CancellableAsyncToolCallback)
- [2. 执行流程](#2. 执行流程)
-
- [2.1 串行执行](#2.1 串行执行)
-
- [2.1.1 方法入口](#2.1.1 方法入口)
- [2.1.2 循环调用](#2.1.2 循环调用)
- [2.2 并行执行](#2.2 并行执行)
-
- [2.2.1 方法入口](#2.2.1 方法入口)
- [2.2.2 两种异步工具](#2.2.2 两种异步工具)
- [2.1.3 原生异步执行](#2.1.3 原生异步执行)
- [2.1.4 包装异步执行](#2.1.4 包装异步执行)
1. 核心组件
异步工具的核心价值在于并行执行多个独立工具调用,当 Agent 需要同时调用多个工具时(如查询天气+股票+新闻),异步模式可以显著减少用户等待时间,提升用户体验。
1.1 StateAwareToolCallback
StateAwareToolCallback 继承自 Spring AI ToolCallback 接口,没有声明任何方法,是一个标记接口,用于标记标识工具需要 Agent 状态注入,让工具能读取 Agent 当前状态、运行配置、修改 Agent 状态等操作。
java
public interface StateAwareToolCallback extends ToolCallback {
// marker interface - no methods required
}
在 AgentToolNode#executeToolCallWithInterceptors() 在方法执行前,会进行状态注入:
java
// 仅对以下三类工具注入状态(这些工具需要读写Agent的执行状态):
// 1. StateAwareToolCallback:状态感知型工具(AsyncToolCallback 是其子类)
// 2. FunctionToolCallback:函数式接口工具(BiFunction 实现的工具)
// 3. MethodToolCallback:反射方法型工具
if (toolCallback instanceof StateAwareToolCallback
|| toolCallback instanceof FunctionToolCallback<?, ?>
|| toolCallback instanceof MethodToolCallback) {
// 向工具上下文注入三大核心Agent信息,供工具内部使用:
toolContextMap.putAll(Map.of(
AGENT_STATE_CONTEXT_KEY, state, // 注入Agent【全局只读状态】
AGENT_CONFIG_CONTEXT_KEY, config, // 注入Agent【运行时配置】
AGENT_STATE_FOR_UPDATE_CONTEXT_KEY, extraStateFromToolCall // 注入Agent【可修改状态】
));
}
状态常量类:
java
// AgentToolNode.java:562-566 自动注入
toolContextMap.putAll(Map.of(
AGENT_STATE_CONTEXT_KEY, state, // 当前 OverAllState (只读)
AGENT_CONFIG_CONTEXT_KEY, config, // RunnableConfig 配置
AGENT_STATE_FOR_UPDATE_CONTEXT_KEY, extraStateFromToolCall // 状态更新 Map (可写)
));
状态会被设置到工具上下文中:

继承关系:

1.2 AsyncToolCallback
AsyncToolCallback 是 Spring AI Alibaba 框架中用于支持异步工具执行的核心接口,它继承自 StateAwareToolCallback,具备【状态感知能力】+【异步执行能力】。
核心作用:
- 用于执行长耗时、阻塞型工具任务(如网络请求、大数据处理、第三方接口调用)
- 不阻塞
Agent主线程,通过CompletableFuture实现异步非阻塞执行 - 支持超时控制、任务取消
核心方法:
callAsync:异步执行工具call():同步回退方法getTimeout()- 超时配置isAsync():是否为异步工具
源码如下:
java
public interface AsyncToolCallback extends StateAwareToolCallback {
/**
* 【核心异步方法】
* 异步执行工具逻辑,是异步工具的**唯一必须实现方法**
* @param arguments 工具入参(JSON字符串)
* @param context 工具执行上下文
* @return CompletableFuture<String> 异步结果对象,任务完成后自动返回结果
*/
CompletableFuture<String> callAsync(String arguments, ToolContext context);
/**
* 【同步降级方法】默认实现
* 兼容同步工具接口,将异步调用转为同步阻塞调用
* 作用:统一同步/异步工具的调用入口,方便框架统一处理
*
* 异常处理优化:
* 1. 解包 CompletionException,抛出原始异常
* 2. 捕获取消异常,转为框架标准 ToolCancelledException
*/
@Override
default String call(String arguments, ToolContext context) {
try {
// 等待异步任务执行完成(同步阻塞)
return callAsync(arguments, context).join();
}
// 处理异步执行异常:解包包装异常,抛出原始错误
catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException re) {
throw re;
}
if (cause instanceof Error err) {
throw err;
}
throw new RuntimeException("Async tool execution failed", cause);
}
// 处理任务取消异常
catch (CancellationException e) {
throw new ToolCancelledException("Tool execution was cancelled", e);
}
}
/**
* 标记是否为异步工具
* @return 固定返回 true,代表当前是异步工具
*/
default boolean isAsync() {
return true;
}
/**
* 异步工具执行超时时间
* 默认:5分钟
* 可重写自定义超时时间
*/
default Duration getTimeout() {
return Duration.ofMinutes(5);
}
}
1.3 AsyncToolCallbackAdapter
【异步工具适配器】,核心功能:将同步工具回调(ToolCallback)包装为异步工具回调(AsyncToolCallback)。
核心作用:
- 无侵入改造:让原生同步工具(
@Tool、FunctionToolCallback等)直接支持异步执行 - 并行执行:借助线程池(
Executor)实现同步工具并发运行 - 统一执行标准:同步/异步工具对外暴露统一的
AsyncToolCallback接口
重要限制 :包装后的同步工具无法真正响应取消,因为同步方法是阻塞操作,只能执行完成后终止。
核心属性:
java
/** 被包装的【原始同步工具】(委托对象,真正执行业务逻辑) */
private final ToolCallback delegate;
/** 线程池执行器:用于异步执行同步工具的方法 */
private final Executor executor;
/** 异步执行超时时间,防止任务无限阻塞 */
private final Duration timeout;
构造函数:
java
/**
* 构造适配器:使用默认超时时间(5分钟)
* @param delegate 原始同步工具
* @param executor 异步执行线程池
*/
public AsyncToolCallbackAdapter(ToolCallback delegate, Executor executor) {
this(delegate, executor, Duration.ofMinutes(5));
}
/**
* 构造适配器:自定义超时时间
* @param delegate 原始同步工具
* @param executor 异步执行线程池
* @param timeout 自定义超时时间
*/
public AsyncToolCallbackAdapter(ToolCallback delegate, Executor executor, Duration timeout) {
this.delegate = Objects.requireNonNull(delegate, "delegate must not be null");
this.executor = Objects.requireNonNull(executor, "executor must not be null");
this.timeout = Objects.requireNonNull(timeout, "timeout must not be null");
}
【核心异步执行方法】将同步工具的 call() 方法,提交到线程池异步执行:
java
/**
* 【核心异步执行方法】
* 将同步工具的call()方法,提交到线程池异步执行
* @param arguments 工具入参JSON
* @param context 工具上下文
* @return 异步结果Future
*/
@Override
public CompletableFuture<String> callAsync(String arguments, ToolContext context) {
// 在线程池中执行 原始同步工具的call方法,实现异步非阻塞
return CompletableFuture.supplyAsync(() -> delegate.call(arguments, context), executor);
}
【核心静态方法】按需包装同步工具为异步工具:
java
/**
* 【核心静态方法】按需包装同步工具为异步工具
* 1. 判断:如果已经是AsyncToolCallback,直接返回,不重复包装
* 2. 否则:创建适配器,将同步工具转为异步工具
*
* 对应业务场景:executeToolByType 中同步转异步的核心调用方法
*
* @param callback 待包装的工具
* @param executor 线程池
* @param timeout 超时时间
* @return 统一的异步工具回调
*/
public static AsyncToolCallback wrapIfNeeded(ToolCallback callback, Executor executor, Duration timeout) {
Objects.requireNonNull(callback, "callback must not be null");
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(timeout, "timeout must not be null");
// 已经是异步工具,直接返回
if (callback instanceof AsyncToolCallback async) {
return async;
}
// 同步工具 → 包装为异步适配器
return new AsyncToolCallbackAdapter(callback, executor, timeout);
}
其他方法:
java
public class AsyncToolCallbackAdapter implements AsyncToolCallback {
/**
* 获取异步超时时间
*/
@Override
public Duration getTimeout() {
return timeout;
}
// ===================== 以下方法全部【委托转发】给原始同步工具 =====================
// 适配器只做异步包装,工具元数据、定义、同步调用逻辑完全复用原工具
@Override
public ToolDefinition getToolDefinition() {
return delegate.getToolDefinition();
}
@Override
public ToolMetadata getToolMetadata() {
return delegate.getToolMetadata();
}
@Override
public String call(String toolInput) {
return delegate.call(toolInput);
}
@Override
public String call(String toolInput, ToolContext toolContext) {
return delegate.call(toolInput, toolContext);
}
/**
* 获取被包装的原始同步工具
*/
public ToolCallback getDelegate() {
return delegate;
}
/**
* 【静态工具方法】按需包装(默认超时)
* 如果工具已经是异步工具,直接返回;否则包装为适配器
*/
public static AsyncToolCallback wrapIfNeeded(ToolCallback callback, Executor executor) {
return wrapIfNeeded(callback, executor, Duration.ofMinutes(5));
}
}
1.4 CancellableAsyncToolCallback
【可取消的异步工具回调接口】继承 AsyncToolCallback,在异步执行基础上,扩展了任务协作式取消能力。
核心作用:
- 适用于
Agent并行执行多个工具的场景,支持中途取消指定任务 - 协作式取消:工具内部主动检查取消信号,优雅终止执行(非强制中断)
- 避免并行任务浪费资源,支持超时/手动中断工具执行
使用规范 : 实现类必须在任务执行过程中,周期性检查取消令牌,收到信号后立即停止。
【核心方法】支持取消的异步工具执行:
java
public interface CancellableAsyncToolCallback extends AsyncToolCallback {
/**
* 【核心方法】支持取消的异步工具执行
* 相比普通异步方法,多了 CancellationToken 取消令牌参数
*
* 实现要求:
* 工具逻辑中必须循环/定期调用 cancellationToken.throwIfCancelled()
* 一旦收到取消信号,立即抛出异常终止任务
*
* @param arguments 工具入参(JSON字符串)
* @param context 工具执行上下文
* @param cancellationToken 取消令牌:用于接收并响应取消信号
* @return 异步执行结果 Future
*/
CompletableFuture<String> callAsync(String arguments, ToolContext context, CancellationToken cancellationToken);
/**
* 【默认兼容实现】
* 重写父接口的无取消令牌方法,默认传入【空令牌(不支持取消)】
* 作用:保证接口兼容性,普通异步调用也能正常执行
*
* @param arguments 工具入参
* @param context 工具上下文
* @return 异步执行结果 Future
*/
@Override
default CompletableFuture<String> callAsync(String arguments, ToolContext context) {
// 调用支持取消的方法,传入空令牌(CancellationToken.NONE = 无取消信号)
return callAsync(arguments, context, CancellationToken.NONE);
}
}
2. 执行流程
2.1 串行执行
2.1.1 方法入口
在 AgentToolNode#apply() 方法中,进行并行、串行执行的判断,满足以下两个条件时,进入并行执行,否则进行串行执行:
- 配置开启了
parallelToolExecution(并行工具执行开关) - 工具调用数量 >
1(多个工具才需要并行)
处理逻辑:
java
// 1. 配置开启了 parallelToolExecution(并行工具执行开关)
// 2. 工具调用数量 > 1(多个工具才需要并行)
if (parallelToolExecution && toolCalls.size() > 1) {
logger.info("开启并行工具执行,工具数量:{}", toolCalls.size());
return executeToolCallsParallel(toolCalls, state, config);
}
// 否则 → 执行【串行工具调用】
// 场景:单个工具、关闭并行配置、降级场景
else {
logger.info("执行串行工具执行,工具数量:{}", toolCalls.size());
return executeToolCallsSequential(toolCalls, state, config);
}
2.1.2 循环调用
在顺序执行 executeToolCallsSequential 方法中,会循环调用所有工具:
java
for (AssistantMessage.ToolCall toolCall : toolCalls) {
// Each tool gets its own isolated update map
// If this tool times out, clear() only affects this map, not mergedUpdates
Map<String, Object> toolSpecificUpdate = new ConcurrentHashMap<>();
ToolCallResponse response = executeToolCallWithInterceptors(toolCall, state, config, toolSpecificUpdate,
false);
toolResponses.add(response.toToolResponse());
returnDirect = shouldReturnDirect(toolCall, returnDirect, config);
// Merge immediately - subsequent timeout clear() won't affect already-merged data
mergedUpdates.putAll(toolSpecificUpdate);
}
注意 :串行、并行执行,最后都是调用 executeToolCallWithInterceptors 方法进行执行,支持串行执行时就算是异步任务,效果和同步执行一样。
2.2 并行执行
Agent 需要同时调用多个独立工具时,异步并行执行可显著减少总耗时多工具调用并行执行,支持并发限流、超时、优雅取消、线程安全状态管理。
注意事项:
- 通过
state.snapShot()创建的状态快照为浅拷贝,状态中的可变对象(如List/Map)是共享的。 - 工具禁止直接修改从状态中获取的集合,必须通过
extraStateFromToolCall写入更新。
2.2.1 方法入口
executeToolCallsParallel 方法包含了所有并行执行逻辑:
- 将所有异步任务提交到线程池
- 等待所有并行工具执行完成
- 将所有结果写入到
OverAllState中
处理逻辑:
java
/**
* 【核心底层方法】多工具调用并行执行(支持并发限流、超时、优雅取消、线程安全状态管理)
*
* <p>
* <b>重要说明:</b> 通过 state.snapShot() 创建的状态快照为<b>浅拷贝</b>,状态中的可变对象(如List/Map)是共享的。
* 工具<b>禁止直接修改</b>从状态中获取的集合,必须通过 extraStateFromToolCall 写入更新。
* </p>
*
* <h3>并行状态合并语义</h3>
* <p>
* 并行模式使用 ToolStateCollector,严格遵循每个状态Key配置的 KeyStrategy 合并策略:
* <ul>
* <li>APPEND 策略:所有工具的结果会<b>追加合并</b></li>
* <li>REPLACE 策略:最后写入的结果覆盖(并行顺序不保证,非确定性)</li>
* </ul>
* </p>
* <p>
* 与串行模式区别:串行使用 Map.putAll() 简单覆盖,并行支持高级合并策略,保证并发安全。
* </p>
*
* @param toolCalls LLM 下发的工具调用列表
* @param state Agent 全局状态
* @param config 运行时配置
* @return 所有工具执行后的合并状态
*/
private Map<String, Object> executeToolCallsParallel(List<AssistantMessage.ToolCall> toolCalls, OverAllState state,
RunnableConfig config) {
// ===================== 1. 并行模式兼容提示 =====================
// 并行模式下:忽略「同步转异步」配置,避免线程池饥饿/死锁(核心安全设计)
if (wrapSyncToolsAsAsync && logger.isDebugEnabled()) {
logger.debug("并行执行模式:自动忽略 wrapSyncToolsAsAsync,同步工具直接在并行线程中执行,防止线程池耗尽");
}
// 获取工具执行线程池(来自 ParallelNode 全局线程池)
Executor executor = getToolExecutor(config);
// ===================== 2. 线程安全:创建状态快照 =====================
// 浅拷贝状态,避免并行工具修改原始状态导致线程不安全
OverAllState stateSnapshot = state.snapShot().orElse(state);
// 工具状态收集器:支持按 KeyStrategy 并发安全合并工具结果
ToolStateCollector stateCollector = new ToolStateCollector(toolCalls.size(), state.keyStrategies());
// ===================== 3. 并发安全数据结构 =====================
// 原子引用数组:CAS 无锁更新工具执行结果,避免线程竞争
AtomicReferenceArray<ToolCallResponse> orderedResponses = new AtomicReferenceArray<>(toolCalls.size());
// 同步集合:收集执行异常
List<Throwable> failures = java.util.Collections.synchronizedList(new ArrayList<>());
// 并发Map:存储每个工具的取消令牌,支持超时/手动取消可取消异步工具
Map<Integer, DefaultCancellationToken> cancellationTokens = new ConcurrentHashMap<>();
// ===================== 4. 并发限流:信号量控制最大并行工具数 =====================
// 限制同时执行的工具数量,防止服务器压力过大
Semaphore semaphore = new Semaphore(maxParallelTools);
// ===================== 5. 构建并行任务流 =====================
List<CompletableFuture<Void>> futures = IntStream.range(0, toolCalls.size()).mapToObj(index -> {
// 获取当前工具
AssistantMessage.ToolCall toolCall = toolCalls.get(index);
// 为当前工具创建独立的状态更新Map
Map<String, Object> toolSpecificUpdate = stateCollector.createToolUpdateMap(index);
// 提交异步任务到线程池
return CompletableFuture.runAsync(() -> {
try {
// 获取信号量许可,限流并发
semaphore.acquire();
try {
// ===================== 6. 执行工具(带拦截器、状态注入、取消令牌) =====================
// 内部调用 executeToolByType → 执行同步/异步/可取消工具
ToolCallResponse response = executeToolCallWithInterceptors(
toolCall, stateSnapshot, config,
toolSpecificUpdate, true, cancellationTokens, index
);
// CAS 原子更新结果:仅当结果为空时写入(避免超时覆盖正常结果)
orderedResponses.compareAndSet(index, null, response);
} finally {
// 释放信号量
semaphore.release();
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
failures.add(e);
// 中断异常:写入错误结果
orderedResponses.compareAndSet(index, null,
ToolCallResponse.error(toolCall.id(), toolCall.name(), "工具执行被中断"));
}
}, executor)
// 全局超时控制
.orTimeout(toolExecutionTimeout.toMillis(), TimeUnit.MILLISECONDS)
// 异常/超时处理
.exceptionally(ex -> {
Throwable cause = ex instanceof CompletionException ? ex.getCause() : ex;
ToolCallResponse errorResponse = ToolCallResponse.error(toolCall.id(), toolCall.name(),
extractErrorMessage(cause));
// CAS 原子写入错误结果
if (orderedResponses.compareAndSet(index, null, errorResponse)) {
// 超时:丢弃脏状态 + 取消工具执行
if (cause instanceof TimeoutException) {
// 丢弃超时工具的状态更新,避免脏数据
stateCollector.discardToolUpdateMap(index);
// 触发协作式取消:通知可取消工具优雅停止
DefaultCancellationToken token = cancellationTokens.get(index);
if (token != null) {
token.cancel();
logger.debug("工具[{}]索引[{}]超时,已发送取消信号", toolCall.name(), index);
}
}
failures.add(ex);
}
return null;
});
}).toList();
// ===================== 7. 等待所有并行工具执行完成 =====================
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// ===================== 8. 组装执行结果 =====================
Map<String, Object> updatedState = new HashMap<>();
List<ToolResponseMessage.ToolResponse> toolResponses = new ArrayList<>();
Boolean returnDirect = null;
// 遍历原子数组,收集所有工具结果
for (int i = 0; i < orderedResponses.length(); i++) {
ToolCallResponse response = orderedResponses.get(i);
// 兜底:空结果自动构造错误响应
if (response == null) {
AssistantMessage.ToolCall toolCall = toolCalls.get(i);
response = ToolCallResponse.error(toolCall.id(), toolCall.name(), "工具未返回有效结果");
logger.warn("工具[{}]索引[{}]结果为空,使用兜底错误", toolCall.name(), i);
}
toolResponses.add(response.toToolResponse());
returnDirect = shouldReturnDirect(toolCalls.get(i), returnDirect, config);
}
// 构建消息体,写入Agent状态
ToolResponseMessage.Builder builder = ToolResponseMessage.builder().responses(toolResponses);
if (returnDirect != null && returnDirect) {
builder.metadata(Map.of(FINISH_REASON_METADATA_KEY, FINISH_REASON));
}
updatedState.put("messages", builder.build());
// 合并所有工具的状态更新(按 KeyStrategy 策略)
updatedState.putAll(stateCollector.mergeAll());
// 执行日志
if (enableActingLog) {
logger.info("[ThreadId {}] Agent {} 并行工具执行完成,总工具数:{},失败数:{}",
config.threadId().orElse(THREAD_ID_DEFAULT), agentName, toolCalls.size(), failures.size());
}
return updatedState;
}
2.2.2 两种异步工具
AgentToolNode#executeToolByType() 方法中,支持两种异步工具:
- 原生异步工具
- 同步包装为异步工具
执行逻辑:
java
/**
* 【工具执行路由分发器 - 核心方法】
* 根据工具回调的类型,自动路由到 同步/异步 执行逻辑
* 支持并行执行、取消令牌、同步工具自动包装为异步工具
*
* @param toolCallback 工具执行回调(异步/同步工具)
* @param request 工具调用请求
* @param toolContextMap 工具执行上下文
* @param config 运行时配置
* @param extraStateFromToolCall 状态更新收集Map
* @param inParallelExecution 是否为并行执行模式
* @param cancellationTokens 并行执行的取消令牌集合
* @param toolIndex 并行执行中工具的索引
* @return 工具执行响应结果
*/
private ToolCallResponse executeToolByType(ToolCallback toolCallback, ToolCallRequest request,
Map<String, Object> toolContextMap, RunnableConfig config, Map<String, Object> extraStateFromToolCall,
boolean inParallelExecution, Map<Integer, DefaultCancellationToken> cancellationTokens, int toolIndex) {
// ===================== 分支1:原生异步工具 =====================
// 如果工具本身就是异步回调(AsyncToolCallback),直接执行异步逻辑
if (toolCallback instanceof AsyncToolCallback async) {
return executeAsyncTool(async, request, toolContextMap, config, extraStateFromToolCall,
cancellationTokens, toolIndex);
}
// ===================== 分支2:同步工具 → 包装为异步工具 =====================
// 条件:开启了「同步转异步」配置 + 当前不是并行执行模式
else if (wrapSyncToolsAsAsync && !inParallelExecution) {
// 注释:仅在【串行模式】下将同步工具包装为异步
// 原因:并行模式外层已有异步调度,包装会导致线程池饥饿/死锁
Executor executor = getToolExecutor(config);
// 将同步工具 适配包装为 异步工具
AsyncToolCallback wrappedAsync = AsyncToolCallbackAdapter.wrapIfNeeded(
toolCallback, executor, toolExecutionTimeout);
// 执行包装后的异步工具
return executeAsyncTool(wrappedAsync, request, toolContextMap, config, extraStateFromToolCall,
cancellationTokens, toolIndex);
}
// ===================== 分支3:原生同步执行 =====================
// 其他所有场景:直接执行同步工具
else {
return executeSyncTool(toolCallback, request, toolContextMap, config);
}
}
2.1.3 原生异步执行
原生异步执行直接调用 executeAsyncTool 方法进行异步执行:
java
/**
* 【核心方法】执行异步工具(支持超时、协作式取消、并行任务管理)
* 1. 区分普通异步工具 / 可取消异步工具
* 2. 并行执行时管理取消令牌
* 3. 统一超时控制、异常捕获、结果封装
* 4. 支持工具执行异常的自定义处理器
*
* @param callback 异步工具回调对象
* @param request 工具调用请求
* @param toolContextMap 工具上下文参数
* @param config 运行时配置
* @param extraStateFromToolCall 工具执行的状态更新
* @param cancellationTokens 并行执行的取消令牌集合(可为null)
* @param toolIndex 并行工具索引(取消令牌的key)
* @return 工具执行响应
*/
private ToolCallResponse executeAsyncTool(AsyncToolCallback callback, ToolCallRequest request,
Map<String, Object> toolContextMap, RunnableConfig config,
Map<String, Object> extraStateFromToolCall,
Map<Integer, DefaultCancellationToken> cancellationTokens, int toolIndex) {
// 1. 构建工具执行上下文(封装传入的上下文参数)
ToolContext context = new ToolContext(toolContextMap);
// 声明取消令牌(仅可取消工具会初始化)
DefaultCancellationToken cancellationToken = null;
try {
CompletableFuture<String> future;
// ===================== 2. 路由执行:区分【可取消异步工具】和【普通异步工具】 =====================
// 如果工具实现了 CancellableAsyncToolCallback → 创建真实的取消令牌
if (callback instanceof CancellableAsyncToolCallback cancellable) {
// 创建取消令牌
cancellationToken = new DefaultCancellationToken();
// 并行执行模式:将令牌存入集合,供外层超时/取消逻辑调用
if (cancellationTokens != null && toolIndex >= 0) {
cancellationTokens.put(toolIndex, cancellationToken);
}
// 调用【支持取消】的异步方法
future = cancellable.callAsync(request.getArguments(), context, cancellationToken);
}
else {
// 普通异步工具:直接调用无取消令牌的方法
future = callback.callAsync(request.getArguments(), context);
}
// 异步任务为空 → 直接返回错误
if (future == null) {
return ToolCallResponse.error(request.getToolCallId(), request.getToolName(),
"Async tool returned null future");
}
// ===================== 3. 执行异步任务 + 超时控制 =====================
// 等待任务执行完成,超时时间使用工具自身配置的超时时间
String result = future.orTimeout(callback.getTimeout().toMillis(), TimeUnit.MILLISECONDS).join();
// ===================== 4. 执行成功:打印日志 + 返回成功结果 =====================
if (enableActingLog) {
logger.info("[ThreadId {}] Agent {} acting, async tool {} finished",
config.threadId().orElse(THREAD_ID_DEFAULT), agentName, request.getToolName());
if (logger.isDebugEnabled()) {
logger.debug("Tool {} returned: {}", request.getToolName(), result);
}
}
return ToolCallResponse.of(request.getToolCallId(), request.getToolName(), result);
}
// ===================== 5. 异常处理:异步执行异常包装 =====================
catch (CompletionException e) {
// 获取真实异常(剥离CompletableFuture包装层)
Throwable cause = e.getCause() != null ? e.getCause() : e;
// 异常1:超时异常
if (cause instanceof TimeoutException) {
// 超时 → 发送取消信号,让工具优雅停止
if (cancellationToken != null) {
cancellationToken.cancel();
}
// 清空脏状态:超时后丢弃所有状态更新
extraStateFromToolCall.clear();
logger.warn("Async tool {} timed out, discarding any state updates", request.getToolName());
return ToolCallResponse.error(request.getToolCallId(), request.getToolName(), extractErrorMessage(cause));
}
// 异常2:工具执行异常(走自定义异常处理器)
else if (cause instanceof ToolExecutionException toolExecutionException) {
logger.error("Async tool {} execution failed, handling with processor: {}", request.getToolName(),
toolExecutionExceptionProcessor.getClass().getName(), toolExecutionException);
// 调用异常处理器,返回友好提示
String result = toolExecutionExceptionProcessor.process(toolExecutionException);
return ToolCallResponse.of(request.getToolCallId(), request.getToolName(), result);
}
// 异常3:其他异步执行异常
else {
logger.error("Async tool {} execution failed: {}", request.getToolName(), cause.getMessage(), cause);
return ToolCallResponse.error(request.getToolCallId(), request.getToolName(), extractErrorMessage(cause));
}
}
// 异常6:任务被主动取消
catch (CancellationException e) {
logger.warn("Async tool {} execution was cancelled", request.getToolName(), e);
return ToolCallResponse.error(request.getToolCallId(), request.getToolName(), extractErrorMessage(e));
}
// 异常7:兜底所有未知异常
catch (Exception e) {
logger.error("Async tool {} execution failed: {}", request.getToolName(), e.getMessage(), e);
return ToolCallResponse.error(request.getToolCallId(), request.getToolName(), extractErrorMessage(e));
}
}
/**
* 【重载方法】执行异步工具(简化版,无并行取消令牌)
* 内部调用完整的 executeAsyncTool 方法,默认关闭并行模式
*/
private ToolCallResponse executeAsyncTool(AsyncToolCallback callback, ToolCallRequest request,
Map<String, Object> toolContextMap, RunnableConfig config,
Map<String, Object> extraStateFromToolCall) {
// 取消令牌集合=null,工具索引=-1 → 非并行执行
return executeAsyncTool(callback, request, toolContextMap, config, extraStateFromToolCall, null, -1);
}
2.1.4 包装异步执行
同时满足两个条件才会开启包装:
- 开启了【同步工具自动转异步】开关 (
wrapSyncToolsAsAsync=true) - 当前处于【串行执行模式】(非并行执行)
核心设计原因:
- 仅在【串行模式】下将同步工具转为异步:避免阻塞
Agent主线程,提升响应速度 - 【并行模式】禁止转换:外层已经通过
runAsync实现了并发,内层再包装异步会导致线程池耗尽(线程饥饿),甚至引发死锁!
处理逻辑:
java
else if (wrapSyncToolsAsAsync && !inParallelExecution) {
// Wrap sync tool as async for unified async execution
// Only in sequential mode - parallel mode already has concurrency from outer
// runAsync
// Wrapping in parallel mode would cause executor starvation (deadlock)
Executor executor = getToolExecutor(config);
AsyncToolCallback wrappedAsync = AsyncToolCallbackAdapter.wrapIfNeeded(toolCallback, executor,
toolExecutionTimeout);
return executeAsyncTool(wrappedAsync, request, toolContextMap, config, extraStateFromToolCall,
cancellationTokens, toolIndex);
}
会调用 ParallelNode 的静态方法获取线程池:
java
/**
* Get the executor for tool execution from config or use default.
*/
private Executor getToolExecutor(RunnableConfig config) {
return ParallelNode.getExecutor(config, AGENT_TOOL_NAME);
}
ParallelNode 是【并行执行节点】,可以并发执行多个节点/工具,支持结果聚合、并发控制、线程池管理。
其内部维护了一个默认线程池:
java
// ===================== 【核心】获取线程池(你之前调用的 getToolExecutor) =====================
/**
* 优先级:节点自定义线程池 → 全局默认并行线程池 → 框架内置线程池
*/
public static Executor getExecutor(RunnableConfig config, String nodeId) {
return config.metadata(nodeId)
.filter(Executor.class::isInstance)
.map(Executor.class::cast)
.orElseGet(() -> config.metadata(RunnableConfig.DEFAULT_PARALLEL_EXECUTOR_KEY)
.filter(Executor.class::isInstance)
.map(Executor.class::cast)
.orElse(DEFAULT_EXECUTOR));
}
// ===================== 【内置默认线程池】企业级优化线程池 =====================
/**
* 核心参数:
* 核心线程:CPU核心数*2(最低4)
* 最大线程:CPU核心数*4(最高200)
* 队列容量:1000
* 拒绝策略:调用者执行(不丢任务)
*/
private static final ExecutorService DEFAULT_EXECUTOR = new ThreadPoolExecutor(
calculateCorePoolSize(),
calculateMaximumPoolSize(),
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(calculateQueueCapacity()),
DEFAULT_THREAD_FACTORY,
new ThreadPoolExecutor.CallerRunsPolicy() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
logger.warn("Task rejected, execute in caller thread");
super.rejectedExecution(r, executor);
}
}
) {
// 任务执行前后/线程池终止 日志钩子
@Override
protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); }
@Override
protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); }
@Override
protected void terminated() { logger.info("ParallelNode thread pool terminated"); }
};