Spring AI Alibaba 1.x 系列【15】工具执行拦截器(ToolInterceptor)
1. Hook 与 Interceptor 机制
在 Spring AI Alibaba 的 Agent 开发中,原生的执行流程往往无法满足生产级场景的监控、安全、限流、容错等需求。所以,提供了 Hook 与 Interceptor 机制,执行流程的精细化控制,打造高可用、高安全、可观测的生产级 Agent 应用。
二者共同支撑四大核心能力:
- 监控:日志、埋点、性能统计
- 修改:转换提示词、工具选择、输出格式
- 控制:重试、回退、提前终止
- 强制执行:限流、安全护栏、
PII检测
这种设计模式类似于 AOP(面向切面编程),提供了横切关注点的处理能力。例如,在 ReactAgent 中,可在 Agent 执行的关键节点插入自定义逻辑:

2. Interceptor
Interceptor 是所有拦截器的顶级父接口:
public interface Interceptor {
/**
* 获取拦截器的唯一名称
* @return 拦截器名称,用于标识、日志、去重管理
*/
String getName();
}
2.1 ToolInterceptor
ToolInterceptor 是工具调用拦截器抽象类,继承自 Interceptor 接口,专门用于包装/增强 Agent 的工具调用流程。
核心能力:
- 修改工具调用请求参数
- 修改工具执行返回结果
- 实现重试、缓存、日志、监控、权限校验等横切逻辑
- 控制工具调用的执行流程
类定义:
package com.alibaba.cloud.ai.graph.agent.interceptor;
/**
* Tool interceptor that can wrap tool calls.
* Implementations can modify requests, responses, or add behavior like retry, caching, etc.
* 工具调用拦截器,用于包装工具调用,可修改请求/响应,添加重试、缓存等能力
*/
public abstract class ToolInterceptor implements Interceptor {
/**
* Wrap a tool call with custom logic.
* 包装工具调用,注入自定义逻辑
*
* Implementations can:
* 实现类可以:
* - Modify the request before calling the handler 调用处理器前修改请求
* - Call the handler multiple times (retry logic) 多次调用处理器(实现重试)
* - Modify the response after handler returns 处理器返回后修改响应
* - Add caching, logging, monitoring, etc. 添加缓存、日志、监控等
*
* @param request The tool call request 工具调用请求对象
* @param handler The next handler in the chain (or base handler) 调用链中的下一个处理器
* @return The tool call response 工具调用响应结果
*/
public abstract ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler);
}
实现类:
ToolRetryInterceptor:工具执行失败时自动重试,支持指数退避和抖动策略。ToolErrorInterceptor:最简单的错误处理拦截器,捕获所有异常并返回错误消息,而非抛出异常中断流程。ToolEmulatorInterceptor:用LLM模拟工具返回结果,用于测试和开发场景,不执行真实工具。LargeResultEvictionInterceptor:当工具返回结果过大时(超过Token限制),自动将结果保存到文件系统,并返回截断消息 + 文件路径指针。

