Repository navigation
Closed
Labels
area/graph图引擎:图工作流、路由、节点与嵌套图图引擎:图工作流、路由、节点与嵌套图area/human-in-the-loop人工介入:审批、暂停与人工反馈人工介入:审批、暂停与人工反馈area/persistence持久化:状态存储、检查点与中断恢复持久化:状态存储、检查点与中断恢复kind/bug缺陷:实际行为与预期不符缺陷:实际行为与预期不符needs-triage待分诊:尚需确认类型、领域或优先级待分诊:尚需确认类型、领域或优先级
Description
背景
这是上游遗留问题 alibaba/spring-ai-alibaba#4886。在当前 ARGI main(7a0bf414b)中仍可离线复现。
当前行为
当状态键使用 AppendStrategy 时,调用三参数 CompiledGraph.updateState(config, values, asNode) 会把已有状态再次通过 APPEND 策略合并,导致列表内容翻倍。
例如已有 messages = ["seed"],仅更新 human_feedback 并指定 asNode 后,状态变为:
messages = ["seed", "seed"]
两参数 updateState(config, values) 不经过该分支,因此不会触发同样的重复。
最小复现
MemorySaver saver = new MemorySaver();
CompiledGraph graph = new StateGraph(() -> Map.of("messages", KeyStrategy.APPEND))
.addNode("resume", node_async(state -> Map.of()))
.addEdge(StateGraph.START, "resume")
.addEdge("resume", StateGraph.END)
.compile(CompileConfig.builder()
.saverConfig(SaverConfig.builder().register(saver).build())
.build());
RunnableConfig config = RunnableConfig.builder().threadId("upstream-4886").build();
RunnableConfig checkpointConfig = saver.put(config, Checkpoint.builder()
.state(Map.of("messages", List.of("seed")))
.nodeId(StateGraph.START)
.nextNodeId("resume")
.build());
RunnableConfig updated = graph.updateState(
checkpointConfig, Map.of("human_feedback", "next"), "resume");
assertThat(graph.getState(updated).state().value("messages", List.class).orElseThrow())
.containsExactly("seed", "seed"); // 当前实际结果本地验证结果:JDK 17,argi-graph-core,测试通过并确认重复;无需模型、API Key 或外部服务。
根因
CompiledGraph#updateState 先在 349 行合并用户提供的 values,随后:
nextNodeId(asNode, branchCheckpoint.getState(), ...)对直接边返回new Command(route.id(), state)(441-443行),Command.update()携带的是完整 state;357行又执行branchCheckpoint.updateState(nextNodeCommand.update(), keyStrategyMap);- 完整 state 被当成增量再次套用
AppendStrategy,已有列表因此重复。
条件边路径也会在 464-465 行返回已合并后的完整 state,存在相同的二次合并风险。
影响
- HITL 中断后使用
asNode更新状态并恢复执行时,消息历史可能指数式膨胀或出现重复消息。 - 所有非幂等
KeyStrategy(不仅是 APPEND)都可能产生错误语义。 asNode的路由选择与状态增量被混在同一个Command.update()中,容易造成恢复路径与普通执行路径不一致。
期望
指定 asNode 只应决定后续路由,并只合并边动作真正返回的增量;不应把完整 checkpoint state 重新应用一遍。
验收建议
- 增加直接边与条件边的回归测试。
- 覆盖
AppendStrategy,并至少增加一个自定义非幂等策略用例。 - 验证两参数/三参数
updateState在相同更新下保持一致的状态内容,仅nextNode不同。 - 覆盖 HITL interruption → updateState → resume 的真实链路。
Metadata
Metadata
Assignees
Labels
area/graph图引擎:图工作流、路由、节点与嵌套图图引擎:图工作流、路由、节点与嵌套图area/human-in-the-loop人工介入:审批、暂停与人工反馈人工介入:审批、暂停与人工反馈area/persistence持久化:状态存储、检查点与中断恢复持久化:状态存储、检查点与中断恢复kind/bug缺陷:实际行为与预期不符缺陷:实际行为与预期不符needs-triage待分诊:尚需确认类型、领域或优先级待分诊:尚需确认类型、领域或优先级
Activity
logicwu0 commentedon Oct 13, 2026
I can take this. I reproduced the duplication on current main (e10f9ab): 12 of the 14 regression cases fail before the fix, while the two no-asNode controls pass. The fix keeps routing commands delta-only and persists the resolved next node in the checkpoint, so resume does not evaluate the same conditional edge and apply its delta again. Both MemorySaver and VersionedMemoryCheckpointSaver are covered, including APPEND, a custom additive strategy, parallel conditional routing, and interruption -> updateState -> resume. All 14 cases now pass locally, along with the offline graph-core suite and 12 related Agent tests. I will submit the PR shortly.