1. 概述

Hook 生命周期:

  • 注册与初始化:Hook 通过 Builder 注册,在 ReactAgent 构造时被收集、去重、设置 Agent 信息,并按 HookPosition 分类排序后创建为图节点。

  • 执行流程:Agent 运行时,HookBEFORE_AGENTBEFORE_MODELAFTER_MODEL(反序) → AFTER_AGENT(反序) 的顺序执行,返回的 Map<String, Object> 使用 KeyStrategy 合并到状态。

Hook 是宏观扩展点,可访问 Agent 全局状态并提供 Interceptor(微观扩展点),Interceptor 再被注入到 LLM/Tool Node 中包装具体操作调用。

示例代码:

        ModelCallLimitHook modelCallLimitHook = ModelCallLimitHook.builder().runLimit(1).build();

        PIIDetectionHook piiDetectionHook = PIIDetectionHook.builder()
                .piiType(PIIType.IP)
                .strategy(RedactionStrategy.REDACT)
                .applyToInput(true)
                .build();

        ReactAgent myAgent = ReactAgent.builder()
                .name("my-agent")
                .model(zhiPuAiChatModel)
                .methodTools(new WeatherTool())
                .hooks(piiDetectionHook,modelCallLimitHook)
                .build();

2. 生命周期

注册、初始化、图构建时序图:

在这里插入图片描述
执行阶段时序图:
在这里插入图片描述

2.1 注册阶段

Builder 配置钩子后,会将所有 Hook 添加到 Builder#hooks 属性中:

	public Builder hooks(List<? extends Hook> hooks) {
		Assert.notNull(hooks, "hooks cannot be null");
		Assert.noNullElements(hooks, "hooks cannot contain null elements");
		this.hooks.addAll(hooks);
		return this;
	}

	public Builder hooks(Hook... hooks) {
		Assert.notNull(hooks, "hooks cannot be null");
		Assert.noNullElements(hooks, "hooks cannot contain null elements");
		this.hooks.addAll(List.of(hooks));
		return this;
	}

Builder 内部存储:

protected List<Hook> hooks = new ArrayList<>();
protected List<Interceptor> interceptors = new ArrayList<>();
protected List<ModelInterceptor> modelInterceptors = new ArrayList<>();
protected List<ToolInterceptor> toolInterceptors = new ArrayList<>();

2.2 初始化阶段

ReactAgent 构造函数实例化时,会将 Builder 中的所有钩子保存到成员变量:

public ReactAgent(AgentLlmNode llmNode, AgentToolNode toolNode, CompileConfig compileConfig, Builder builder) {
    // 1. 保存 hooks 到成员变量
    this.hooks = builder.hooks;
    
    // 2. 保存 interceptors
    this.modelInterceptors = builder.modelInterceptors;
    this.toolInterceptors = builder.toolInterceptors;
    
    // 3. 收集 Hook 提供的 Interceptor 并合并
    List<ModelInterceptor> mergedModelInterceptors = collectAndMergeModelInterceptors();
    List<ToolInterceptor> mergedToolInterceptors = collectAndMergeToolInterceptors();
    
    // 4. 注入到节点
    if (mergedModelInterceptors != null && !mergedModelInterceptors.isEmpty()) {
        this.llmNode.setModelInterceptors(mergedModelInterceptors);
    }
    if (mergedToolInterceptors != null && !mergedToolInterceptors.isEmpty()) {
        this.toolNode.setToolInterceptors(mergedToolInterceptors);
    }
}

并提取钩子后的 ModelInterceptorToolInterceptor

private List<ModelInterceptor> collectAndMergeModelInterceptors() {
    List<ModelInterceptor> result = new ArrayList<>();
    Set<String> addedNames = new HashSet<>();

    // 优先添加 Agent 配置的 interceptors
    if (this.modelInterceptors != null) {
        for (ModelInterceptor interceptor : this.modelInterceptors) {
            result.add(interceptor);
            addedNames.add(interceptor.getName());
        }
    }

    // 从 hooks 收集 interceptors (跳过已存在的名称)
    if (this.hooks != null) {
        for (Hook hook : this.hooks) {
            List<ModelInterceptor> hookInterceptors = hook.getModelInterceptors();
            for (ModelInterceptor interceptor : hookInterceptors) {
                if (!addedNames.contains(interceptor.getName())) {
                    result.add(interceptor);
                }
            }
        }
    }
    return result;
}