2.2 ToolCallRequest
ToolCallRequest 是工具调用的核心请求封装对象,用于在 Agent、拦截器、工具执行器之间传递标准化的工具调用请求,包含工具名称、入参、调用上下文等信息。
所有属性均为 final 修饰,保证对象不可变性,线程安全:
| 变量名 | 类型 | 含义 | 作用 |
|---|---|---|---|
| toolName | String | 工具名称 | 标识要调用的目标工具 |
| arguments | String | 工具调用参数 | JSON 格式的工具入参字符串 |
| toolCallId | String | 工具调用唯一ID | 关联模型返回的工具调用标识,用于对话追踪 |
| context | Map<String, Object> | 调用上下文 | 存储会话、用户、配置等扩展信息(防御性拷贝) |
| executionContext | ToolCallExecutionContext | 执行上下文 | 工具执行的元信息(可选) |
private final String toolName;
private final String arguments;
private final String toolCallId;
private final Map<String, Object> context;
private final ToolCallExecutionContext executionContext;
构造函数:
public ToolCallRequest(String toolName, String arguments, String toolCallId, Map<String, Object> context,
ToolCallExecutionContext executionContext) {
this.toolName = toolName;
this.arguments = arguments;
this.toolCallId = toolCallId;
// Defensive copy to prevent external modification
this.context = context != null ? new HashMap<>(context) : new HashMap<>();
this.executionContext = executionContext;
}
/**
* Backward-compatible constructor.
* <p>
* {@code executionContext} is optional and can be injected via {@link Builder}.
*/
public ToolCallRequest(String toolName, String arguments, String toolCallId, Map<String, Object> context) {
this(toolName, arguments, toolCallId, context, null);
}
静态工具方法:
| 方法 | 返回值 | 功能 |
|---|---|---|
builder() |
Builder | 创建空构建器,链式构建对象 |
from(ToolCall) |
ToolCallRequest | 将 Spring AI 原生工具调用对象转换为当前对象 |
builder(ToolCallRequest) |
Builder | 基于现有请求对象克隆构建 |
示例 1 ,从原生 ToolCall 构建请求:
// 获取模型返回的工具调用
AssistantMessage.ToolCall toolCall = ...;
// 快速构建 ToolCallRequest
ToolCallRequest request = ToolCallRequest.from(toolCall);
示例 2,链式手动构建:
ToolCallRequest request = ToolCallRequest.builder()
.toolName("get_user_info")
.arguments("{\"userId\":\"123\"}")
.toolCallId("call_001")
.context(Map.of("thread_id", "session_1"))
.build();
2.3 ToolCallResponse
ToolCallResponse 是工具调用的标准响应封装对象,用于统一承载工具执行的返回结果、状态信息、元数据,是工具调用链路的核心出参。
所有属性均为 final 修饰,保证对象不可变性,线程安全:
| 变量名 | 类型 | 含义 | 作用 |
|---|---|---|---|
| result | String | 工具执行结果 | 工具返回的业务数据/错误信息 |
| toolName | String | 工具名称 | 与请求的工具名称一一对应 |
| toolCallId | String | 工具调用ID | 关联请求ID,用于上下文追踪 |
| status | String | 执行状态 | 标识执行成功/失败(如 success/error) |
| metadata | Map | 元数据 | 扩展信息(错误详情、耗时、自定义参数) |
构造方法:
public ToolCallResponse(String result, String toolName, String toolCallId) {
this(result, toolName, toolCallId, null, null);
}
public ToolCallResponse(String result, String toolName, String toolCallId, String status, Map<String, Object> metadata) {
this.result = result;
this.toolName = toolName;
this.toolCallId = toolCallId;
this.status = status;
this.metadata = metadata != null ? new HashMap<>(metadata) : Collections.emptyMap();
}
框架提供了便捷的静态方法,快速创建响应对象:
| 方法 | 功能 | 适用场景 |
|---|---|---|
of(toolCallId, toolName, result) |
创建成功响应 | 工具执行正常返回 |
error(toolCallId, toolName, 错误信息) |
创建错误响应 | 工具执行失败,自定义错误信息 |
error(toolCallId, toolName, 异常) |
从异常创建错误响应 | 捕获异常后生成标准化错误 |
转换为 Spring AI 原生 ToolResponse 对象方法:
public ToolResponseMessage.ToolResponse toToolResponse()
支持链式调用,灵活构建复杂响应对象:
ToolCallResponse response = ToolCallResponse.builder()
.content("执行成功")
.toolName("get_user_info")
.toolCallId("call_001")
.status("success")
.metadata(Map.of("cost", 100))
.build();
示例 1 ,创建成功响应:
ToolCallResponse response = ToolCallResponse.of("call_001", "get_user_info", "用户名称:张三");
示例 2 ,创建错误响应(文字信息):
ToolCallResponse response = ToolCallResponse.error("call_001", "get_user_info", "用户不存在");
示例 3 ,创建错误响应(异常捕获):
try {
// 工具执行逻辑
} catch (Exception e) {
ToolCallResponse response = ToolCallResponse.error("call_001", "get_user_info", e);
}
示例 4 ,转换为 Spring AI 原生对象
ToolResponseMessage.ToolResponse nativeResponse = response.toToolResponse();
2.4 ToolCallHandler
ToolCallHandler 是工具调用拦截器责任链的核心函数式接口,作为 ToolInterceptor 的关键参数,负责串联拦截器并执行工具调用的核心逻辑,是实现工具调用环绕增强的基础。
源码如下:
/**
* Handler interface for tool call interceptors.
* Implementations can wrap and modify tool calls.
* 工具调用拦截器的处理器接口,实现类可包装和修改工具调用
*/
@FunctionalInterface
public interface ToolCallHandler {
/**
* Execute a tool call request.
* 执行工具调用请求
*
* @param request The tool call request 工具调用请求对象
* @return The tool call response 工具调用响应结果
*/
ToolCallResponse call(ToolCallRequest request);
}
结合 ToolInterceptor 演示 ToolCallHandler 的标准用法:
/**
* 工具日志拦截器
*/
public class LogToolInterceptor extends ToolInterceptor {
@Override
public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
// 1. 前置增强:打印调用日志
System.out.println("工具调用开始:" + request.getToolName());
// 2. 核心:调用 handler.call() 执行后续逻辑(必须调用)
ToolCallResponse response = handler.call(request);
// 3. 后置增强:打印结果日志
System.out.println("工具调用结束:" + response.getResult());
// 4. 返回响应结果
return response;
}
}
3. ToolInterceptor 实现类
3.1 ToolRetryInterceptor
ToolRetryInterceptor(重试拦截器)在工具执行失败时自动重试,支持指数退避和抖动策略。
使用场景
- 网络不稳定的外部
API调用 - 临时性服务故障(如数据库连接超时)
- 需要容错的关键工具
核心配置参数:
ToolRetryInterceptor interceptor = ToolRetryInterceptor.builder()
.maxRetries(2) // 最大重试次数,默认 2
.initialDelay(1000) // 初始延迟(ms),默认 1000
.maxDelay(60000) // 最大延迟(ms),默认 60000
.backoffFactor(2.0) // 退避因子,默认 2.0(指数)
.jitter(true) // 随机抖动 ±25%,默认 true
.toolName("search_api") // 只对特定工具重试
.retryOn(IOException.class) // 只对特定异常重试
.onFailure(OnFailureBehavior.RETURN_MESSAGE) // 失败行为
.build();
执行逻辑:
@Override
public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
String toolName = request.getToolName();
// 1. 检查是否需要重试此工具
if (toolNames != null && !toolNames.contains(toolName)) {
return handler.call(request); // 不重试,直接执行
}
Exception lastException = null;
int attempt = 0;
// 2. 重试循环
while (attempt <= maxRetries) {
try {
return handler.call(request); // 尝试执行
} catch (Exception e) {
lastException = e;
// 3. 检查异常是否应该重试
if (!retryOn.test(e)) {
throw e; // 不符合重试条件,直接抛出
}
if (attempt == maxRetries) {
break; // 达到最大重试次数
}
// 4. 计算延迟(指数退避 + 抖动)
long delay = calculateDelay(attempt);
Thread.sleep(delay);
attempt++;
}
}
// 5. 处理最终失败
if (onFailure == OnFailureBehavior.RAISE) {
throw new RuntimeException("Tool call failed after retries", lastException);
} else {
// 返回错误消息作为工具响应
return ToolCallResponse.of(toolCallId, toolName,
"Tool call failed after " + (maxRetries + 1) + " attempts");
}
}
3.2 ToolErrorInterceptor
ToolErrorInterceptor(错误处理拦截器)最简单的错误处理拦截器,捕获所有异常并返回错误消息,而非抛出异常中断流程。
使用场景:
- 防止工具异常导致
Agent崩溃 - 让
LLM自行处理工具失败情况
执行逻辑:
public class ToolErrorInterceptor extends ToolInterceptor {
@Override
public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
try {
return handler.call(request); // 尝试执行
} catch (Exception e) {
// 捕获异常,返回错误消息作为工具响应
return ToolCallResponse.of(
request.getToolCallId(),
request.getToolName(),
"Tool failed: " + e.getMessage()
);
}
}
@Override
public String getName() {
return "ToolError";
}
}
3.3 LargeResultEvictionInterceptor
LargeResultEvictionInterceptor(大结果驱逐拦截器)参考 Anthropic Claude Code 的 Python 实现。当工具返回结果过大时(超过 Token 限制),自动将结果保存到文件系统,并返回截断消息 + 文件路径指针。
使用场景:
- 大型日志文件分析:工具返回大量日志
- 数据库查询结果:返回数万行数据
API响应过大:如搜索结果、列表数据- 防止
Token溢出:保持对话上下文可控
核心配置参数:
LargeResultEvictionInterceptor interceptor = LargeResultEvictionInterceptor.builder()
.toolTokenLimitBeforeEvict(20000) // Token 限制,默认 20000
.excludeFilesystemTools() // 排除文件系统工具
.excludeTool("list_files") // 单独排除某个工具
.backend(new LocalFilesystemBackend()) // 文件存储后端
.build();
执行逻辑:
@Override
public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
// 1. 执行工具获取结果
ToolCallResponse response = handler.call(request);
// 2. 判断是否需要驱逐
if (!shouldEvictResult(request.getToolName(), response.getResult())) {
return response; // 结果不大,直接返回
}
// 3. 处理大结果:保存到文件,返回截断消息
return processLargeResult(response, request.getToolCallId());
}
驱逐判断逻辑:
private boolean shouldEvictResult(String toolName, String result) {
// 功能未启用
if (toolTokenLimitBeforeEvict == null) return false;
// 工具被排除(如文件工具自己处理大内容)
if (excludedTools.contains(toolName)) return false;
// 结果为空
if (result == null || result.isEmpty()) return false;
// Token 估算:4 字符 ≈ 1 Token
return result.length() > 4 * toolTokenLimitBeforeEvict;
}
大结果处理流程:
private ToolCallResponse processLargeResult(ToolCallResponse response, String toolCallId) {
String content = response.getResult();
// 1. 生成文件路径(tool_call_id 安全化)
String sanitizedId = sanitizeToolCallId(toolCallId);
String filePath = LARGE_RESULTS_DIR + sanitizedId; // ./large_tool_results/
// 2. 写入文件系统
backend.write(filePath, content);
// 3. 提取前 10 行作为样本
String contentSample = extractContentSample(content);
// 4. 构建驱逐消息
String evictedMessage = String.format(TOO_LARGE_TOOL_MSG, toolCallId, filePath, contentSample);
// 5. 返回截断响应
return ToolCallResponse.builder()
.content(evictedMessage)
.toolName(response.getToolName())
.toolCallId(response.getToolCallId())
.status("evicted_to_filesystem")
.build();
}
驱逐消息格式:
private static final String TOO_LARGE_TOOL_MSG = """
Tool result too large, the result of this tool call %s was saved
in the filesystem at this path: %s
You can read the result from the filesystem by using the read_file tool,
but make sure to only read part of the result at a time.
Here are the first 10 lines of the result:
%s
""";
3.4 ToolEmulatorInterceptor
ToolEmulatorInterceptor(工具模拟拦截器)用 LLM 模拟工具返回结果,用于测试和开发场景,不执行真实工具。
使用场景:
- 测试
Agent逻辑:无需真实工具环境 - 模拟昂贵
API:避免调用付费服务 - 模拟危险操作:如支付、删除等
- 开发调试:快速迭代
Agent行为
核心配置参数:
ToolEmulatorInterceptor interceptor = ToolEmulatorInterceptor.builder()
.model(chatModel) // 用于模拟的 LLM(必需)
.emulateAllTools(true) // 默认 true:模拟所有工具
.addTool("expensive_api") // 只模拟指定工具
.addTool("payment_gateway")
.promptTemplate(customTemplate) // 自定义模拟提示词
.build();
执行逻辑:
@Override
public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
String toolName = request.getToolName();
// 1. 判断是否需要模拟
boolean shouldEmulate = emulateAll || toolsToEmulate.contains(toolName);
if (!shouldEmulate) {
return handler.call(request); // 不模拟,执行真实工具
}
// 2. 构建模拟提示词
String prompt = String.format(promptTemplate,
toolName,
"No description available",
request.getArguments());
// 3. 用 LLM 生成模拟结果
ChatResponse response = emulatorModel.call(new Prompt(new UserMessage(prompt)));
String emulatedResult = response.getResult().getOutput().getText();
// 4. 短路返回(不执行真实工具)
return ToolCallResponse.of(request.getToolCallId(), toolName, emulatedResult);
}
默认提示词模板:
private String promptTemplate = """
You are emulating a tool call for testing purposes.
Tool: %s
Description: %s
Arguments: %s
Generate a realistic response that this tool would return given these arguments.
Return ONLY the tool's output, no explanation or preamble.
Introduce variation into your responses.
""";
4. 生命周期
示例代码:
ToolErrorInterceptor toolErrorInterceptor = ToolErrorInterceptor.builder().build();
ReactAgent chatAgent = ReactAgent.builder()
.name("my-agent")
.model(zhiPuAiChatModel)
.methodTools(new WeatherTool())
.interceptors(toolErrorInterceptor)
.build();
String text = chatAgent.call("查询长沙的天气情况").getText();
System.out.println(text);
4.1 加载流程
ReactAgent.builder() 注册拦截器入口:
public Builder interceptors(List<? extends Interceptor> interceptors) {
Assert.notNull(interceptors, "interceptors cannot be null");
Assert.noNullElements(interceptors, "interceptors cannot contain null elements");
this.interceptors.addAll(interceptors);
return this;
}
public Builder interceptors(Interceptor... interceptors) {
Assert.notNull(interceptors, "interceptors cannot be null");
Assert.noNullElements(interceptors, "interceptors cannot contain null elements");
this.interceptors.addAll(List.of(interceptors));
return this;
}
DefaultBuilder # ReactAgent build() 会先将统一注册的拦截器集合,按照拦截器类型进行拆分归类:
/**
* 【拦截器分类核心方法】
* 将统一注册的拦截器集合,按照拦截器类型进行拆分归类:
* 1. ModelInterceptor:大模型交互拦截器(处理LLM请求/响应)
* 2. ToolInterceptor:工具调用拦截器(处理Agent工具执行)
* 拆分后分别存入独立的集合,便于后续按场景执行拦截逻辑
*/
protected void separateInterceptorsByType() {
// 1. 判断全局拦截器集合是否非空,空则无需拆分
if (CollectionUtils.isNotEmpty(interceptors)) {
// 2. 初始化 大模型交互拦截器 集合
modelInterceptors = new ArrayList<>();
// 3. 初始化 工具调用拦截器 集合
toolInterceptors = new ArrayList<>();
// 4. 遍历所有统一注册的拦截器,进行类型匹配拆分
for (Interceptor interceptor : interceptors) {
// 判断:当前拦截器是 大模型交互拦截器 → 加入modelInterceptors
if (interceptor instanceof ModelInterceptor) {
modelInterceptors.add((ModelInterceptor) interceptor);
}
// 判断:当前拦截器是 工具调用拦截器 → 加入toolInterceptors
// 注:使用if而非else if,支持一个拦截器同时实现两种接口
if (interceptor instanceof ToolInterceptor) {
toolInterceptors.add((ToolInterceptor) interceptor);
}
}
}
}
最后在 ReactAgent 构造函数中,从 ModelHook、AgentHook 钩子中收集工具拦截器,并与当前已配置的工具拦截器合并,并设置给 AgentToolNode :
// 1. 收集并合并拦截器 (从 hooks 和 builder 中收集)
List<ToolInterceptor> mergedToolInterceptors = collectAndMergeToolInterceptors();
// 2. 注入到 ToolNode
if (mergedToolInterceptors != null && !mergedToolInterceptors.isEmpty()) {
this.toolNode.setToolInterceptors(mergedToolInterceptors);
}
/**
* 收集并合并工具拦截器
* 从 ModelHook、AgentHook 钩子中收集工具拦截器,并与当前已配置的工具拦截器合并
* <p>
* 核心规则:
* 1. 优先级:ReactAgent 配置的拦截器 > 钩子中的拦截器
* 2. 去重机制:拦截器名称相同则跳过,保留高优先级的拦截器
* 3. 最终返回合并后的拦截器集合,无任何拦截器时返回 null
*
* @return 合并后的工具拦截器集合,无拦截器则返回 null
*/
private List<ToolInterceptor> collectAndMergeToolInterceptors() {
// 初始化最终合并结果集合
List<ToolInterceptor> result = new ArrayList<>();
// 用于记录已添加的拦截器名称,实现【名称去重】,避免重复拦截器
Set<String> addedNames = new HashSet<>();
// ===================== 第一步:优先添加当前配置的工具拦截器(最高优先级) =====================
if (this.toolInterceptors != null && !this.toolInterceptors.isEmpty()) {
for (ToolInterceptor interceptor : this.toolInterceptors) {
// 加入结果集
result.add(interceptor);
// 记录该拦截器名称,标记为已添加
addedNames.add(interceptor.getName());
}
}
// ===================== 第二步:收集钩子中的工具拦截器(低优先级) =====================
// 判断钩子集合是否存在且不为空
if (this.hooks != null && !this.hooks.isEmpty()) {
for (Hook hook : this.hooks) {
// 从当前钩子中获取工具拦截器列表
List<ToolInterceptor> hookInterceptors = hook.getToolInterceptors();
// 仅处理非空的拦截器列表
if (hookInterceptors != null && !hookInterceptors.isEmpty()) {
for (ToolInterceptor interceptor : hookInterceptors) {
String name = interceptor.getName();
// 去重判断:未添加过的拦截器才加入结果集
if (!addedNames.contains(name)) {
result.add(interceptor);
addedNames.add(name);
} else {
// 重名拦截器:打印日志,跳过(保留优先级更高的配置拦截器)
logger.info("Skipping tool interceptor '{}' from hook '{}' because an interceptor with the same name already exists in ReactAgent configuration", name, hook.getName());
}
}
}
}
}
// 最终结果:空集合返回null,否则返回合并后的列表
return result.isEmpty() ? null : result;
}
构建完成的 AgentToolNode 中封装了所有工具拦截器:

4.2 执行流程
执行逻辑:
private ToolCallResponse executeToolCallWithInterceptors(
AssistantMessage.ToolCall toolCall,
OverAllState state,
RunnableConfig config,
Map<String, Object> extraStateFromToolCall,
boolean inParallelExecution) {
// 步骤 1: 创建 ToolCallRequest
ToolCallRequest request = ToolCallRequest.builder()
.toolCall(toolCall) // 工具调用信息(id, name, arguments)
.context(config.metadata().orElse(new HashMap<>()))
.executionContext(new ToolCallExecutionContext(config, state))
.build();
// 步骤 2: 创建 baseHandler(实际执行工具)
ToolCallHandler baseHandler = req -> {
ToolCallback toolCallback = resolve(req.getToolName(), config);
// ... 执行实际工具调用
return executeToolByType(toolCallback, req, toolContextMap, config, ...);
};
// 步骤 3: 构建拦截器链(责任链模式)
ToolCallHandler chainedHandler = InterceptorChain.chainToolInterceptors(
toolInterceptors,
baseHandler
);
// 步骤 4: 执行链式调用
return chainedHandler.call(request);
}
执行流程:
┌──────────────────────────────────────────────────────────────────────────┐
│ ToolInterceptor 执行流程 │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ReactAgent.call() │
│ │ │
│ ↓ │
│ AgentToolNode.apply() │
│ │ │
│ ↓ │
│ executeToolCallsSequential/Parallel() │
│ │ │
│ ↓ │
│ executeToolCallWithInterceptors() │
│ │ │
│ ├─→ 构建 ToolCallRequest │
│ │ - toolName │
│ │ - arguments │
│ │ - toolCallId │
│ │ - executionContext (config, state) │
│ │ │
│ ├─→ 创建 baseHandler (实际工具执行逻辑) │
│ │ │
│ ├─→ InterceptorChain.chainToolInterceptors() │
│ │ │ │
│ │ ↓ 从后向前包装 │
│ │ interceptors[0] → interceptors[1] → ... → baseHandler │
│ │ │
│ └─→ chainedHandler.call(request) │
│ │ │
│ ↓ │
│ ┌────────────────────────────────────┐ │
│ │ ToolInterceptor.interceptToolCall() │ │
│ │ - 可修改 request │ │
│ │ - 可多次调用 handler (重试) │ │
│ │ - 可修改 response │ │
│ │ - 可添加缓存、日志、监控等 │ │
│ └────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ToolCallResponse │
│ │ │
│ ↓ │
│ ToolResponseMessage → 更新 State │
│ │
└──────────────────────────────────────────────────────────────────────────┘
4.2.1 构建 ToolCallRequest
封装工具调用所需的全部信息:工具信息、上下文、执行环境:
ToolCallRequest request = ToolCallRequest.builder()
.toolCall(toolCall)
.context(config.metadata().orElse(new HashMap<>()))
.executionContext(new ToolCallExecutionContext(config, state))
.build();

