Spring AI Alibaba 1.x 系列【21】 Hook 生命周期与执行流程源码剖析
文章目录
1. 概述
Hook 生命周期:
-
注册与初始化:
Hook通过Builder注册,在ReactAgent构造时被收集、去重、设置Agent信息,并按HookPosition分类排序后创建为图节点。 -
执行流程:
Agent运行时,Hook按BEFORE_AGENT→BEFORE_MODEL→AFTER_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);
}
}
并提取钩子后的 ModelInterceptor 、ToolInterceptor :
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 在不同位置,都会被注册为图中的一个执行节点,可以看到这里有 5 个 Hook 相关的节点:

比如 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 节点中的钩子方法… 移植到流程结束了…
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)