2.3 图构建阶段

初始化执行图时(第一次执行时懒加载):

  • 注入默认 InstructionAgentHook(指令注入钩子)
  • 验证 Hook 唯一性
  • Hook 设置 Agent 实例、名称属性
  • ToolInjection hooks 注入工具
  • HookPosition 分类
  • 为每个位置创建图节点
protected StateGraph initGraph() throws GraphStateException {
    
    // 步骤 1: 初始化 hooks 列表
    if (hooks == null) hooks = new ArrayList<>();
    
    // 步骤 2: 注入默认 InstructionAgentHook
    List<Hook> effectiveHooks = new ArrayList<>();
    effectiveHooks.add(InstructionAgentHook.create());  // 默认 Hook
    effectiveHooks.addAll(hooks);                       // 用户 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");
        }
        
        // 步骤 4: 为 Hook 设置 agent 信息 ← 关键!
        hook.setAgentName(this.name);
        hook.setAgent(this);  // Hook 可访问 Agent 实例
    }
    
    // 步骤 5: 创建 StateGraph
    StateGraph graph = new StateGraph(name, 
        buildMessagesKeyStrategyFactory(effectiveHooks), 
        stateSerializer);
    
    // 步骤 6: 为 ToolInjection hooks 注入工具
    setupToolsForHooks(effectiveHooks, toolNode);
    
    // 步骤 7: 按 HookPosition 分类
    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-11: 为每个位置创建图节点
    for (Hook hook : beforeAgentHooks) {
        graph.addNode(Hook.getFullHookName(hook) + ".before", agentHook::beforeAgent);
    }
    for (Hook hook : afterModelHooks) {
        graph.addNode(Hook.getFullHookName(hook) + ".afterModel", modelHook::afterModel);
    }
    // ... 其他位置同理
    
    // 步骤 12-13: 连接边和路由
    setupHookEdges(graph, beforeAgentHooks, afterAgentHooks, ...);
    
    return graph;
}

2.3.1 添加默认钩子

添加默认钩子 InstructionAgentHook 用于在 Agent 每次运行前将 ReactAgent 的系统指令注入消息列表。

		// Always inject default InstructionAgentHook so instruction is handled in beforeAgent
		List<Hook> effectiveHooks = new ArrayList<>();
		effectiveHooks.add(InstructionAgentHook.create());
		effectiveHooks.addAll(hooks);

默认钩子位置在最前:

在这里插入图片描述

2.3.2 名称唯一性校验

循环所有 Hook ,如果发现名称重复,则会抛出 IllegalArgumentException

		// Validate hook uniqueness
		Set<String> hookNames = new HashSet<>();
		for (Hook hook : effectiveHooks) {
			if (!hookNames.add(Hook.getFullHookName(hook))) {
				throw new IllegalArgumentException("Duplicate hook instances found");
			}

			// set agent name to every hook node.
			hook.setAgentName(this.name);
			hook.setAgent(this);
		}

Hook 接口中定义了获取名称的静态方法:

	static String getFullHookName(Hook hook) {
		return AGENT_HOOK_NAME_PREFIX + hook.getName();
	}

在这里插入图片描述

2.3.3 注入 Agent 信息

在名称唯一性校验通过后,会为每一个 Hook 配置 Agent 的名称和实例对象。

在这里插入图片描述

2.3.4 ToolInjection 设置并注入工具

为实现 ToolInjection 接口的钩子设置并注入工具,仅注入匹配钩子要求的工具(按名称 / 类型匹配)。

/**
 * 为实现ToolInjection接口的钩子初始化并注入工具
 * <p>
 * 遍历所有钩子,仅向实现了工具注入接口的钩子,注入匹配名称/类型的工具
 * </p>
 * @param hooks 钩子列表
 * @param toolNode 包含可用工具的Agent工具节点
 */
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) {
		if (hook instanceof ToolInjection toolInjection) {
			// 根据钩子要求查找匹配的工具
			ToolCallback toolToInject = findToolForHook(toolInjection, availableTools);
			// 找到匹配工具则执行注入
			if (toolToInject != null) {
				toolInjection.injectTool(toolToInject);
			}
		}
	}
}

