文章目录
- [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 核心架构组件
AgentLlmNode:LLM推理节点,负责消息处理和决策生成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 构造函数执行步骤
执行以下初始化步骤:
- 调用父类
BaseAgent构造函数,设置基础属性(name、description、outputKey等) - 初始化线程状态映射表(用于
human-in-the-loop) - 设置核心节点(
llmNode、toolNode)和配置(compileConfig) - 注入
Hook和Interceptor - 设置状态序列化器(默认使用
Jackson序列化器) - 收集并合并拦截器(从
hooks和builder中收集) - 将拦截器注入到对应节点中
- 设置
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 & 验证唯一性
代码核心功能:
- 空值防护 :处理
hooks为null的情况,初始化空集合 - 默认 Hook 注入 :强制添加
InstructionAgentHook,保证指令逻辑生效 - 唯一性校验 :通过
HashSet校验Hook全名称,杜绝重复实例 - 上下文绑定 :为所有
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 中添加 LLM 、Tool 核心节点
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 节点底层执行逻辑
call、stream 等方法中,实际调用的是 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