4.2.2 创建 ToolCallHandler
这里用了一个函数式 Lambda 写法,我们将其拆解为一个标准 Java 类:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* 【工具调用基础执行器】
* 独立类实现 ToolCallHandler 接口
* 负责:解析工具、上下文构建、状态注入、最终执行工具调用
* 完全替代原 Lambda 函数式写法
*/
public class DefaultToolCallHandler implements ToolCallHandler {
private static final Logger logger = LoggerFactory.getLogger(DefaultToolCallHandler.class);
// ===================== 依赖注入(构造器传入) =====================
private final ReactAgent agent;
private final OverAllState state;
private final RunnableConfig config;
private final Map<String, Object> extraStateFromToolCall;
private final boolean inParallelExecution;
private final Map<Integer, DefaultCancellationToken> cancellationTokens;
private final int toolIndex;
/**
* 构造器:传入所有执行所需的上下文依赖
*/
public DefaultToolCallHandler(ReactAgent agent,
OverAllState state,
RunnableConfig config,
Map<String, Object> extraStateFromToolCall,
boolean inParallelExecution,
Map<Integer, DefaultCancellationToken> cancellationTokens,
int toolIndex) {
this.agent = agent;
this.state = state;
this.config = config;
this.extraStateFromToolCall = extraStateFromToolCall;
this.inParallelExecution = inParallelExecution;
this.cancellationTokens = cancellationTokens;
this.toolIndex = toolIndex;
}
/**
* 【核心执行方法】
* 重写接口方法,执行完整的工具调用逻辑
*/
@Override
public ToolCallResponse call(ToolCallRequest req) {
// 1. 根据工具名称,解析获取对应的工具执行回调
ToolCallback toolCallback = agent.resolve(req.getToolName(), config);
// 2. 未找到对应工具,抛出异常
if (toolCallback == null) {
logger.warn(ReactAgent.POSSIBLE_LLM_TOOL_NAME_CHANGE_WARNING, req.getToolName());
throw new IllegalStateException("No ToolCallback found for tool name: " + req.getToolName());
}
// 3. 打印Agent执行日志
if (agent.isEnableActingLog()) {
logger.info("[ThreadId {}] Agent {} acting, executing tool {}.",
config.threadId().orElse(ReactAgent.THREAD_ID_DEFAULT),
agent.getAgentName(),
req.getToolName());
}
// 4. 构建工具执行上下文
Map<String, Object> toolContextMap = new HashMap<>(agent.getToolContext());
toolContextMap.putAll(req.getContext());
// 5. 为状态感知型工具注入 Agent 全局状态
if (toolCallback instanceof StateAwareToolCallback ||
toolCallback instanceof FunctionToolCallback<?, ?> ||
toolCallback instanceof MethodToolCallback) {
toolContextMap.putAll(Map.of(
ReactAgent.AGENT_STATE_CONTEXT_KEY, state,
ReactAgent.AGENT_CONFIG_CONTEXT_KEY, config,
ReactAgent.AGENT_STATE_FOR_UPDATE_CONTEXT_KEY, extraStateFromToolCall
));
}
// 6. 执行工具(同步/异步路由)
return agent.executeToolByType(
toolCallback,
req,
toolContextMap,
config,
extraStateFromToolCall,
inParallelExecution,
cancellationTokens,
toolIndex
);
}
}