根据钩子的需求查找匹配的工具匹配优先级:

  • 按工具名称匹配
  • 按工具类型匹配
  • 返回第一个可用工具(无指定要求时)
/**
 * 根据钩子的需求查找匹配的工具
 * <p>
 * 匹配优先级:
 * 1. 按工具名称精准匹配
 * 2. 按工具类型匹配
 * 3. 无指定要求时,返回第一个可用工具
 * </p>
 * @param toolInjection 需要注入工具的钩子实例
 * @param availableTools 所有可用的工具回调列表
 * @return 匹配成功的工具,未找到则返回null
 */
private ToolCallback findToolForHook(ToolInjection toolInjection, List<ToolCallback> availableTools) {
	// 获取钩子要求的工具名称和类型
	String requiredToolName = toolInjection.getRequiredToolName();
	Class<? extends ToolCallback> requiredToolType = toolInjection.getRequiredToolType();

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

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

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

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

2.3.5 按执行位置对钩子进行分类

按执行位置对钩子进行分类:

List<Hook> beforeAgentHooks = filterHooksByPosition(effectiveHooks, HookPosition.BEFORE_AGENT); // Agent执行前钩子集合
List<Hook> afterAgentHooks = filterHooksByPosition(effectiveHooks, HookPosition.AFTER_AGENT);   // Agent执行后钩子集合
List<Hook> beforeModelHooks = filterHooksByPosition(effectiveHooks, HookPosition.BEFORE_MODEL); // 模型调用前钩子集合
List<Hook> afterModelHooks = filterHooksByPosition(effectiveHooks, HookPosition.AFTER_MODEL);   // 模型调用后钩子集合

根据钩子执行位置过滤钩子列表:

/**
 * 根据钩子执行位置过滤钩子列表
 * <p>
 * 过滤规则:
 * 1. 钩子的执行位置包含指定位置时,会被纳入结果
 * 2. 实现Prioritized接口的钩子,会按优先级排序
 * 3. 未实现优先级接口的钩子,保持原有顺序
 * 4. 最终结果:排序后的优先级钩子在前,普通钩子在后
 * </p>
 * @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);
		}
	}
	
	// 第三步:对优先级钩子按 order 值升序排序(数值越小优先级越高)
	prioritizedHooks.sort(Comparator.comparingInt(h -> ((Prioritized) h).getOrder()));
	
	// 第四步:合并结果:优先级钩子在前,普通钩子保持原有顺序在后
	List<Hook> result = new ArrayList<>(prioritizedHooks);
	result.addAll(nonPrioritizedHooks);
	
	return result;
}

分类后的结果:

在这里插入图片描述

2.3.6 注册为执行图节点

按执行时机和钩子类型,将所有 Hook 适配并注册到执行图,使其能在 Agent 生命周期的对应阶段自动执行。

Agent 执行前钩子注册为执行图节点:

// 为 Agent 执行前钩子 注册图节点
for (Hook hook : beforeAgentHooks) {
	// 判断钩子类型:标准 AgentHook
	if (hook instanceof AgentHook agentHook) {
		// 注册节点:使用钩子完整名称 + 后缀,绑定 beforeAgent 执行方法
		graph.addNode(Hook.getFullHookName(hook) + ".before", agentHook::beforeAgent);
	}
	// 判断钩子类型:消息型 AgentHook
	else if (hook instanceof MessagesAgentHook messagesAgentHook) {
		// 注册节点:使用静态方法包装消息钩子执行逻辑
		graph.addNode(Hook.getFullHookName(hook) + ".before", MessagesAgentHook.beforeAgentAction(messagesAgentHook));
	}
}

Agent 执行后钩子注册图节点:

// 为 Agent 执行后钩子 注册图节点
for (Hook hook : afterAgentHooks) {
	// 标准 AgentHook
	if (hook instanceof AgentHook agentHook) {
		graph.addNode(Hook.getFullHookName(hook) + ".after", agentHook::afterAgent);
	}
	// 消息型 AgentHook
	else if (hook instanceof MessagesAgentHook messagesAgentHook) {
		graph.addNode(Hook.getFullHookName(hook) + ".after", MessagesAgentHook.afterAgentAction(messagesAgentHook));
	}
}

为模型调用前钩子注册图节点:

// 
for (Hook hook : beforeModelHooks) {
	// 判断钩子类型:标准 ModelHook
	if (hook instanceof ModelHook modelHook) {
		// 特殊处理:中断钩子直接注册实例(支持流程中断能力)
		if (hook instanceof InterruptionHook interruptionHook) {
			graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", interruptionHook);
		}
		// 普通 ModelHook 绑定 beforeModel 方法
		else {
			graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", modelHook::beforeModel);
		}
	}
	// 消息型 ModelHook
	else if (hook instanceof MessagesModelHook messagesModelHook) {
		graph.addNode(Hook.getFullHookName(hook) + ".beforeModel", MessagesModelHook.beforeModelAction(messagesModelHook));
	}
}

为模型调用后钩子注册图节点:

// 为 模型调用后钩子 注册图节点
for (Hook hook : afterModelHooks) {
	// 判断钩子类型:标准 ModelHook
	if (hook instanceof ModelHook modelHook) {
		// 特殊处理:人工介入循环钩子直接注册实例(支持异步/中断等待)
		if (hook instanceof HumanInTheLoopHook humanInTheLoopHook) {
			graph.addNode(Hook.getFullHookName(hook) + ".afterModel", humanInTheLoopHook);
		}
		// 普通 ModelHook 绑定 afterModel 方法
		else {
			graph.addNode(Hook.getFullHookName(hook) + ".afterModel", modelHook::afterModel);
		}
	}
	// 消息型 ModelHook
	else if (hook instanceof MessagesModelHook messagesModelHook) {
		graph.addNode(Hook.getFullHookName(hook) + ".afterModel", MessagesModelHook.afterModelAction(messagesModelHook));
	}
}

2.3.7 设置钩子边

setupHookEdges 方法中会对执行节点进行编排,用边(Edge)将节点连接起来,进行执行图编排。

/**
 * 【顶层方法】配置React智能体的所有钩子执行边,统筹钩子编排、工具路由和流程兜底
 * 负责串联四类钩子(Agent前后/Model前后),处理执行顺序,配置最终流程跳转逻辑
 * @param graph 状态图实例,用于添加节点和执行边
 * @param beforeAgentHooks 智能体执行前的钩子列表
 * @param afterAgentHooks 智能体执行后的钩子列表
 * @param beforeModelHooks 模型调用前的钩子列表
 * @param afterModelHooks 模型调用后的钩子列表
 * @param entryNode 状态图总入口节点
 * @param loopEntryNode 智能体循环入口节点
 * @param loopExitNode 智能体循环出口节点
 * @param exitNode 状态图最终出口节点
 * @param agentInstance React智能体实例,用于判断是否包含工具
 * @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 {

    // 1. 正序串联【智能体执行前】钩子,执行完成后回到循环入口
    chainHook(graph, beforeAgentHooks, ".before", loopEntryNode, loopEntryNode, exitNode);

    // 2. 正序串联【模型调用前】钩子,执行完成后进入模型节点
    chainHook(graph, beforeModelHooks, ".beforeModel", AGENT_MODEL_NAME, loopEntryNode, exitNode);

    // 3. 逆序串联【模型调用后】钩子(AOP后置钩子标准执行顺序)
    if (!afterModelHooks.isEmpty()) {
        chainModelHookReverse(graph, afterModelHooks, ".afterModel", AGENT_MODEL_NAME, loopEntryNode, exitNode);
    }

    // 4. 逆序串联【智能体执行后】钩子,执行完成后直接到最终出口
    if (!afterAgentHooks.isEmpty()) {
        chainAgentHookReverse(graph, afterAgentHooks, ".after", exitNode, loopEntryNode, exitNode);
    }

    // 5. 动态配置最终流程边:根据是否有工具,构建不同的执行链路
    if (agentInstance.hasTools) {
        // 场景1:智能体包含工具 → 配置工具调用路由逻辑
        setupToolRouting(graph, loopExitNode, loopEntryNode, exitNode, agentInstance);
    } else if (!loopExitNode.equals(AGENT_MODEL_NAME)) {
        // 场景2:无工具 + 存在模型后置钩子 → 钩子出口连接到最终出口,支持条件跳转
        addHookEdge(graph, loopExitNode, exitNode, loopEntryNode, exitNode, afterModelHooks.get(afterModelHooks.size() - 1).canJumpTo());
    } else {
        // 场景3:无工具 + 无模型后置钩子 → 直接连接循环出口到最终出口(最简流程)
        graph.addEdge(loopExitNode, exitNode);
    }
}

/**
 * 【底层方法】为状态图添加钩子执行边,支持【固定普通边】和【动态条件边】两种模式
 * 有跳转规则:创建条件边,根据状态中的jump_to指令动态路由;
 * 无跳转规则:创建固定边,直接跳转到默认节点
 * @param graph 状态图实例
 * @param name 源节点名称(当前钩子节点)
 * @param defaultDestination 默认跳转目标节点
 * @param modelDestination 模型节点目标
 * @param endDestination 流程结束节点
 * @param canJumpTo 钩子允许的跳转类型集合(模型/工具/结束)
 * @throws GraphStateException 状态图构建异常
 */
private static void addHookEdge(
        StateGraph graph,
        String name,
        String defaultDestination,
        String modelDestination,
        String endDestination,
        List<JumpTo> canJumpTo) throws GraphStateException {

    // 分支1:钩子支持动态条件跳转 → 创建条件边
    if (canJumpTo != null && !canJumpTo.isEmpty()) {
        // 定义路由逻辑:根据状态中的jump_to值,动态决定跳转目标
        EdgeAction router = state -> {
            // 从状态图共享状态中获取跳转指令
            Object jumpToValue = state.value("jump_to").orElse(null);
            JumpTo jumpTo = null;

            // 兼容解析两种格式的跳转指令:JumpTo枚举 / 字符串
            if (jumpToValue != null) {
                if (jumpToValue instanceof JumpTo) {
                    jumpTo = (JumpTo) jumpToValue;
                } else if (jumpToValue instanceof String) {
                    jumpTo = JumpTo.fromStringOrNull((String) jumpToValue);
                }
            }

            // 解析并返回最终的跳转节点
            return resolveJump(jumpTo, modelDestination, endDestination, defaultDestination);
        };

        // 构建所有允许的目标节点集合(状态图要求必须预声明所有可能的跳转目标)
        Map<String, String> destinations = new HashMap<>();
        // 默认节点必加入
        destinations.put(defaultDestination, defaultDestination);

        // 根据钩子允许的跳转类型,添加对应目标节点
        if (canJumpTo.contains(JumpTo.end)) {
            destinations.put(endDestination, endDestination);
        }
        if (canJumpTo.contains(JumpTo.tool)) {
            destinations.put(AGENT_TOOL_NAME, AGENT_TOOL_NAME);
        }
        // 安全校验:禁止源节点和模型节点相同,避免自环死循环
        if (canJumpTo.contains(JumpTo.model) && !name.equals(modelDestination)) {
            destinations.put(modelDestination, modelDestination);
        }

        // 向状态图添加异步条件边
        graph.addConditionalEdges(name, edge_async(router), destinations);
    }
    // 分支2:钩子无跳转规则 → 创建固定普通边,直接跳默认节点
    else {
        graph.addEdge(name, defaultDestination);
    }
}
2.3.7.1 链式组装
执行前钩子(正序)

首先对每个位置的钩子进行链式组装:

		// Chain before_agent hook
		chainHook(graph, beforeAgentHooks, ".before", loopEntryNode, loopEntryNode, exitNode);

		// Chain before_model hook
		chainHook(graph, beforeModelHooks, ".beforeModel", AGENT_MODEL_NAME, loopEntryNode, exitNode);

chainHook 中会正序串联钩子列表,将多个钩子连成执行链,相邻钩子依次连接,最后一个钩子指向默认目标节点,构成完整的钩子执行链路。

/**
 * 【中层方法】正序串联钩子列表,将多个钩子连成执行链
 * 相邻钩子依次连接,最后一个钩子指向默认目标节点,构成完整的钩子执行链路
 * @param graph 状态图实例
 * @param hooks 待串联的钩子列表
 * @param nameSuffix 节点名称后缀,用于区分不同类型的钩子(如.before/.beforeModel)
 * @param defaultNext 最后一个钩子执行完成后的默认跳转节点
 * @param modelDestination 条件跳转时的模型目标节点
 * @param endDestination 条件跳转时的结束节点
 * @throws GraphStateException 状态图构建异常
 */
private static void chainHook(
        StateGraph graph,
        List<Hook> hooks,
        String nameSuffix,
        String defaultNext,
        String modelDestination,
        String endDestination) throws GraphStateException {

    // 1. 遍历钩子列表,正序连接相邻的两个钩子(钩子1→钩子2→钩子3...)
    for (int i = 0; i < hooks.size() - 1; i++) {
        Hook currentHook = hooks.get(i);
        Hook nextHook = hooks.get(i + 1);
        // 为当前钩子添加指向后续钩子的执行边
        addHookEdge(
                graph,
                Hook.getFullHookName(currentHook) + nameSuffix,
                Hook.getFullHookName(nextHook) + nameSuffix,
                modelDestination,
                endDestination,
                currentHook.canJumpTo()
        );
    }

    // 2. 钩子列表非空时,为最后一个钩子绑定默认的下一个业务节点
    if (!hooks.isEmpty()) {
        Hook lastHook = hooks.get(hooks.size() - 1);
        addHookEdge(
                graph,
                Hook.getFullHookName(lastHook) + nameSuffix,
                defaultNext,
                modelDestination,
                endDestination,
                lastHook.canJumpTo()
        );
    }
}

假设传入 3 个智能体前置钩子,最终生成的执行链路::

钩子1.before → 钩子2.before → 钩子3.before → loopEntryNode(循环入口)

例如,_AGENT_HOOK_PIIDetection[IP].beforeModel->_AGENT_HOOK_ModelCallLimit.beforeModel->模型执行节点(和结束节点):

在这里插入图片描述

执行后钩子(反序)

逆序串联执行后钩子执行链:

		// Chain after_model hook (reverse order)
		if (!afterModelHooks.isEmpty()) {
			chainModelHookReverse(graph, afterModelHooks, ".afterModel", AGENT_MODEL_NAME, loopEntryNode, exitNode);
		}

		// Chain after_agent hook (reverse order)
		if (!afterAgentHooks.isEmpty()) {
			chainAgentHookReverse(graph, afterAgentHooks, ".after", exitNode, loopEntryNode, exitNode);
		}

执行顺序:最后注册的钩子先执行,最先注册的钩子最后执行

/**
 * 【智能体后置钩子专用】逆序串联钩子执行链 (AOP后置增强标准逻辑)
 * 与 chainHook 正序相反,用于 afterAgentHooks 智能体后置钩子
 * 执行顺序:最后注册的钩子先执行,最先注册的钩子最后执行
 * @param graph 状态图实例
 * @param hooks 智能体后置钩子列表 (afterAgentHooks)
 * @param nameSuffix 节点名称后缀 (固定为 .after)
 * @param defaultNext 默认下一个节点 (本方法未使用,因为最终指向END)
 * @param modelDestination 条件跳转的模型节点
 * @param endDestination 流程结束节点
 * @throws GraphStateException 状态图构建异常
 */
private static void chainAgentHookReverse(
		StateGraph graph,
		List<Hook> hooks,
		String nameSuffix,
		String defaultNext,
		String modelDestination,
		String endDestination) throws GraphStateException {

	// 1. 钩子列表非空时:将【列表中第一个钩子】直接连接到 流程终点(END)
	if (!hooks.isEmpty()) {
		Hook first = hooks.get(0);
		addHookEdge(graph,
				Hook.getFullHookName(first) + nameSuffix,
				StateGraph.END, // 关键:智能体后置钩子最终执行完直接结束流程
				modelDestination, endDestination,
				first.canJumpTo());
	}

	// 2. 逆序遍历钩子列表:从最后一个钩子向前连接,构建逆序执行链
	// 循环:从最后一个元素开始,到第二个元素结束(i>0)
	for (int i = hooks.size() - 1; i > 0; i--) {
		Hook m1 = hooks.get(i);   // 当前钩子(后注册的)
		Hook m2 = hooks.get(i - 1); // 前一个钩子(先注册的)
		// 反向连接:当前钩子 → 前一个钩子
		addHookEdge(graph,
				Hook.getFullHookName(m1) + nameSuffix,
				Hook.getFullHookName(m2) + nameSuffix,
				modelDestination, endDestination,
				m1.canJumpTo());
	}
}

chainHook(正序)的核心区别:

方法 执行顺序 适用钩子类型 最终指向
chainHook 正序 1→2→3 beforeAgent/beforeModel 下一个业务节点
chainAgentHookReverse 逆序 3→2→1 afterAgent(智能体后置) 流程 END

2.3.8 构建循环出口

最后,根据「是否有工具、是否有模型后置钩子」,自动构建从「循环出口」到「最终出口」的执行链路,是整个 AI 智能体工作流的收尾关键。

// 如果智能体配置了工具,配置工具调用的路由逻辑
if (agentInstance.hasTools) {
    setupToolRouting(graph, loopExitNode, loopEntryNode, exitNode, agentInstance);
} 
// 无工具,但【循环出口不是模型节点】 → 代表存在模型后置钩子,用钩子条件边连接出口
else if (!loopExitNode.equals(AGENT_MODEL_NAME)) {
    // 无工具但有模型后置钩子 → 将循环出口通过钩子条件边连接到最终出口
    addHookEdge(graph, loopExitNode, exitNode, loopEntryNode, exitNode, 
            afterModelHooks.get(afterModelHooks.size() - 1).canJumpTo());
} 
// 无工具 + 无模型后置钩子 → 最简流程,直接固定连接循环出口到最终出口
else {
    // 直接添加普通边:循环执行完成后,无条件跳转到最终出口
    graph.addEdge(loopExitNode, exitNode);
}

2.3.9 构建完成

每一个 Hook 在不同位置,都会被注册为图中的一个执行节点,可以看到这里有 5Hook 相关的节点:

在这里插入图片描述
比如 PIIDetectionHook

在这里插入图片描述
最终,所有 Hook 都被注册为执行节点,并用条件边进行连接,构成了一张完整的执行图:

在这里插入图片描述

2.4 执行阶段

2.4.1 图可视化

首先可以通过以下方式查看图的结构:

        // 获取并编译图
        CompiledGraph compiledGraph = myAgent.getAndCompileGraph();
        // 两种支持的可视化格式(PlantUML/Mermaid)
        // PLANTUML
        GraphRepresentation plantuml = compiledGraph.stateGraph.getGraph(GraphRepresentation.Type.PLANTUML);
        // MERMAID
        GraphRepresentation mermaid = compiledGraph.stateGraph.getGraph(GraphRepresentation.Type.MERMAID);

        System.out.println(plantuml.content());
        System.out.println(mermaid.content());

PLANTUML 可视化后的结果:

在这里插入图片描述

2.4.2 图执行

invoke 流程:

invoke()getAndCompileGraph() → compiledGraph.invoke()StateGraph 执行 → 按节点顺序调用

call 方法在构建输入消息后,会调用到 ReactAgent#doMessageInvoke 进行图的编译和执行:

	protected Optional<OverAllState> doInvoke(Map<String, Object> input, RunnableConfig runnableConfig) {
		CompiledGraph compiledGraph = getAndCompileGraph();
		return compiledGraph.invoke(input, buildNonStreamConfig(runnableConfig));
	}

同步阻塞执行图流程,返回空安全的最终整体状态:

/**
 * 执行图流程并返回最终状态
 * 
 * @param inputs 输入参数键值对集合
 * @param config 流程执行配置项
 * @return 封装最终整体状态的Optional对象(空安全,无结果时返回空Optional)
 */
public Optional<OverAllState> invoke(Map<String, Object> inputs, RunnableConfig config) {
    // 1. 启动响应式流执行图流程 → 2. 获取流程最后一个节点输出 → 3. 提取节点中的整体状态 → 4. 阻塞等待执行完成
    // 5. 用Optional包装结果,避免空指针异常
    return Optional.ofNullable(
        stream(inputs, config)    // 启动图的流式执行,返回响应式流
            .last()               // 获取执行流中的最后一个节点输出(最终结果)
            .map(NodeOutput::state) // 从节点输出中提取整体状态
            .block()              // 同步阻塞,等待响应式流执行完毕,获取状态对象
    );
}

后续就是按照图的结构进行执行了,开始、由边连接决定节点执行顺序、执行 Hook 节点中的钩子方法… 移植到流程结束了…

Logo

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

更多推荐