Spring AI Alibaba 1.x 系列【16】异步工具核心组件与执行流程

文章目录

  • [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

AsyncToolCallbackSpring 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)。

核心作用

  • 无侵入改造:让原生同步工具(@ToolFunctionToolCallback等)直接支持异步执行
  • 并行执行:借助线程池(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"); }
	};
相关推荐
翼龙云_cloud2 小时前
阿里云代理商:详解阿里云部署OpenClaw 且接入Playwright指南
人工智能·阿里云·云计算·openclaw
AI、少年郎2 小时前
MiniMind 第 4 篇:《数据工程|Tokenizer 训练 + 预训练 / SFT/DPO 全数据集处理》
人工智能·python·ai·大模型·微调·大模型训练·minimind
马士兵教育2 小时前
AI工作岗位的就业分层?
开发语言·人工智能·学习·面试·职场和发展
leoZ2312 小时前
胡思乱想。。。
人工智能
南师大蒜阿熏呀2 小时前
AI助手分析清理电脑垃圾
人工智能
清 晨2 小时前
社媒引流不稳定跨境卖家如何建立长期流量池
大数据·人工智能·新媒体运营·跨境·营销策略
quetalangtaosha2 小时前
Anomaly Detection系列(CVPR2025 TAO论文解读)
人工智能·异常检测
pen-ai2 小时前
MAD(Median Absolute Deviation)详解:最稳健的尺度估计方法
人工智能·算法
t***5442 小时前
这些设计模式在现代C++中如何应用
java·c++·设计模式