4.2.3 构建拦截器链
将多个 ToolInterceptor 工具拦截器串联为一个统一的 ToolCallHandler 处理器。
执行顺序(嵌套包裹规则):第一个拦截器为最外层 → 第二个拦截器嵌套其中 → … → 最内层为基础工具执行器
实现方式:从后向前反向包裹,保证拦截器执行顺序与注册顺序一致。
import java.util.List;
/**
* 【工具拦截器链编排核心方法】
*
* @param interceptors 待编排的工具拦截器集合
* @param baseHandler 最终执行真实工具调用的基础处理器
* @return 编排完成的统一处理器;无拦截器时直接返回基础处理器
*/
public static ToolCallHandler chainToolInterceptors(
List<ToolInterceptor> interceptors,
ToolCallHandler baseHandler) {
// 1. 拦截器为空/空集合,直接返回基础处理器,无需编排
if (interceptors == null || interceptors.isEmpty()) {
return baseHandler;
}
// 2. 初始化当前处理器 = 基础工具执行器(最内层执行逻辑)
ToolCallHandler current = baseHandler;
// 3. 【核心逻辑】从最后一个拦截器开始,向前反向包裹
// 目的:保证第一个注册的拦截器,成为最外层的拦截器
for (int i = interceptors.size() - 1; i >= 0; i--) {
// 获取当前遍历到的拦截器
ToolInterceptor interceptor = interceptors.get(i);
// 记录下一个要执行的处理器(内层逻辑)
ToolCallHandler nextHandler = current;
// ===================== 关键改造 =====================
// 移除 Lambda,使用【传统匿名内部类】实现 ToolCallHandler 接口
current = new ToolCallHandler() {
@Override
public ToolCallResponse call(ToolCallRequest request) {
// 执行拦截器的拦截逻辑,并传递下一层处理器
return interceptor.interceptToolCall(request, nextHandler);
}
};
}
// 4. 返回最终包裹完成的拦截器链处理器(最外层)
return current;
}

