Spring AI Alibaba 1.x 系列【37】ReactAgent 构建、执行流程分析

文章目录

  • [1. 类结构概述](#1. 类结构概述)
    • [1.1 核心定义](#1.1 核心定义)
    • [1.2 核心架构组件](#1.2 核心架构组件)
    • [1.3 核心属性](#1.3 核心属性)
    • [1.4 内部类](#1.4 内部类)
      • [1.4.1 AgentSubGraphNode](#1.4.1 AgentSubGraphNode)
      • [1.4.2 AgentToSubCompiledGraphNodeAdapter](#1.4.2 AgentToSubCompiledGraphNodeAdapter)
  • [2. 构建流程](#2. 构建流程)
    • [2.1 Builder 构建器配置项](#2.1 Builder 构建器配置项)
    • [2.2 构造函数执行步骤](#2.2 构造函数执行步骤)
    • [2.3 核心初始化方法:initGraph(全流程)](#2.3 核心初始化方法:initGraph(全流程))
      • [2.3.1 初始化 Hooks & 验证唯一性](#2.3.1 初始化 Hooks & 验证唯一性)
      • [2.3.2 创建 StateGraph](#2.3.2 创建 StateGraph)
      • [2.3.3 添加 LLM、Tool 节点](#2.3.3 添加 LLM、Tool 节点)
      • [2.3.4 为 ToolInjection Hook 注入工具](#2.3.4 为 ToolInjection Hook 注入工具)
      • [2.3.5 按位置分类 Hooks](#2.3.5 按位置分类 Hooks)
      • [2.3.6 添加 Hook 节点](#2.3.6 添加 Hook 节点)
      • [2.3.7 确定节点流向](#2.3.7 确定节点流向)
      • [2.3.8 设置边和条件路由](#2.3.8 设置边和条件路由)
  • [3. 执行流程](#3. 执行流程)
    • [3.1 整体执行流程](#3.1 整体执行流程)
    • [3.2 同步执行](#3.2 同步执行)
      • [3.2.1 call](#3.2.1 call)
      • [3.2.2 invoke/invokeAndGetOutput](#3.2.2 invoke/invokeAndGetOutput)
    • [3.3 流式执行(stream/streamMessages)](#3.3 流式执行(stream/streamMessages))
    • [3.4 中断操作(Human-in-the-loop)](#3.4 中断操作(Human-in-the-loop))
    • [3.5 子图嵌入(asNode)](#3.5 子图嵌入(asNode))
    • [3.6 节点底层执行逻辑](#3.6 节点底层执行逻辑)

1. 类结构概述

1.1 核心定义

ReAct 模式是一种结合推理 (Reasoning) 和行动 (Acting) 的 AI Agent 执行范式,ReactAgent 则是ReAct 模式的核心实现类。是整个 Agent 框架的核心实现,理解其 ReAct 循环、Hook 机制、子图嵌入机制对于构建复杂的多 Agent 系统至关重要。

1.2 核心架构组件

  • AgentLlmNodeLLM 推理节点,负责消息处理和决策生成
  • AgentToolNode:工具执行节点,负责工具调用和结果处理
  • Hook:在 agent 执行的不同阶段 (beforeAgent/afterAgent/beforeModel/afterModel)注入自定义逻辑,实现横切关注点
  • ModelInterceptor/ToolInterceptor:拦截器机制,用于模型/工具调用的预处理和后处理

1.3 核心属性

java 复制代码
public class ReactAgent extends BaseAgent {

	/** 日志记录器 */
	Logger logger = LoggerFactory.getLogger(ReactAgent.class);

	/**
	 * 线程状态映射表,用于 human-in-the-loop 场景
	 *
	 * <p>存储每个线程的中断反馈状态,支持在 agent 执行过程中接收用户反馈。
	 * 使用 ConcurrentHashMap 保证线程安全:
	 * <ul>
	 *   <li>外层 Map: threadId → 状态 Map</li>
	 *   <li>内层 Map: 存储 INTERRUPTION_FEEDBACK_KEY 等状态键</li>
	 * </ul>
	 */
	private final ConcurrentMap<String, Map<String, Object>> threadIdStateMap;

	/** LLM 推理节点,负责消息处理和模型调用 */
	private final AgentLlmNode llmNode;

	/** 工具执行节点,负责工具调用和结果处理 */
	private final AgentToolNode toolNode;

	/** Hook 列表,在 agent 执行各阶段注入自定义逻辑 */
	private List<? extends Hook> hooks;

	/** 模型拦截器列表,用于模型调用的预处理和后处理 */
	private List<ModelInterceptor> modelInterceptors;

	/** 工具拦截器列表,用于工具调用的预处理和后处理 */
	private List<ToolInterceptor> toolInterceptors;

	/** Agent 指令/系统提示词 */
	private String instruction;

	/** 状态序列化器,用于状态的持久化和恢复 */
	private StateSerializer stateSerializer;

	/** 是否拥有工具的标志,决定是否需要工具执行循环 */
    private final Boolean hasTools;

1.4 内部类

1.4.1 AgentSubGraphNode

ReactAgent 适配为 StateGraph子图节点 (SubGraph Node)

java 复制代码
/**
 * 内部类:将 ReactAgent 适配为 StateGraph 的**子图节点 (SubGraph Node)**
 * 核心作用:让一个完整的 ReactAgent 工作流,作为**单个节点**嵌入到其他状态图中
 * 实现工作流模块化、嵌套执行能力
 */
private class AgentSubGraphNode extends Node implements SubGraphNode {

    // 子图对应的**编译后可执行实例**(CompiledGraph)
    private final CompiledGraph subGraph;

    /**
     * 构造方法:创建子图节点
     * @param id 子图节点的唯一ID
     * @param includeContents 子图嵌入时是否继承/包含父图的消息上下文
     * @param returnReasoningContents 是否返回子图的推理过程内容
     * @param subGraph 被包装的子图编译实例
     * @param instruction 子图执行的指令/提示词
     */
    public AgentSubGraphNode(String id, boolean includeContents, boolean returnReasoningContents, CompiledGraph subGraph, String instruction) {
        // 调用父类 Node 构造函数,绑定节点ID + 异步执行逻辑
        super(Objects.requireNonNull(id, "id cannot be null"),
              (config) -> node_async(new AgentToSubCompiledGraphNodeAdapter(
                      id,
                      includeContents,
                      returnReasoningContents,
                      subGraph,
                      instruction,
                      config
              )));
        // 初始化子图实例
        this.subGraph = subGraph;
    }

    /**
     * 实现 SubGraphNode 接口:获取子图原始定义(StateGraph)
     */
    @Override
    public StateGraph subGraph() {
        return subGraph.stateGraph;
    }

    /**
     * 实现 SubGraphNode 接口:获取子图的状态合并策略
     * 用于父子图之间的状态数据合并
     */
    @Override
    public Map<String, KeyStrategy> keyStrategies() {
        return subGraph.getKeyStrategyMap();
    }
}

1.4.2 AgentToSubCompiledGraphNodeAdapter

ReactAgent 子图嵌套执行的核心适配器,实现父图 ↔ 子图的状态传递、执行调度、消息隔离 / 继承、断点续跑能力。

java 复制代码
/**
 * 子图执行适配器
 * 功能:将 ReactAgent 封装为子图节点,实现父图与子图的状态交互、执行调度、消息处理
 * 实现:同步节点动作(带配置)+ 可恢复子图动作
 */
public class AgentToSubCompiledGraphNodeAdapter implements NodeActionWithConfig, ResumableSubGraphAction {

    // 子图节点唯一ID
    private String nodeId;
    // 是否继承父图的消息上下文
    private boolean includeContents;
    // 是否返回子图完整推理过程
    private boolean returnReasoningContents;
    // 子图执行指令
    private String instruction;
    // 子图编译后的可执行实例
    private CompiledGraph childGraph;
    // 父图编译配置
    private CompileConfig parentCompileConfig;

    /**
     * 构造方法:初始化子图适配器所有参数
     */
    public AgentToSubCompiledGraphNodeAdapter(String nodeId, boolean includeContents, boolean returnReasoningContents,
            CompiledGraph childGraph, String instruction, CompileConfig parentCompileConfig) {
        this.nodeId = nodeId;
        this.includeContents = includeContents;
        this.returnReasoningContents = returnReasoningContents;
        this.instruction = instruction;
        this.childGraph = childGraph;
        this.parentCompileConfig = parentCompileConfig;
    }

    /**
     * 实现 ResumableSubGraphAction 接口:获取子图恢复ID,用于断点续跑
     */
    @Override
    public String getResumeSubGraphId() {
        return resumeSubGraphId(nodeId);
    }

    /**
     * 核心执行方法:父图调用子图,完成状态传递、子图执行、结果回传
     * @param parentState 父图全局状态
     * @param config 运行时配置
     * @return 子图执行结果,回写到父图状态
     * @throws Exception 执行异常
     */
    @Override
    public Map<String, Object> apply(OverAllState parentState, RunnableConfig config) throws Exception {
        // 判断是否为子图恢复执行
        final boolean resumeSubgraph = config.metadata(resumeSubGraphId(nodeId), new TypeRef<Boolean>() {}).orElse(false);

        // 构建子图独立的运行配置
        RunnableConfig subGraphRunnableConfig = getSubGraphRunnableConfig(config);
        Flux<GraphResponse<NodeOutput>> subGraphResult;
        Object parentMessages = null;

        // 消息上下文处理:是否继承父图消息
        if (includeContents) {
            // 继承父图所有消息(上下文共享)
            Map<String, Object> stateForChild = new HashMap<>(parentState.data());
            List<Object> newMessages = stateForChild.get("messages") != null 
                ? new ArrayList<>((List<Object>)stateForChild.remove("messages")) 
                : new ArrayList<>();
            stateForChild.put("messages", newMessages);
            subGraphResult = childGraph.graphResponseStream(stateForChild, subGraphRunnableConfig);
        } else {
            // 隔离父图消息(子图独立上下文)
            Map<String, Object> stateForChild = new HashMap<>(parentState.data());
            parentMessages = stateForChild.remove("messages");
            subGraphResult = childGraph.graphResponseStream(stateForChild, subGraphRunnableConfig);
        }

        Map<String, Object> result = new HashMap<>();
        // 子图结果回写到父图的KEY(默认 messages 或自定义 outputKey)
        String outputKeyToParent = StringUtils.hasLength(ReactAgent.this.outputKey) ? ReactAgent.this.outputKey : "messages";
        result.put(outputKeyToParent, getGraphResponseFlux(parentState, subGraphResult, null));
        
        return result;
    }

    /**
     * 子图响应流式处理:滑动窗口缓冲,保证执行顺序
     */
    private @NotNull Flux<GraphResponse<NodeOutput>> getGraphResponseFlux(OverAllState parentState, Flux<GraphResponse<NodeOutput>> subGraphResult, AgentInstructionMessage instructionMessage) {
        // 滑动窗口缓冲,保证流式输出有序
        return subGraphResult
                .buffer(2, 1)
                .flatMap(window -> {
                    if (window.size() == 1) {
                        // 处理最后一个响应:过滤消息、精简结果
                        return Flux.just(processLastResponse(window.get(0), parentState, instructionMessage));
                    } else {
                        // 常规响应:直接透传
                        return Flux.just(window.get(0));
                    }
                }, 1); // 并发度1,严格保证顺序
    }

    /**
     * 处理子图最终响应:消息过滤、推理过程裁剪、父图消息隔离
     */
    private GraphResponse<NodeOutput> processLastResponse(GraphResponse<NodeOutput> lastResponse, OverAllState parentState, AgentInstructionMessage instructionMessage) {
        if (lastResponse == null || !lastResponse.resultValue().isPresent()) {
            return lastResponse;
        }

        Object resultValue = lastResponse.resultValue().get();
        if (resultValue instanceof Map<?,?> map && map.get("messages") instanceof List<?> messages) {
            // 移除子图中继承的父图消息,避免重复
            parentState.value("messages").ifPresent(parentMsgs -> {
                if (parentMsgs instanceof List<?> list) {
                    messages.removeAll(list);
                }
            });

            List<Object> finalMessages;
            // 根据配置决定是否返回完整推理过程
            if (returnReasoningContents) {
                finalMessages = new ArrayList<>(messages);
            } else {
                // 仅保留最后一条结果消息,精简输出
                finalMessages = messages.isEmpty() ? List.of() : List.of(messages.get(messages.size() - 1));
            }

            Map<String, Object> newResultMap = new HashMap<>((Map<String, Object>) resultValue);
            newResultMap.put("messages", finalMessages);
            return GraphResponse.done(newResultMap);
        }
        return lastResponse;
    }

    /**
     * 构建子图独立运行配置:隔离线程ID、检查点、元数据
     * 支持断点续跑,避免父子图检查点冲突
     */
    private RunnableConfig getSubGraphRunnableConfig(RunnableConfig config) {
        RunnableConfig subGraphRunnableConfig = RunnableConfig.builder(config)
                .checkPointId(null)
                .nextNode(null)
                .addMetadata("_AGENT_", subGraphId(nodeId))
                .build();
        subGraphRunnableConfig.clearContext();

        var parentSaver = parentCompileConfig.checkpointSaver();
        var subGraphSaver = childGraph.compileConfig.checkpointSaver();

        // 检查点持久化器校验:父子图必须使用同一个实例
        if (subGraphSaver.isPresent() && parentSaver.isEmpty()) {
            throw new IllegalStateException("Missing CheckpointSaver in parent graph!");
        }

        // 子图独立线程ID:父线程ID_子图ID,避免断点冲突
        if (subGraphSaver.isPresent() && parentSaver.get() == subGraphSaver.get()) {
            subGraphRunnableConfig = RunnableConfig.builder(config)
                    .threadId(config.threadId().map(threadId -> format("%s_%s", threadId, subGraphId(nodeId))).orElseGet(() -> subGraphId(nodeId)))
                    .nextNode(null)
                    .checkPointId(null)
                    .addMetadata("_AGENT_", subGraphId(nodeId))
                    .build();
            subGraphRunnableConfig.clearContext();
        }
        return subGraphRunnableConfig;
    }
}

2. 构建流程

2.1 Builder 构建器配置项

Builder 配置项完整列表:

配置项 类型 默认值 说明
name String 必填 Agent 名称
description String 必填 Agent 描述
chatModel ChatModel 必填 LLM 模型实例
toolCallbacks List<ToolCallback> 工具回调列表
instruction String null Agent 指令/提示词
hooks List<Hook> Hook 列表
modelInterceptors List<ModelInterceptor> 模型拦截器
toolInterceptors List<ToolInterceptor> 工具拦截器
outputKey String "output" 输出状态键
outputKeyStrategy KeyStrategy ReplaceStrategy 输出键更新策略
includeContents boolean true 子图嵌入时是否包含父图消息
returnReasoningContents boolean false 是否返回推理过程
compileConfig CompileConfig null 图编译配置
stateSerializer StateSerializer SpringAIJacksonStateSerializer 状态序列化器
inputSchema Schema null 输入 Schema
outputSchema Schema null 输出 Schema

2.2 构造函数执行步骤

执行以下初始化步骤:

  1. 调用父类 BaseAgent 构造函数,设置基础属性(namedescriptionoutputKey 等)
  2. 初始化线程状态映射表(用于 human-in-the-loop
  3. 设置核心节点(llmNodetoolNode)和配置(compileConfig
  4. 注入 HookInterceptor
  5. 设置状态序列化器(默认使用 Jackson 序列化器)
  6. 收集并合并拦截器(从 hooksbuilder 中收集)
  7. 将拦截器注入到对应节点中
  8. 设置 hasTools 标志(决定是否需要工具执行循环)
java 复制代码
/**
 * ReactAgent 构造函数
 *
 * @param llmNode LLM 推理节点实例
 * @param toolNode 工具执行节点实例
 * @param compileConfig 图编译配置
 * @param builder Builder 实例,包含所有构建参数
 */
public ReactAgent(AgentLlmNode llmNode, AgentToolNode toolNode, CompileConfig compileConfig, Builder builder) {
    // 1. 调用父类构造方法,初始化Agent基础属性:名称、描述、内容包含策略、输出key等
    super(builder.name, builder.description, builder.includeContents, builder.returnReasoningContents, builder.outputKey, builder.outputKeyStrategy);
    
    // 2. 初始化线程状态并发Map,用于存储人类交互(human-in-the-loop)的线程状态
    this.threadIdStateMap = new ConcurrentHashMap<>();

    // 3. 从Builder中赋值核心配置与指令
    this.instruction = builder.instruction;
    // LLM核心推理节点,负责调用大模型生成思考与结果
    this.llmNode = llmNode;
    // 工具执行节点,负责调用外部工具
    this.toolNode = toolNode;
    // 执行图编译配置
    this.compileConfig = compileConfig;
    // 钩子函数集合
    this.hooks = builder.hooks;
    // 模型拦截器集合
    this.modelInterceptors = builder.modelInterceptors;
    // 工具拦截器集合
    this.toolInterceptors = builder.toolInterceptors;
    // 输入输出相关配置
    this.includeContents = builder.includeContents;
    this.inputSchema = builder.inputSchema;
    this.inputType = builder.inputType;
    this.outputSchema = builder.outputSchema;
    this.outputType = builder.outputType;

    // 4. 初始化状态序列化器:优先使用Builder传入的,无则使用默认Jackson序列化器
    // 默认使用SpringAI Jackson序列化器,兼容性与功能更优
    this.stateSerializer = Objects.requireNonNullElseGet(builder.stateSerializer, () -> new SpringAIJacksonStateSerializer(OverAllState::new));

    // 5. 赋值执行器配置
    this.executor = builder.executor;

    // 6. 收集并合并模型/工具拦截器:从Hooks和Builder中统一收集,避免拦截器丢失
    List<ModelInterceptor> mergedModelInterceptors = collectAndMergeModelInterceptors();
    List<ToolInterceptor> mergedToolInterceptors = collectAndMergeToolInterceptors();

    // 7. 将合并后的模型拦截器注入到LLM节点
    if (mergedModelInterceptors != null && !mergedModelInterceptors.isEmpty()) {
        this.llmNode.setModelInterceptors(mergedModelInterceptors);
    }
    // 将合并后的工具拦截器注入到工具节点
    if (mergedToolInterceptors != null && !mergedToolInterceptors.isEmpty()) {
        this.toolNode.setToolInterceptors(mergedToolInterceptors);
    }

    // 8. 设置工具标记:判断是否存在可用工具,决定Agent是否需要执行工具调用循环
    hasTools = toolNode.getToolCallbacks() != null && !toolNode.getToolCallbacks().isEmpty();
}

2.3 核心初始化方法:initGraph(全流程)

ReactAgent 的核心实现,构建完整的 ReAct 循环状态图。

图结构示意

复制代码
START → beforeAgent Hooks → beforeModel Hooks → LLM Node
  ↑                                               ↓
  │                                        afterModel Hooks
  │                                               ↓
Tool Node ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←┤
  ↓                                               ↓
afterAgent Hooks ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←← END

入口源码

java 复制代码
	/**
	 * 初始化并构建 ReactAgent 的状态图 (核心方法)
	 *
	 * @return 构建完成的 StateGraph 实例
	 * @throws GraphStateException 如果图构建过程中发生错误
	 */
	@Override
	protected StateGraph initGraph() throws GraphStateException {

		// 步骤 1: 初始化 hooks 列表,确保不为 null
		if (hooks == null) {
			hooks = new ArrayList<>();
		}

		// 步骤 2: 注入默认 InstructionAgentHook (处理 instruction 指令)
		// 该 hook 在 beforeAgent 阶段将 instruction 注入到消息列表
		List<Hook> effectiveHooks = new ArrayList<>();
		effectiveHooks.add(InstructionAgentHook.create());
		effectiveHooks.addAll(hooks);

		// 步骤 3: 验证 hook 唯一性,避免重复执行
		Set<String> hookNames = new HashSet<>();
		for (Hook hook : effectiveHooks) {
			if (!hookNames.add(Hook.getFullHookName(hook))) {
				throw new IllegalArgumentException("Duplicate hook instances found");
			}

			// 为每个 hook 设置 agent 名称和引用,便于 hook 内部访问 agent 状态
			hook.setAgentName(this.name);
			hook.setAgent(this);
		}

		// 步骤 4: 创建 StateGraph,使用自定义 KeyStrategyFactory 和 StateSerializer
		StateGraph graph = new StateGraph(name, buildMessagesKeyStrategyFactory(effectiveHooks), stateSerializer);

		// 步骤 5: 添加核心节点 - LLM 推理节点和工具执行节点
		graph.addNode(AGENT_MODEL_NAME, node_async(this.llmNode));
		if (hasTools) {
			graph.addNode(AGENT_TOOL_NAME, node_async(this.toolNode));
		}

		// 步骤 6: 为需要工具的 hooks 注入工具 (实现 ToolInjection 接口的 hooks)
		setupToolsForHooks(effectiveHooks, toolNode);

		// 步骤 7: 按 HookPosition 分类 hooks,便于后续按位置添加节点和边
		List<Hook> beforeAgentHooks = filterHooksByPosition(effectiveHooks, HookPosition.BEFORE_AGENT);
		List<Hook> afterAgentHooks = filterHooksByPosition(effectiveHooks, HookPosition.AFTER_AGENT);
		List<Hook> beforeModelHooks = filterHooksByPosition(effectiveHooks, HookPosition.BEFORE_MODEL);
		List<Hook> afterModelHooks = filterHooksByPosition(effectiveHooks, HookPosition.AFTER_MODEL);

		// 步骤 8: 为 beforeAgent hooks 添加图节点
		// 支持 AgentHook (直接操作 state) 和 MessagesAgentHook (操作消息列表) 两种类型
		for (Hook hook : beforeAgentHooks) {
			if (hook instanceof AgentHook agentHook) {
				graph.addNode(Hook.getFullHookName(hook) + ".before", agentHook::beforeAgent);
			} else if (hook instanceof MessagesAgentHook messagesAgentHook) {
				graph.addNode(Hook.getFullHookName(hook) + ".before", MessagesAgentHook.beforeAgentAction(messagesAgentHook));
			}
		}

		// 步骤 9: 为 afterAgent hooks 添加图节点
		for (Hook hook : afterAgentHooks) {
			if (hook instanceof AgentHook agentHook) {
				graph.addNode(Hook.getFullHookName(hook) + ".after", agentHook::afterAgent);
			} else if (hook instanceof MessagesAgentHook messagesAgentHook) {
				graph.addNode(Hook.getFullHookName(hook) + ".after", MessagesAgentHook.afterAgentAction(messagesAgentHook));
			}
		}

		// 步骤 10: 为 beforeModel hooks 添加图节点
		// 特殊处理 InterruptionHook (它本身是 NodeActionWithConfig)
		for (Hook hook : beforeModelHooks) {
			if (hook instanceof ModelHook modelHook) {
				if (hook instanceof InterruptionHook interruptionHook) {
					graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", interruptionHook);
				} else {
					graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", modelHook::beforeModel);
				}
			} else if (hook instanceof MessagesModelHook messagesModelHook) {
				graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", MessagesModelHook.beforeModelAction(messagesModelHook));
			}
		}

		// 步骤 11: 为 afterModel hooks 添加图节点
		// 特殊处理 HumanInTheLoopHook (它本身是 NodeActionWithConfig)
		for (Hook hook : afterModelHooks) {
			if (hook instanceof ModelHook modelHook) {
				if (hook instanceof HumanInTheLoopHook humanInTheLoopHook) {
					graph.addNode(Hook.getFullHookName(hook) + ".afterModel", humanInTheLoopHook);
				} else {
					graph.addNode(Hook.getFullHookName(hook) + ".afterModel", modelHook::afterModel);
				}
			} else if (hook instanceof MessagesModelHook messagesModelHook) {
				graph.addNode(Hook.getFullHookName(hook) + ".afterModel", MessagesModelHook.afterModelAction(messagesModelHook));
			}
		}

		// 步骤 12: 确定节点流向 - 计算关键节点的名称
		String entryNode = determineEntryNode(beforeAgentHooks, beforeModelHooks);
		String loopEntryNode = determineLoopEntryNode(beforeModelHooks);
		String loopExitNode = determineLoopExitNode(afterModelHooks);
		String exitNode = determineExitNode(afterAgentHooks);

		// 步骤 13: 设置边和条件路由 - 连接所有节点
		graph.addEdge(START, entryNode);
		setupHookEdges(graph, beforeAgentHooks, afterAgentHooks, beforeModelHooks, afterModelHooks,
				entryNode, loopEntryNode, loopExitNode, exitNode, this);
		return graph;
	}

2.3.1 初始化 Hooks & 验证唯一性

代码核心功能:

  1. 空值防护 :处理 hooksnull 的情况,初始化空集合
  2. 默认 Hook 注入 :强制添加 InstructionAgentHook,保证指令逻辑生效
  3. 唯一性校验 :通过 HashSet 校验 Hook 全名称,杜绝重复实例
  4. 上下文绑定 :为所有 Hook 设置所属 Agent 的名称和实例,建立关联
java 复制代码
// 若传入的 hooks 集合为 null,初始化空列表避免空指针
if (hooks == null) {
    hooks = new ArrayList<>();
}

// 始终注入默认的 InstructionAgentHook,确保 Agent 指令在 beforeAgent 阶段被处理
List<Hook> effectiveHooks = new ArrayList<>();
effectiveHooks.add(InstructionAgentHook.create());
effectiveHooks.addAll(hooks);

// 校验 Hook 唯一性,防止重复注册
Set<String> hookNames = new HashSet<>();
for (Hook hook : effectiveHooks) {
    // 通过 Hook 全名称判断是否重复,重复则抛出异常
    if (!hookNames.add(Hook.getFullHookName(hook))) {
        throw new IllegalArgumentException("Duplicate hook instances found");
    }

    // 为每个 Hook 绑定所属的 Agent 名称与实例
    hook.setAgentName(this.name);
    hook.setAgent(this);
}

2.3.2 创建 StateGraph

使用状态序列化器创建 StateGraph 工作流实例:

java 复制代码
// 传入:Agent名称、键策略工厂、状态序列化器
StateGraph graph = new StateGraph(name, buildMessagesKeyStrategyFactory(effectiveHooks), stateSerializer);

构建 State 键策略工厂:

java 复制代码
**
 * 构建 State 键策略工厂,统一管理全局状态的合并规则
 * @param hooks 钩子列表,可自定义扩展键策略
 * @return KeyStrategyFactory 键策略工厂
 */
private KeyStrategyFactory buildMessagesKeyStrategyFactory(List<? extends Hook> hooks) {
    // 返回 Lambda 表达式形式的 KeyStrategyFactory
    return () -> {
        // 存储所有状态键对应的合并策略
        HashMap<String, KeyStrategy> keyStrategyHashMap = new HashMap<>();

        // 1. 配置输出键的策略(outputKey)
        if (outputKey != null && !outputKey.isEmpty()) {
            keyStrategyHashMap.put(outputKey, 
                outputKeyStrategy == null ? new ReplaceStrategy() : outputKeyStrategy);
        }

        // 2. 固定配置:messages 消息列表使用【追加策略 AppendStrategy】
        // 保证多轮对话消息不会被覆盖,持续累加
        keyStrategyHashMap.put("messages", new AppendStrategy());

        // 3. 集成所有 Hook 自定义的键策略
        if (hooks != null) {
            for (Hook hook : hooks) {
                // 获取单个 Hook 定义的键策略
                Map<String, KeyStrategy> hookStrategies = hook.getKeyStrategys();
                // 非空则合并到全局策略中
                if (hookStrategies != null && !hookStrategies.isEmpty()) {
                    keyStrategyHashMap.putAll(hookStrategies);
                }
            }
        }

        return keyStrategyHashMap;
    };
}

2.3.3 添加 LLM、Tool 节点

StateGraph 中添加 LLMTool 核心节点

java 复制代码
// 1. 向 StateGraph 中添加 LLM 核心节点
// 节点名称:AGENT_MODEL_NAME,将同步 llmNode 包装为异步节点
graph.addNode(AGENT_MODEL_NAME, node_async(this.llmNode));

// 2. 如果当前 Agent 包含工具,添加 Tool 执行节点
if (hasTools) {
    graph.addNode(AGENT_TOOL_NAME, node_async(this.toolNode));
}

2.3.4 为 ToolInjection Hook 注入工具

为实现了 ToolInjection 接口的钩子初始化并注入工具方法:

java 复制代码
	/**
	 * 为实现ToolInjection接口的钩子初始化并注入工具
	 * 仅注入与钩子要求的工具名称/类型相匹配的工具
	 *
	 * @param hooks 钩子列表
	 * @param toolNode 承载可用工具的代理工具节点
	 */
	private void setupToolsForHooks(List<? extends Hook> hooks, AgentToolNode toolNode) {
		// 校验参数:钩子集合或工具节点为空,直接退出
		if (hooks == null || hooks.isEmpty() || toolNode == null) {
			return;
		}

		// 获取工具节点中所有可用的工具回调
		List<ToolCallback> availableTools = toolNode.getToolCallbacks();
		// 无可用工具时直接退出
		if (availableTools == null || availableTools.isEmpty()) {
			return;
		}

		// 遍历所有钩子,匹配并注入工具
		for (Hook hook : hooks) {
			// 仅处理支持工具注入的钩子(实现ToolInjection接口)
			if (hook instanceof ToolInjection toolInjection) {
				// 根据钩子配置查找匹配的工具
				ToolCallback toolToInject = findToolForHook(toolInjection, availableTools);
				// 找到匹配工具后,执行注入逻辑
				if (toolToInject != null) {
					toolInjection.injectTool(toolToInject);
				}
			}
		}
	}

根据钩子的需求查找匹配的工具方法:

java 复制代码
	/**
	 * 根据钩子的需求查找匹配的工具
	 * 匹配优先级:1) 按名称匹配 2) 按类型匹配 3) 返回第一个可用工具
	 *
	 * @param toolInjection 需要注入工具的钩子对象
	 * @param availableTools 所有可用的工具回调集合
	 * @return 匹配成功的工具,未找到则返回null
	 */
	private ToolCallback findToolForHook(ToolInjection toolInjection, List<ToolCallback> availableTools) {
		String requiredToolName = toolInjection.getRequiredToolName();
		Class<? extends ToolCallback> requiredToolType = toolInjection.getRequiredToolType();

		// 第一优先级:按工具名称精准匹配
		if (requiredToolName != null) {
			for (ToolCallback tool : availableTools) {
				String toolName = tool.getToolDefinition().name();
				if (requiredToolName.equals(toolName)) {
					return tool;
				}
			}
		}

		// 第二优先级:按工具类型匹配
		if (requiredToolType != null) {
			for (ToolCallback tool : availableTools) {
				if (requiredToolType.isInstance(tool)) {
					return tool;
				}
			}
		}

		// 第三优先级:无指定名称/类型要求时,返回第一个可用工具
		if (requiredToolName == null && requiredToolType == null && !availableTools.isEmpty()) {
			return availableTools.get(0);
		}

		// 未匹配到任何工具,返回null
		return null;
	}

2.3.5 按位置分类 Hooks

根据钩子的执行位置(基于@HookPositions注解)进行过滤分类的工具方法:

java 复制代码
/**
 * 根据钩子的执行位置(基于@HookPositions注解)进行过滤
 * 若钩子的getHookPositions()包含指定位置,则纳入结果
 * 实现Prioritized接口的钩子会按优先级排序
 * 未实现Prioritized接口的钩子保持原有顺序
 *
 * @param hooks 待过滤的钩子列表
 * @param position 用于过滤的目标执行位置
 * @return 匹配指定执行位置的钩子列表(已按优先级排序)
 */
private static List<Hook> filterHooksByPosition(List<? extends Hook> hooks, HookPosition position) {
	// 流式过滤:仅保留包含目标执行位置的钩子
	List<Hook> filtered = hooks.stream()
			.filter(hook -> {
				HookPosition[] positions = hook.getHookPositions();
				return Arrays.asList(positions).contains(position);
			})
			.collect(Collectors.toList());
	
	// 将钩子分为两类:实现优先级接口的、未实现优先级接口的
	List<Hook> prioritizedHooks = new ArrayList<>();
	List<Hook> nonPrioritizedHooks = new ArrayList<>();
	
	for (Hook hook : filtered) {
		if (hook instanceof Prioritized) {
			prioritizedHooks.add(hook);
		} else {
			nonPrioritizedHooks.add(hook);
		}
	}
	
	// 对优先级钩子按排序值升序排列
	prioritizedHooks.sort(Comparator.comparingInt(h -> ((Prioritized) h).getOrder()));
	
	// 合并结果:排序后的优先级钩子在前,普通钩子保持原顺序在后
	List<Hook> result = new ArrayList<>(prioritizedHooks);
	result.addAll(nonPrioritizedHooks);
	
	return result;
}

2.3.6 添加 Hook 节点

将四类生命周期 Hook 注册为 StateGraph 独立节点,实现 Agent 全流程的拦截、扩展、增强能力。

java 复制代码
// 1. 注册【beforeAgent】类型的 Hook 节点(Agent执行前)
for (Hook hook : beforeAgentHooks) {
    if (hook instanceof AgentHook agentHook) {
        // 注册普通 AgentHook 的 beforeAgent 方法为节点
        graph.addNode(Hook.getFullHookName(hook) + ".before", agentHook::beforeAgent);
    } else if (hook instanceof MessagesAgentHook messagesAgentHook) {
        // 注册消息型 AgentHook 的 beforeAgent 方法为节点
        graph.addNode(Hook.getFullHookName(hook) + ".before", MessagesAgentHook.beforeAgentAction(messagesAgentHook));
    }
}

// 2. 注册【afterAgent】类型的 Hook 节点(Agent执行后)
for (Hook hook : afterAgentHooks) {
    if (hook instanceof AgentHook agentHook) {
        graph.addNode(Hook.getFullHookName(hook) + ".after", agentHook::afterAgent);
    } else if (hook instanceof MessagesAgentHook messagesAgentHook) {
        graph.addNode(Hook.getFullHookName(hook) + ".after", MessagesAgentHook.afterAgentAction(messagesAgentHook));
    }
}

// 3. 注册【beforeModel】类型的 Hook 节点(LLM模型调用前)
for (Hook hook : beforeModelHooks) {
    if (hook instanceof ModelHook modelHook) {
        // 特殊处理:中断型 Hook 直接注册
        if (hook instanceof InterruptionHook interruptionHook) {
            graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", interruptionHook);
        } else {
            // 普通 ModelHook 注册 beforeModel 方法
            graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", modelHook::beforeModel);
        }
    } else if (hook instanceof MessagesModelHook messagesModelHook) {
        // 消息型 ModelHook 注册对应节点
        graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", MessagesModelHook.beforeModelAction(messagesModelHook));
    }
}

// 4. 注册【afterModel】类型的 Hook 节点(LLM模型调用后)
for (Hook hook : afterModelHooks) {
    if (hook instanceof ModelHook modelHook) {
        // 特殊处理:人工介入型 Hook 直接注册
        if (hook instanceof HumanInTheLoopHook humanInTheLoopHook) {
            graph.addNode(Hook.getFullHookName(hook) + ".afterModel", humanInTheLoopHook);
        } else {
            // 普通 ModelHook 注册 afterModel 方法
            graph.addNode(Hook.getFullHookName(hook) + ".afterModel", modelHook::afterModel);
        }
    } else if (hook instanceof MessagesModelHook messagesModelHook) {
        // 消息型 ModelHook 注册对应节点
        graph.addNode(Hook.getFullHookName(hook) + ".afterModel", MessagesModelHook.afterModelAction(messagesModelHook));
    }
}

2.3.7 确定节点流向

根据是否存在 Agent 钩子与 Model 钩子,分别动态计算出状态图的流程入口、循环入口、循环出口和流程出口节点名称。

流程入口节点
java 复制代码
/**
 * 确定状态图的【流程入口节点】
 * 优先级:Agent钩子 > Model钩子 > 默认代理模型节点
 *
 * @param agentHooks 代理端钩子列表
 * @param modelHooks 模型端钩子列表
 * @return 入口节点全名称
 */
private static String determineEntryNode(
		List<Hook> agentHooks,
		List<Hook> modelHooks) {

	// 优先使用第一个Agent钩子的前置节点作为入口
	if (!agentHooks.isEmpty()) {
		return Hook.getFullHookName(agentHooks.get(0)) + ".before";
	}
	// 无Agent钩子时,使用第一个Model钩子的模型前置节点
	else if (!modelHooks.isEmpty()) {
		return Hook.getFullHookName(modelHooks.get(0)) + ".beforeModel";
	}
	// 无任何钩子时,返回默认代理模型节点
	else {
		return AGENT_MODEL_NAME;
	}
}
流程出口节点
java 复制代码
/**
 * 确定状态图的【流程出口节点】
 * 优先使用最后一个Agent钩子的后置节点,无则使用状态图结束节点
 *
 * @param agentHooks 代理端钩子列表
 * @return 出口节点全名称
 */
private static String determineExitNode(
		List<Hook> agentHooks) {

	// 存在Agent钩子时,使用最后一个钩子的后置节点作为出口
	if (!agentHooks.isEmpty()) {
		return Hook.getFullHookName(agentHooks.get(agentHooks.size() - 1)) + ".after";
	}
	// 无Agent钩子时,返回状态图默认结束节点
	else {
		return StateGraph.END;
	}
}
循环入口节点
java 复制代码
/**
 * 确定状态图的【循环入口节点】
 * 仅依赖Model钩子,无钩子则使用默认代理模型节点
 *
 * @param modelHooks 模型端钩子列表
 * @return 循环入口节点全名称
 */
private static String determineLoopEntryNode(
		List<Hook> modelHooks) {

	// 使用第一个Model钩子的模型前置节点作为循环入口
	if (!modelHooks.isEmpty()) {
		return Hook.getFullHookName(modelHooks.get(0)) + ".beforeModel";
	}
	// 无Model钩子时,返回默认代理模型节点
	else {
		return AGENT_MODEL_NAME;
	}
}
循环出口节点
java 复制代码
/**
 * 确定状态图的【循环出口节点】
 * 仅依赖Model钩子,无钩子则使用默认代理模型节点
 *
 * @param modelHooks 模型端钩子列表
 * @return 循环出口节点全名称
 */
private static String determineLoopExitNode(
		List<Hook> modelHooks) {

	// 使用第一个Model钩子的模型后置节点作为循环出口
	if (!modelHooks.isEmpty()) {
		return Hook.getFullHookName(modelHooks.get(0)) + ".afterModel";
	}
	// 无Model钩子时,返回默认代理模型节点
	else {
		return AGENT_MODEL_NAME;
	}
}

2.3.8 设置边和条件路由

为状态图配置所有钩子的节点边连接,按顺序串联各类钩子节点,并根据工具配置完成最终的节点路由跳转。

java 复制代码
// 1. 建立工作流基础入口边:固定起始节点 START → 工作流入口节点
graph.addEdge(START, entryNode);

// 2. 编排全流程节点流转路径
// 自动连接所有 Hook 节点、LLM 节点、Tool 节点,构建完整的 ReAct 循环执行链路
setupHookEdges(graph, beforeAgentHooks, afterAgentHooks, beforeModelHooks, afterModelHooks,
        entryNode, loopEntryNode, loopExitNode, exitNode, this);

自动连接所有 Hook 节点、LLM 节点、Tool 节点,构建完整的 ReAct 循环执行链路:

java 复制代码
/**
 * 为状态图配置所有钩子的节点边连接
 * 按顺序串联各类钩子节点,并根据工具配置完成最终的节点路由跳转
 *
 * @param graph 状态图实例
 * @param beforeAgentHooks Agent执行前的钩子列表
 * @param afterAgentHooks Agent执行后的钩子列表
 * @param beforeModelHooks 模型调用前的钩子列表
 * @param afterModelHooks 模型调用后的钩子列表
 * @param entryNode 流程入口节点
 * @param loopEntryNode 循环入口节点
 * @param loopExitNode 循环出口节点
 * @param exitNode 流程出口节点
 * @param agentInstance 代理实例对象
 * @throws GraphStateException 状态图构建异常
 */
private static void setupHookEdges(
		StateGraph graph,
		List<Hook> beforeAgentHooks,
		List<Hook> afterAgentHooks,
		List<Hook> beforeModelHooks,
		List<Hook> afterModelHooks,
		String entryNode,
		String loopEntryNode,
		String loopExitNode,
		String exitNode,
		ReactAgent agentInstance) throws GraphStateException {

	// 串联 Agent前置 钩子节点
	chainHook(graph, beforeAgentHooks, ".before", loopEntryNode, loopEntryNode, exitNode);

	// 串联 Model前置 钩子节点
	chainHook(graph, beforeModelHooks, ".beforeModel", AGENT_MODEL_NAME, loopEntryNode, exitNode);

	// 非空时,逆序串联 Model后置 钩子节点
	if (!afterModelHooks.isEmpty()) {
		chainModelHookReverse(graph, afterModelHooks, ".afterModel", AGENT_MODEL_NAME, loopEntryNode, exitNode);
	}

	// 非空时,逆序串联 Agent后置 钩子节点
	if (!afterAgentHooks.isEmpty()) {
		chainAgentHookReverse(graph, afterAgentHooks, ".after", exitNode, loopEntryNode, exitNode);
	}

	// 代理包含工具时,配置工具路由逻辑
	if (agentInstance.hasTools) {
		setupToolRouting(graph, loopExitNode, loopEntryNode, exitNode, agentInstance);
	}
	// 无工具但存在Model后置钩子时,将循环出口直接连接到流程出口
	else if (!loopExitNode.equals(AGENT_MODEL_NAME)) {
		addHookEdge(graph, loopExitNode, exitNode, loopEntryNode, exitNode, afterModelHooks.get(afterModelHooks.size() - 1).canJumpTo());
	}
	// 无工具且无Model后置钩子时,循环出口直接连接默认流程出口
	else {
		graph.addEdge(loopExitNode, exitNode);
	}
}

3. 执行流程

3.1 整体执行流程

java 复制代码
START → beforeAgent Hooks → beforeModel Hooks → LLM Node → afterModel Hooks
    → (有工具调用?) → Tool Node → beforeModel Hooks → LLM Node → ...
    → (无工具调用?) → afterAgent Hooks → END

使用示例:

java 复制代码
// 创建一个ReAct智能代理实例
ReactAgent agent = ReactAgent.builder()
     // 设置代理名称,用于标识和调试
     .name("my-react-agent")
     // 代理的功能描述
     .description("A ReAct agent example for weather and information search")
     
     // 设置底层聊天模型,可以是GPT-3.5/4或其他LLM
     .chatModel(chatModel)  // chatModel需预先初始化配置
     
     // 配置代理可调用的工具集
     // 这里添加了天气查询和搜索引擎两个工具
     .toolCallbacks(List.of(
         weatherTool,    // 天气查询工具,需实现ToolCallback接口
         searchTool     // 网络搜索工具,需实现ToolCallback接口
     ))
     
     // 设置系统指令,定义代理的基础行为模式
     .instruction("你是一个智能助手,能够回答问题并使用工具获取实时信息")
     
     // 添加钩子函数,这里添加了人工干预钩子
     // 当代理不确定时,可以请求人类协助
     .hooks(List.of(
         new HumanInTheLoopHook()  // 实现人工审核/干预逻辑
     ))
    .build();

// 使用代理处理用户查询
// 示例场景:用户询问天气情况
AssistantMessage response = agent.call("今天天气怎么样?");

3.2 同步执行

3.2.1 call

提供 8 个重载的 call() 返回助手消息(AssistantMessage )方法,均为同步阻塞执行,用于外部快速调用 Agent,是最常用的入口方法集合:

java 复制代码
/**
 * 最简调用方式 - 字符串输入
 *
 * <p>将字符串转换为 UserMessage 并执行 agent,返回 AI 的回复。
 * 适用于简单的单次对话场景。
 *
 * @param message 用户输入的字符串消息
 * @return AI 生成的 AssistantMessage 回复
 * @throws GraphRunnerException 如果图执行过程中发生错误
 */
public AssistantMessage call(String message) throws GraphRunnerException {
    // 字符串消息,无配置 → 委托给核心执行方法
    return doMessageInvoke(message, null);
}

/**
 * 字符串输入 + 配置控制
 *
 * <p>允许通过 RunnableConfig 控制执行行为,如:
 * <ul>
 *   <li>threadId: 设置线程 ID,用于状态持久化</li>
 *   <li>metadata: 传递额外的元数据</li>
 *   <li>timeout: 设置执行超时时间</li>
 * </ul>
 *
 * @param message 用户输入的字符串消息
 * @param config 运行时配置,控制执行行为
 * @return AI 生成的 AssistantMessage 回复
 * @throws GraphRunnerException 如果图执行过程中发生错误
 */
public AssistantMessage call(String message, RunnableConfig config) throws GraphRunnerException {
    // 字符串消息 + 自定义执行配置 → 委托核心执行方法
    return doMessageInvoke(message, config);
}

/**
 * UserMessage 输入 - 支持复杂消息构造
 *
 * <p>UserMessage 可以包含:
 * <ul>
 *   <li>文本内容</li>
 *   <li>多媒体内容 (图片、文件等)</li>
 *   <li>元数据和注释</li>
 * </ul>
 *
 * @param message 构造好的 UserMessage 实例
 * @return AI 生成的 AssistantMessage 回复
 * @throws GraphRunnerException 如果图执行过程中发生错误
 */
public AssistantMessage call(UserMessage message) throws GraphRunnerException {
    // 复杂消息对象,无配置 → 委托核心执行方法
    return doMessageInvoke(message, null);
}

/**
 * UserMessage 输入 + 配置控制
 *
 * @param message 构造好的 UserMessage 实例
 * @param config 运行时配置,控制执行行为
 * @return AI 生成的 AssistantMessage 回复
 * @throws GraphRunnerException 如果图执行过程中发生错误
 */
public AssistantMessage call(UserMessage message, RunnableConfig config) throws GraphRunnerException {
    // 复杂消息对象 + 自定义执行配置 → 委托核心执行方法
    return doMessageInvoke(message, config);
}

/**
 * 消息列表输入 - 支持多轮对话
 *
 * <p>传入完整的消息历史,适用于:
 * <ul>
 *   <li>多轮对话场景</li>
 *   <li>需要保持上下文的对话</li>
 *   <li>恢复之前的对话状态</li>
 * </ul>
 *
 * @param messages 消息历史列表
 * @return AI 生成的 AssistantMessage 回复
 * @throws GraphRunnerException 如果图执行过程中发生错误
 */
public AssistantMessage call(List<Message> messages) throws GraphRunnerException {
    // 多轮对话消息列表,无配置 → 委托核心执行方法
    return doMessageInvoke(messages, null);
}

/**
 * 消息列表输入 + 配置控制
 *
 * @param messages 消息历史列表
 * @param config 运行时配置,控制执行行为
 * @return AI 生成的 AssistantMessage 回复
 * @throws GraphRunnerException 如果图执行过程中发生错误
 */
public AssistantMessage call(List<Message> messages, RunnableConfig config) throws GraphRunnerException {
    // 多轮对话消息列表 + 自定义执行配置 → 委托核心执行方法
    return doMessageInvoke(messages, config);
}

/**
 * 自定义Map参数输入 - 支持扩展输入
 *
 * <p>可传入自定义键值对参数,除固定保留键外,其他键会作为图状态传递
 * <p>保留关键字:messages、input,用于传递用户问题/输入
 * <p>其他自定义键:可用于提示词占位符、自定义状态值
 *
 * @param inputs 输入参数Map(保留关键字:messages、input)
 * @return AI 生成的 AssistantMessage 回复
 * @throws GraphRunnerException 图执行失败时抛出
 */
public AssistantMessage call(Map<String, Object> inputs) throws GraphRunnerException {
    // 自定义Map输入,无配置 → 委托核心执行方法
    return doMessageInvoke(inputs, null);
}

/**
 * 自定义Map参数输入 + 配置控制
 *
 * <p>可传入自定义键值对参数,除固定保留键外,其他键会作为图状态传递
 * <p>保留关键字:messages、input,用于传递用户问题/输入
 *
 * @param inputs 输入参数Map(保留关键字:messages、input)
 * @param config 运行时配置,控制执行行为
 * @return AI 生成的 AssistantMessage 回复
 * @throws GraphRunnerException 图执行失败时抛出
 */
public AssistantMessage call(Map<String, Object> inputs, RunnableConfig config) throws GraphRunnerException {
    // 自定义Map输入 + 执行配置 → 委托核心执行方法
    return doMessageInvoke(inputs, config);
}

均统一委托给 doMessageInvoke() 执行:

java 复制代码
/**
 * 通用对象类型消息执行入口
 * <p>接收任意类型消息(String/UserMessage/List<Message>),统一构建为Map输入
 * @param message 任意类型用户消息(字符串/单消息/消息列表)
 * @param config 执行配置(可为null)
 * @return 最终AI回复消息
 * @throws GraphRunnerException 执行图异常
 */
private AssistantMessage doMessageInvoke(Object message, RunnableConfig config) throws GraphRunnerException {
    // 1. 将任意类型消息统一构建为标准Map输入格式
    Map<String, Object> inputs = buildMessageInput(message);
    // 2. 调用底层真正执行方法,执行Agent流程
    // 3. 从执行结果中提取标准的AssistantMessage返回
    return extractAssistantMessage(doInvoke(inputs, config));
}

/**
 * 自定义Map类型消息执行入口
 * <p>直接接收已构建好的Map参数,跳过消息构建步骤,用于高级场景
 * @param inputs 自定义输入Map(包含messages/input等状态)
 * @param config 执行配置
 * @return 最终AI回复消息
 * @throws GraphRunnerException 执行图异常
 */
private AssistantMessage doMessageInvoke(Map<String, Object> inputs, RunnableConfig config) throws GraphRunnerException {
    // 直接执行Agent流程,并提取结果返回
    return extractAssistantMessage(doInvoke(inputs, config));
}

从全局状态中提取助手消息:

java 复制代码
/**
 * 从全局状态中提取助手消息
 * <p>支持两种提取策略:
 * 1. 若指定了outputKey,直接通过key获取对应助手消息
 * 2. 未指定key时,从messages状态列表中获取【最后一条】助手消息
 * @param state 全局状态对象(Optional包装,允许为空)
 * @return 提取到的AssistantMessage
 * @throws IllegalStateException 指定outputKey但状态中无对应值时抛出
 * @throws AgentException 未找到任何有效的AssistantMessage时抛出
 */
private AssistantMessage extractAssistantMessage(Optional<OverAllState> state) {
    // 策略1:配置了outputKey,直接通过指定key从状态中提取消息
    if (StringUtils.hasLength(outputKey)) {
        return state.flatMap(s -> s.value(outputKey))
                    // 将状态值强转为AssistantMessage类型
                    .map(msg -> (AssistantMessage) msg)
                    // 未找到指定key的消息,抛出非法状态异常
                    .orElseThrow(() -> new IllegalStateException("Output key " + outputKey + " not found in agent state"));
    }

    // 策略2:未配置outputKey,从messages列表中提取最后一条AssistantMessage
    // 消息转换时增加校验逻辑,避免类型转换异常
    return state.flatMap(s -> s.value("messages"))
                .stream()
                // 将状态中的messages对象强转为列表,并流式处理
                .flatMap(messageList -> ((List<?>) messageList).stream()
                        // 过滤出所有AssistantMessage类型的消息
                        .filter(msg -> msg instanceof AssistantMessage)
                        .map(msg -> (AssistantMessage) msg))
                // 取列表中【最后一条】助手消息(reduce((a,b)->b) 经典取最后一个元素用法)
                .reduce((first, second) -> second)
                // 未找到任何助手消息,抛出自定义异常
                .orElseThrow(() -> new AgentException("No AssistantMessage found in 'messages' state"));
}

3.2.2 invoke/invokeAndGetOutput

invoke()invokeAndGetOutput() 同步执行相关的能力继承自 Agent 抽象基类。

3.3 流式执行(stream/streamMessages)

stream()streamMessages() 流式执行相关的能力继承自 Agent 抽象基类。

3.4 中断操作(Human-in-the-loop)

提供三种重载方式的中断方法,支持清空线程状态、传入消息列表、传入文本字符串三种人在回路场景的操作。

java 复制代码
    /**
     * 清空指定线程的状态 - 仅保留 threadId
     * <p>用于在 human-in-the-loop(人在回路)场景中清空当前状态,不添加任何新消息。
     * @param config 运行配置,必须包含线程ID
     */
    public void interrupt(RunnableConfig config) {
        // 传入空消息列表,清空代理状态
        updateAgentState(List.of(), config);
    }

    /**
     * 添加中断反馈消息 - 消息列表形式
     * <p>在 human-in-the-loop 场景中,用户可以在 agent 执行过程中插入多条反馈消息。
     * @param messages 待插入的消息列表
     * @param config   运行配置,必须包含线程ID
     */
    public void interrupt(List<Message> messages, RunnableConfig config) {
        updateAgentState(messages, config);
    }

    /**
     * 添加中断反馈消息 - 字符串形式 (最常用)
     * <p>将字符串自动封装为 UserMessage 并添加到线程状态,简化调用方使用。
     * @param userMessage 用户输入的文本消息
     * @param config       运行配置,必须包含线程ID
     */
    public void interrupt(String userMessage, RunnableConfig config) {
        // 自动构建用户消息,调用核心更新方法
        updateAgentState(List.of(UserMessage.builder().text(userMessage).build()), config);
    }

    /**
     * 核心方法:使用中断反馈更新代理线程状态
     * 线程安全,可与 InterruptionHook 中的 apply() 方法并发调用
     *
     * 线程安全保证:
     * - threadIdStateMap 是 ConcurrentHashMap,保证并发访问安全
     * - computeIfAbsent 原子操作,确保内部Map创建的原子性
     * - 内部存储容器同样使用 ConcurrentHashMap,保证put操作安全
     *
     * 并发行为:
     * - 若在 apply() 处理反馈前调用:新值会被正常处理
     * - 若在 apply() 移除反馈后调用:新值会留存至下一次迭代
     * - 若与 apply() 并发调用:原子操作保证无数据丢失
     *
     * @param state  待更新的状态对象(空列表/消息列表/自定义状态)
     * @param config 运行配置,用于获取线程ID
     * @throws IllegalArgumentException 配置中未提供threadId时抛出
     */
    public void updateAgentState(Object state, RunnableConfig config) {
        // 从配置中获取线程ID,为空则直接抛出异常
        String threadId = config.threadId()
                .orElseThrow(() -> new IllegalArgumentException("threadId must be provided in RunnableConfig for interruption."));

        // 原子性获取/创建线程对应的状态容器(ConcurrentHashMap保证线程安全)
        Map<String, Object> stateStatus = threadIdStateMap.computeIfAbsent(threadId, k -> new ConcurrentHashMap<>());

        // 存储中断反馈状态,覆盖原有值
        stateStatus.put(INTERRUPTION_FEEDBACK_KEY, state);
    }
}

3.5 子图嵌入(asNode)

ReactAgent 转换为 Node,用于嵌入其他图作为子图。

java 复制代码
	/**
	 * 将 ReactAgent 转换为 Node,用于嵌入其他图作为子图
	 *
	 * <p>实现 {@link BaseAgent#asNode(boolean, boolean)} 抽象方法,
	 * 创建 {@link AgentSubGraphNode} 实例,将当前 agent 包装为可嵌入的子图节点。
	 *
	 * @param includeContents 是否包含父图的消息内容到子图
	 * @param returnReasoningContents 是否返回推理过程内容
	 * @return 包装后的 Node 实例,可用于添加到其他 StateGraph
	 */
	@Override
	public Node asNode(boolean includeContents, boolean returnReasoningContents) {
		if (this.compiledGraph == null) {
			this.compiledGraph = getAndCompileGraph();
		}
		return new AgentSubGraphNode(this.name, includeContents, returnReasoningContents, this.compiledGraph, this.instruction);
	}

3.6 节点底层执行逻辑

callstream 等方法中,实际调用的是 CompiledGraph 的执行方法:

java 复制代码
	public AssistantMessage call(String message) throws GraphRunnerException {
		return doMessageInvoke(message, null);
	}
	
	private AssistantMessage doMessageInvoke(Object message, RunnableConfig config) throws GraphRunnerException {
		Map<String, Object> inputs = buildMessageInput(message);
		return extractAssistantMessage(doInvoke(inputs, config));
	}
	
	protected Optional<OverAllState> doInvoke(Map<String, Object> input, RunnableConfig runnableConfig) {
		CompiledGraph compiledGraph = getAndCompileGraph();
		return compiledGraph.invoke(input, buildNonStreamConfig(runnableConfig));
	}

最终调用到 CompiledGraph #invoke() 方法,按照流程图依次执行,直到结束:

java 复制代码
START → beforeAgent Hooks → beforeModel Hooks → LLM Node → afterModel Hooks
    → (有工具调用?) → Tool Node → beforeModel Hooks → LLM Node → ...
    → (无工具调用?) → afterAgent Hooks → END
相关推荐
龙侠九重天1 小时前
Token是什么?深入理解计费与上下文窗口
人工智能·ai·大模型·llm·token
xiaotao1311 小时前
04-进阶方向: 01-计算机视觉(CV)——语义分割:FCN与U-Net
人工智能·计算机视觉·u-net·fcn
qq_283720051 小时前
2026 最新 Python+AI 零基础入门实战教程:从零搭建企业级人工智能项目
人工智能·python·#机器学习·#python #ai零基础·#大模型开发·#rag·#ai避坑
w_t_y_y1 小时前
AI工程化设计(五)Agent设计范式(1)ReAct
人工智能
Kevin_Kung1 小时前
A2A-多智能体协作的“高速公路”
人工智能
小花皮猪1 小时前
2026 SERP + LLM 训练数据采集指南(Bright Data MCP + Dify)
人工智能·爬虫·工作流·dify·serp
CoderJia程序员甲1 小时前
GitHub 热榜项目 - 日榜(2026-04-23)
人工智能·ai·大模型·github·ai教程
叹一曲当时只道是寻常1 小时前
memos-cli 安装与使用教程:将 Memos 笔记同步到本地并支持 AI 语义搜索
人工智能·笔记·golang
财经资讯数据_灵砚智能1 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(夜间-次晨)2026年4月22日
大数据·人工智能·python·信息可视化·自然语言处理