RunnableConfig 详细分析

5 阅读6分钟

RunnableConfig 详细分析

源码路径:spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/RunnableConfig.java(约 505 行)


1. 类的定位

RunnableConfig 是图引擎的运行时配置,每一次 stream() / invoke() 调用都需要一个 RunnableConfig 实例。它回答的核心问题是:这次执行属于哪个线程、从哪个检查点恢复、用什么流模式、携带什么元数据

CompileConfig(编译时配置)的分工:

维度CompileConfigRunnableConfig
生命周期图编译时,一次性每次执行,可多次
谁持有CompiledGraph 内部字段调用方创建,作为参数传入
职责定义"图能做什么"(中断点、Saver、递归上限)定义"这次执行怎么做"(线程 ID、恢复点、元数据)
用户 ─── RunnableConfig ───▶ CompiledGraph.stream/invoke
                                    │
                  CompileConfig ────┘(编译时已固定)

2. 核心字段

字段类型说明
threadIdString线程标识,checkpoint 存储的分区 key,不同线程互相隔离
checkPointIdString检查点 ID,从指定快照恢复时使用
nextNodeString下一个要执行的节点 ID,Human-in-the-loop 跳转用
streamModeCompiledGraph.StreamMode流模式:VALUES(默认)/ SNAPSHOTS
metadataMap<String, Object>不可变,每次执行的环境信息(模型名、工具名、人类反馈等)
contextConcurrentMap<String, Object>可变,节点间传递的临时上下文,不持久化到 checkpoint
storeStore长期记忆存储,跨执行持久化
interruptedNodesMap<String, Object>被标记为中断的节点 ID → 中断数据

metadatacontext 的关键区别metadata 是不可变的,创建后不能修改,适合存放"这次执行的配置信息";contextConcurrentHashMap,节点执行过程中可读写,适合"节点间传话",但不会持久化。


3. 元数据常量(公共 key 约定)

RunnableConfig 定义了一组标准 metadata key,供图引擎内部和用户使用:

常量用途
AGENT_MODEL_NAME"_AGENT_MODEL_"当前使用的 AI 模型名称
AGENT_TOOL_NAME"_AGENT_TOOL_"当前工具名称
AGENT_HOOK_NAME_PREFIX"_AGENT_HOOK_"钩子前缀,拼接节点 ID
AGENT_NAME_KEY"_AGENT_"Agent 名称
HUMAN_FEEDBACK_METADATA_KEY"HUMAN_FEEDBACK"Human-in-the-loop 反馈数据
STATE_UPDATE_METADATA_KEY"STATE_UPDATE"状态更新数据
DEFAULT_PARALLEL_EXECUTOR_KEY"_DEFAULT_PARALLEL_EXECUTOR_"默认并行执行器
DEFAULT_PARALLEL_AGGREGATION_STRATEGY_KEY"_DEFAULT_PARALLEL_AGGREGATION_STRATEGY_"默认并行聚合策略
DYNAMIC_TOOL_CALLBACKS_METADATA_KEY"_DYNAMIC_TOOL_CALLBACKS_"动态工具回调
MERGE_REASONING_CONTENT_METADATA_KEY"_MERGE_REASONING_CONTENT_"是否合并推理内容

这些常量本质上是 metadata Map 的标准化 key,避免硬编码字符串散落在代码各处。


4. 不可变设计 + with 方法

RunnableConfigfinal class,字段在构造后不可变(metadataCollections.unmodifiableMap 包装)。需要修改配置时,使用 with* 方法返回一个新实例

// 原始 config
RunnableConfig config = RunnableConfig.builder()
    .threadId("thread-1")
    .build();

// 切换流模式 → 返回新 config,原 config 不变
RunnableConfig snapshotConfig = config.withStreamMode(StreamMode.SNAPSHOTS);

// 标记恢复 → 返回新 config
RunnableConfig resumeConfig = config.withResume();

// 更新检查点 ID → 返回新 config
RunnableConfig newConfig = config.withCheckPointId("cp-42");

这种设计保证了:

  • 线程安全:并发场景下多个线程可共享同一个 config 而不互相影响
  • 快照不可变:checkpoint 中保存的 config 引用不会被后续修改污染