4.2.4 进入拦截器
方法入口:
return chainedHandler.call(request);
在责任链中,先进入第一个拦截器,这里只有 ToolErrorInterceptor 所以直接执行后续拦截器链:
@Override
public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
try {
return handler.call(request);
} catch (Exception e) {
return ToolCallResponse.of(request.getToolCallId(), request.getToolName(),
"Tool failed: " + e.getMessage());
}
}
4.2.5 工具执行
进入到 baseHandler 方法中,进行工具调用并返回工具结果:
// ===================== 2. 创建基础执行处理器(真正执行工具的核心逻辑) =====================
ToolCallHandler baseHandler = req -> {
// 根据工具名称,解析获取对应的工具执行回调
ToolCallback toolCallback = resolve(req.getToolName(), config);
// 未找到对应工具回调,打印警告并抛出异常
if (toolCallback == null) {
logger.warn(POSSIBLE_LLM_TOOL_NAME_CHANGE_WARNING, req.getToolName());
throw new IllegalStateException("No ToolCallback found for tool name: " + req.getToolName());
}
// 打印Agent执行日志(配置开启时)
if (enableActingLog) {
logger.info("[ThreadId {}] Agent {} acting, executing tool {}.",
config.threadId().orElse(THREAD_ID_DEFAULT), agentName, req.getToolName());
}
// 构建工具执行上下文:基础上下文 + 请求上下文
Map<String, Object> toolContextMap = new HashMap<>(toolContext);
toolContextMap.putAll(req.getContext());
// ===================== 状态注入:为支持状态感知的工具注入全局状态 =====================
// 适配类型:状态感知工具、函数式工具、方法反射工具
if (toolCallback instanceof StateAwareToolCallback || toolCallback instanceof FunctionToolCallback<?, ?>
|| toolCallback instanceof MethodToolCallback) {
toolContextMap.putAll(Map.of(
AGENT_STATE_CONTEXT_KEY, state, // 全局状态
AGENT_CONFIG_CONTEXT_KEY, config, // 运行配置
AGENT_STATE_FOR_UPDATE_CONTEXT_KEY, extraStateFromToolCall // 待更新的状态
));
}
// ===================== 路由执行:根据回调类型,执行同步/异步工具 =====================
return executeToolByType(toolCallback, req, toolContextMap, config, extraStateFromToolCall,
inParallelExecution, cancellationTokens, toolIndex);
};
4.2.6 返回拦截器
工具执行后,责任链依次执行(有内到外)所有拦截器,也就是执行 handler.call 后面的处理逻辑:
// Execute the tool call
ToolCallResponse response = handler.call(request);
// .......
最终,返回 ToolCallResponse :

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)