1. Hook 与 Interceptor 机制

Spring AI AlibabaAgent 开发中,原生的执行流程往往无法满足生产级场景的监控、安全、限流、容错等需求。所以,提供了 HookInterceptor 机制,执行流程的精细化控制,打造高可用、高安全、可观测的生产级 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 CodePython 实现。当工具返回结果过大时(超过 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 构造函数中,从 ModelHookAgentHook 钩子中收集工具拦截器,并与当前已配置的工具拦截器合并,并设置给 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

在这里插入图片描述

Logo

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

更多推荐