唯一的例外是 context 字段(ConcurrentHashMap),它是有意的可变共享状态,用于节点间传递临时数据。


5. Builder 模式

RunnableConfig.Builder 继承自 HasMetadata.Builder<Builder>,提供链式构造:

RunnableConfig config = RunnableConfig.builder()
    .threadId("thread-1")
    .checkPointId("cp-42")
    .nextNode("nodeB")
    .streamMode(StreamMode.VALUES)
    .addMetadata("configData", "test")
    .addMetadata(RunnableConfig.AGENT_MODEL_NAME, "gpt-4")
    .defaultParallelExecutor(ForkJoinPool.commonPool())
    .store(myStore)
    .build();

Builder 方法一览

方法说明
threadId(String)设置线程 ID
checkPointId(String)设置检查点 ID
nextNode(String)设置下一节点
streamMode(StreamMode)设置流模式
addMetadata(String, Object)添加元数据(继承自 HasMetadata.Builder)
addHumanFeedback(InterruptionMetadata)添加人类反馈
resume()设置 HUMAN_FEEDBACK 占位符,标记恢复执行
addStateUpdate(Map<String,Object>)添加状态更新
addParallelNodeExecutor(String, Executor)给指定并行节点设置执行器
defaultParallelExecutor(Executor)设置全局默认并行执行器
addParallelNodeAggregationStrategy(String, NodeAggregationStrategy)设置并行节点聚合策略
defaultParallelAggregationStrategy(NodeAggregationStrategy)设置默认聚合策略
mergeReasoningContent(boolean)设置推理内容合并标志
store(Store)设置长期存储
clearContext()清空上下文
build()构建 RunnableConfig

Builder 有两个构造器:

  • Builder() — 空构造,从零开始
  • Builder(RunnableConfig config)复制构造,从已有 config 拷贝所有字段

with* 方法内部就是使用复制构造器:

public RunnableConfig withStreamMode(StreamMode streamMode) {
    return RunnableConfig.builder(this)   // 复制当前 config
        .streamMode(streamMode)           // 覆盖目标字段
        .build();                         // 构建新实例
}

6. 中断(Interrupt)相关方法

中断是 Human-in-the-loop 的核心机制,RunnableConfig 负责追踪哪些节点被中断

方法行为
markNodeAsInterrupted(nodeId)interruptedNodes 中记录该节点
isInterrupted(nodeId)查询节点是否被中断
withNodeResumed(nodeId)创建新 config,将该节点标记为"已恢复"
removeInterrupted(nodeId)移除中断标记
interruptData(key)读取中断时附带的数据

典型的中断-恢复流程

// 1. 首次执行,遇到中断
CompiledGraph graph = workflow.compile();
RunnableConfig config = RunnableConfig.builder().threadId("t1").build();
graph.stream(Map.of("input", "hello"), config).blockLast();
// → 执行到 nodeB 时被 interruptsBefore 中断

// 2. 获取中断时的快照
StateSnapshot snapshot = graph.getState(config);

// 3. 人工修改状态
config = graph.updateState(snapshot.config(), Map.of("approval", "yes"), "nodeB");

// 4. 恢复执行
RunnableConfig resumeConfig = config.withResume();
graph.stream(null, resumeConfig).blockLast();

7. 并行执行器配置

Builder 提供两级并行执行器配置:

// 方式一:给特定并行节点指定执行器
RunnableConfig.builder()
    .addParallelNodeExecutor("parallelNode_A", customExecutor)
    .build();

// 方式二:设置全局默认执行器
RunnableConfig.builder()
    .defaultParallelExecutor(ForkJoinPool.commonPool())
    .build();

// 同理,聚合策略也有两级
RunnableConfig.builder()
    .addParallelNodeAggregationStrategy("targetNode", myStrategy)
    .defaultParallelAggregationStrategy(defaultStrategy)
    .build();

执行器存储在 metadata 中(以 _DEFAULT_PARALLEL_EXECUTOR_nodeId 为 key),运行时由 ParallelNode 读取。


8. HasMetadata 接口

RunnableConfig 实现了 HasMetadata<RunnableConfig.Builder> 接口:

public interface HasMetadata<B extends HasMetadata.Builder<B>> {
    String INTERRUPT_PREFIX = "__NODE_INTERRUPT__";

    static String formatNodeId(String nodeId);   // → "__NODE_INTERRUPT__(nodeId)"

    Map<String, Object> metadata();
    Object metadata(String key);
    <T> T metadata(String key, TypeRef<T> typeRef);
    <T> T getMetadataAndRemove(String key, TypeRef<T> typeRef);  // 读后删除
}

INTERRUPT_PREFIX 是中断节点在 metadata 中的 key 前缀,格式为 __NODE_INTERRUPT__(nodeId)


9. 在图引擎中的流转路径

用户构建 RunnableConfig
    ↓
CompiledGraph.stream(inputs, config)
    ↓
GraphRunner(initialState, config, compiledGraph)
    ↓
GraphRunnerContext(state, config)
    ├─ config 含 HUMAN_FEEDBACK 或 checkPointId?
    │   └─ Yes → initializeFromResume()(从 checkpoint 恢复)
    │   └─ No  → initializeFromStart()
    ↓
每个节点执行后:
    ├─ GraphLifecycleListener.afterNode(nodeId, state, config)
    ├─ BaseCheckpointSaver.put(config, checkpoint)
    │   └─ 返回含新 checkPointId 的 RunnableConfig
    └─ 中断判断 → config.markNodeAsInterrupted(nodeId)
    ↓
执行结束:
    ├─ GraphLifecycleListener.onComplete(state, config)
    └─ CompileConfig.releaseThread = true?
        └─ BaseCheckpointSaver.release(config) — 释放线程资源

10. 与 CompileConfig 的协作总结

┌─────────────────────────────────────────────────────┐
│                  CompileConfig(编译时)               │
│  saverConfig      → 用什么 Saver 存 checkpoint       │
│  interruptsBefore → 哪些节点前中断                    │
│  interruptsAfter  → 哪些节点后中断                    │
│  recursionLimit   → 最大迭代数                        │
│  releaseThread    → 执行完是否释放线程                 │
└────────────────────────┬────────────────────────────┘
                         │ 编译固化
                         ▼
┌─────────────────────────────────────────────────────┐
│                  RunnableConfig(运行时)              │
│  threadId         → 存到哪个 thread 分区              │
│  checkPointId     → 从哪个快照恢复                    │
│  nextNode         → 恢复后跳到哪个节点                │
│  metadata         → 模型名/工具名/人类反馈             │
│  context          → 节点间临时传话                     │
│  streamMode       → VALUES / SNAPSHOTS               │
│  interruptedNodes → 运行时追踪中断状态                 │
└─────────────────────────────────────────────────────┘

11. 总结

维度说明
职责图的运行时配置:线程标识、检查点定位、流模式、执行元数据
不可变性final class,字段构造后不可变,修改通过 with* 返回新实例
例外context 字段为 ConcurrentHashMap,有意允许节点间可变共享
中断追踪interruptedNodes 记录哪些节点被中断,配合 withResume() 恢复
Builder链式构造 + 复制构造,支持并行执行器/聚合策略的两级配置
元数据约定10+ 个标准常量 key,避免硬编码字符串
流转用户创建 → CompiledGraph → GraphRunnerContext → Saver.put → 返回新 config

相关文件

  • CompiledGraph.md — 执行层,RunnableConfig 的主要消费方
  • StateGraph.md — 图定义层,边动作接口接收 RunnableConfig
  • EdgeCondition.md — 条件边路由,singleAction / multiAction 接收 RunnableConfig
  • AsyncCommandAction.md — 单路由 action,签名含 RunnableConfig 参数
  • CompileConfig.java — 编译时配置,与 RunnableConfig 互补协作
  • HasMetadata.javaRunnableConfig 的父接口,定义 metadata 读取能力
  • GraphRunnerContext.java — 持有 RunnableConfig,根据其内容决定启动或恢复
  • BaseCheckpointSaver.javalist/get/put/release 均以 RunnableConfig 为参数
  • GraphLifecycleListener.java — 所有生命周期回调传入 RunnableConfig