From a36fe72639d6f61a43ef9a54df78db6bcea7da3e Mon Sep 17 00:00:00 2001 From: zhuyongxin Date: Thu, 16 Jul 2026 18:59:35 +0800 Subject: [PATCH] docs(mvp): plan chat diagnosis stategraph refactor --- ..._Spring_AI_Alibaba_Graph_诊断编排改造.html | 192 +++++ ...16_Spring_AI_Alibaba_Graph_诊断编排改造.md | 383 +++++++++ mvp/issues/README.md | 3 +- ...chat-diagnosis-stategraph-orchestration.md | 770 ++++++++++++++++++ 4 files changed, 1347 insertions(+), 1 deletion(-) create mode 100644 knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造.html create mode 100644 knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造.md create mode 100644 mvp/issues/active/ISS-011-chat-diagnosis-stategraph-orchestration.md diff --git a/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造.html b/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造.html new file mode 100644 index 0000000..0420f0e --- /dev/null +++ b/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造.html @@ -0,0 +1,192 @@ + + + + + + Spring AI Alibaba Graph:诊断编排改造 + + + + +
+
+

Spring AI Alibaba Graph:诊断编排改造

+

作者:叫我小杨同学的小码酱

+ StateGraphAgent 编排条件边故障诊断 +
+ +
+

0. 核心摘要

+

让 ReactAgent 继续负责做事,让 StateGraph 负责下一步去哪里。

+

生活类比:Planner、Executor、Verifier 是医院科室,Graph 是分诊和转诊制度。

+

官方核心模型是 State、Nodes、Edges。本地依赖 1.1.2.0 已确认支持 StateGraph、条件边、编译配置、中断和 threadId。

+
+ +
+

1. 概念破冰

+
状态记事实,节点做任务,边管下一步,检查点管恢复。
+

当前 ChatService 已经是半个状态机:SequentialAgent 运行 Planner、Executor、Verifier,外层 Java 再根据 PASS、LOW_CONFID、REJECT 决定 Composer 或重试。Graph 改造的价值,是把分散的控制权显式化。

+
当前:SequentialAgent + 外层 if/else
+目标:StateGraph 条件边 + ReactAgent 语义节点
+
+ +
+

2. 深度解析

+

Supervisor 适合动态选择专科 Agent;Gatekeeper、Verifier 等强制门禁应由代码边控制。普通工具失败也不需要 interrupt,只有等待人工输入或审批时才暂停。

+
+flowchart TD + S["START"] --> P["Planner"] + P --> E["Executor"] + E -- "结构有效" --> G["Gatekeeper"] + E -- "阻断或非法" --> F["Fallback"] + G -- "允许" --> V["Verifier"] + G -- "拒绝" --> F + V -- "通过或拒绝" --> C["Composer"] + V -- "低置信且有预算" --> R["Retry Guard"] + R -- "补证据" --> P + R -- "停止" --> C + C --> X["END"] + F --> X +
+ +

状态设计

+

保存统一诊断上下文、计划、Executor 结构化输出、Gatekeeper 结果、Verifier verdict、重试轮次和最终结果。不要在 State 里复制所有原始日志或完整思考过程。

+ +

核心伪代码

+
StateGraph graph = new StateGraph("diagnosis_workflow", strategies)
+  .addNode("planner", plannerNode)
+  .addNode("executor", executorNode)
+  .addNode("gatekeeper", gatekeeperNode)
+  .addNode("verifier", verifierNode)
+  .addNode("retry_guard", retryGuardNode)
+  .addNode("composer", composerNode)
+  .addNode("fallback", fallbackNode)
+  .addEdge(START, "planner")
+  .addEdge("planner", "executor")
+  .addConditionalEdges("executor", routeAfterExecutor,
+      Map.of("gatekeeper","gatekeeper",
+             "retry_guard","retry_guard",
+             "fallback","fallback"))
+  .addConditionalEdges("gatekeeper", routeAfterGatekeeper,
+      Map.of("verifier","verifier","fallback","fallback"))
+  .addConditionalEdges("verifier", routeAfterVerifier,
+      Map.of("retry_guard","retry_guard",
+             "composer","composer",
+             "fallback","fallback"))
+  .addConditionalEdges("retry_guard", routeAfterRetry,
+      Map.of("planner","planner","composer","composer"))
+  .addEdge("composer", END)
+  .addEdge("fallback", END);
+ +

运行边界

+
RunnableConfig config = RunnableConfig.builder()
+  .threadId(runId)
+  .addMetadata("sessionId", sessionId)
+  .addMetadata("runId", runId)
+  .build();
+

一个 diagnosis_run 使用一个 Graph thread,避免同一 session 下多个 run 共享检查点。MemorySaver 不是跨重启持久化。

+ +

ReactAgent 的接入

+

本地 ReactAgent 提供 asNode(boolean, boolean)。当前项目第一阶段更适合用适配节点调用已有 Agent,显式控制输入、outputKey 和解析;状态契约稳定后再评估直接 asNode。

+
+ +
+

3. 深度裂变

+

🔍 搜索内化:改造的是控制权,不是 Agent

+

Graph 的节点可以是 LLM,也可以是普通 Java 代码;ReactAgent 本身已经是子图。所谓“Multi-Agent 改 Graph”,实际上是把跨 Agent 状态转换交给父 Graph。

+

官方页面示例有 OverAllStaste 拼写错误,实际类型是 OverAllState。网站主分支可能领先于本地依赖,最终必须以项目 JAR 和编译测试为准。

+
+ +
+

4. 实战指南

+
    +
  1. 先定义节点结果状态,不改 Prompt。
  2. +
  3. 把现有 Planner、Executor、Verifier 包装为 Node。
  4. +
  5. Gatekeeper 和固定降级做成 Java Node。
  6. +
  7. 迁移现有两轮 LOW_CONFID 控制。
  8. +
  9. 加入 Executor 阻断、非法结构和 Gatekeeper REJECT 条件边。
  10. +
  11. 保留旧 Sequential 链路作为短期回退。
  12. +
  13. 最后再增加 HITL、并行和专科 SubAgent。
  14. +
+

避坑

+
    +
  • 不要让 Supervisor 决定是否跳过安全门禁。
  • +
  • 不要把 no_evidence 当成 Executor 失败。
  • +
  • 不要让多个 run 共用 sessionId 作为 Graph threadId。
  • +
  • 不要把 MemorySaver 当成生产持久化。
  • +
  • 不要未验证 messages 传播就直接大量使用 asNode(true, true)。
  • +
+
+ +
+

5. 温故知新

+

FAQ

+
1. Graph 会替代 ReactAgent 吗?

不会,ReactAgent 可以作为子图节点继续使用。

+
2. 为什么不用 Supervisor 控制失败?

失败跳转是确定性规则,不需要增加一次模型决策。

+
3. no_evidence 是否直接 fallback?

不一定,合法 no-evidence 引用仍要经过 Gatekeeper 和 Verifier。

+
4. Composer 必须是 Agent 吗?

正常表达可以使用轻量 Agent,系统失败要保留固定模板。

+
5. threadId 用什么?

当前数据模型下优先使用 runId,sessionId 作为元数据。

+
6. 何时需要 Checkpointer?

需要暂停、恢复和检查 Graph 历史状态时。

+
7. 能直接使用 agent.asNode 吗?

可以,但要验证 outputKey、messages 和父子检查点。

+
8. AIOps 要单独 Graph 吗?

入口和输出策略独立,诊断核心可以共用。

+ +

自测题

+
    +
  1. 为什么 Executor 工具阻断不应由 Verifier 决定重试?
  2. +
  3. ReplaceStrategy 和 AppendStrategy 各适合什么状态?
  4. +
  5. 为什么合法 no_evidence 仍然需要 Gatekeeper?
  6. +
  7. Supervisor 与条件边的决策权有什么不同?
  8. +
  9. 为什么 Graph threadId 更适合使用 runId?
  10. +
  11. 什么情况下才应该配置 interruptBefore?
  12. +
+
+
+ + + + + diff --git a/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造.md b/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造.md new file mode 100644 index 0000000..ae04c8e --- /dev/null +++ b/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造/knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造.md @@ -0,0 +1,383 @@ +# Spring AI Alibaba Graph:诊断编排改造 + +作者:叫我小杨同学的小码酱 +标签:Spring AI Alibaba、StateGraph、Agent 编排、条件边、故障诊断 + +## 0. 核心摘要 + +一句话:让 ReactAgent 继续负责“做事”,让 StateGraph 负责“下一步去哪里”。 + +生活类比:Planner、Executor、Verifier 是医院里的不同科室,Graph 是分诊和转诊制度;不能让某个科室自己决定跳过检验和会诊。 + +真理锚点:官方文档将 Graph 概括为 State、Nodes、Edges,核心关系是“节点完成工作,边决定下一步做什么”。当前项目依赖的 `spring-ai-alibaba-graph-core:1.1.2.0` 本地 JAR 已确认提供 `StateGraph.addConditionalEdges(...)`、`CompileConfig.interruptBefore/After(...)` 和 `RunnableConfig.threadId(...)`。 + +## 1. 概念破冰 + +> 巧记:状态记事实,节点做任务,边管下一步,检查点管恢复。 + +当前 `ChatService` 已经像一个半成品状态机:`SequentialAgent` 固定执行 Planner、Executor、Verifier,外层 Java 循环再判断 PASS、LOW_CONFID、REJECT,并决定 Composer 或下一轮。问题不是 Agent 不够多,而是状态转换分散在 `SequentialAgent`、Hook 和外层 `if/else` 中。 + +```text +当前 +SequentialAgent: Planner -> Executor -> Verifier + | +ChatService 外层: PASS / LOW_CONFID / REJECT -> Composer / retry + +目标 +StateGraph 显式表示所有阶段和条件边 +ReactAgent 作为图中的语义节点继续复用 +``` + +## 2. 深度解析 + +### 2.1 为什么不是直接换成 SupervisorAgent + +SupervisorAgent 适合在多个专科 Agent 之间动态选择,例如 Database、Redis、JVM。当前诊断链路中的 Gatekeeper、Verifier 是不能随意跳过的质量门禁。如果让 LLM Supervisor 决定下一步,关键流程会从代码控制变成模型决策。 + +当前真正需要的是确定性路由: + +```mermaid +flowchart TD + S["START"] --> P["Planner"] + P --> E["Executor"] + E -- "证据结构有效" --> G["Gatekeeper"] + E -- "执行阻断或非法输出" --> F["Fallback"] + G -- "PASS 或 LOW_CONFID" --> V["Verifier"] + G -- "REJECT" --> F + V -- "PASS" --> C["Composer"] + V -- "LOW_CONFID 且有预算" --> R["Retry Guard"] + V -- "REJECT 或无预算" --> C + R -- "允许补证据" --> P + R -- "停止" --> C + C --> X["END"] + F --> X +``` + +### 2.2 State 应保存什么 + +状态应该保存跨节点需要共享的原始事实和结构化结果,不保存拼好的 Prompt,也不保存无边界增长的模型思考过程。 + +建议的语义状态: + +- `diagnosis_context`:入口适配后的统一诊断上下文。 +- `planner_plan`:Planner 的结构化计划。 +- `executor_output`:`executor_evidence_v2`。 +- `executor_status`:成功、阻断、非法输出或失败。 +- `gatekeeper_result`:代码验真结果。 +- `verifier_output`、`verdict`:可推导性结果。 +- `retry_context`、`round`:有限补证据状态。 +- `final_answer`:最终表达。 +- `failure_reason`:确定性的失败原因。 + +现有 Trace 已由 `agent_step` 和 `tool_invocation` 持久化,Graph State 不需要复制所有原始日志。 + +### 2.3 KeyStrategy 如何选择 + +诊断状态大多使用 `ReplaceStrategy`,因为每个阶段产生当前轮的最新结果。只有确实需要累计的轻量事件列表才使用 `AppendStrategy`。 + +```java +KeyStrategyFactory diagnosisStateStrategies() { + return () -> { + Map strategies = new HashMap<>(); + strategies.put("diagnosis_context", new ReplaceStrategy()); + strategies.put("planner_plan", new ReplaceStrategy()); + strategies.put("executor_output", new ReplaceStrategy()); + strategies.put("executor_status", new ReplaceStrategy()); + strategies.put("gatekeeper_result", new ReplaceStrategy()); + strategies.put("verifier_output", new ReplaceStrategy()); + strategies.put("verdict", new ReplaceStrategy()); + strategies.put("retry_context", new ReplaceStrategy()); + strategies.put("round", new ReplaceStrategy()); + strategies.put("final_answer", new ReplaceStrategy()); + strategies.put("failure_reason", new ReplaceStrategy()); + return strategies; + }; +} +``` + +### 2.4 ReactAgent 怎样放进 Graph + +本地 `1.1.2.0` 的 `ReactAgent` 提供 `asNode(boolean includeContents, boolean returnReasoningContents)`,可以直接作为子图节点。但当前项目 Planner、Executor、Verifier 的输入组织和输出解析已经有较多定制,第一阶段更推荐使用适配节点显式调用现有 Agent: + +```java +var plannerNode = node_async((state, config) -> { + DiagnosisContext context = requireContext(state); + String prompt = plannerInput(context, state.value("retry_context").orElse(null)); + + AssistantMessage response = plannerAgent.call(prompt, childConfig(config, "planner")); + PlannerPlan plan = plannerParser.parse(extractText(response)); + + return Map.of( + "planner_plan", plan, + "failure_reason", "" + ); +}); +``` + +适配节点的好处是不会意外把父图全部 `messages` 注入所有 Agent,也能继续复用当前解析器、Hook、Prompt 和 ToolCallback。 + +等统一状态契约稳定后,可以评估: + +```java +graph.addNode("planner", plannerAgent.asNode(false, false)); +``` + +但需要先验证父子图的 `messages`、outputKey 和 Checkpointer 是否符合预期。 + +### 2.5 Executor 节点只报告状态,不决定路由 + +```java +var executorNode = node_async((state, config) -> { + try { + PlannerPlan plan = requirePlan(state); + DiagnosisContext context = requireContext(state); + + AssistantMessage response = executorAgent.call( + executorInput(context, plan), + childConfig(config, "executor") + ); + + ExecutorEvidence output = executorParser.parse(extractText(response)); + + if (!output.isStructurallyValid()) { + return Map.of( + "executor_status", "INVALID_OUTPUT", + "failure_reason", "executor_evidence_v2 解析失败" + ); + } + + // no_evidence 仍然是合法结构,需要交给 Gatekeeper 验证真实引用。 + return Map.of( + "executor_status", "COMPLETED", + "executor_output", output + ); + } + catch (ToolCapabilityBlockedException e) { + return Map.of( + "executor_status", "TOOL_BLOCKED", + "failure_reason", e.getMessage() + ); + } + catch (Exception e) { + return Map.of( + "executor_status", "FAILED", + "failure_reason", safeMessage(e) + ); + } +}); +``` + +注意:`no_evidence` 不是 Executor 失败。当前项目已经用 `$.no_evidence` 表达“查询成功但无匹配证据”,它仍应进入 Gatekeeper 和 Verifier,防止被过度表达为“问题不存在”。 + +### 2.6 Gatekeeper 节点保持纯代码 + +```java +var gatekeeperNode = node_async((state, config) -> { + ExecutorEvidence output = requireExecutorOutput(state); + String runId = metadata(config, "runId"); + + GatekeeperResult result = executorGatekeeperService.validate(runId, output); + + return Map.of( + "gatekeeper_result", result, + "gatekeeper_status", result.severity() + ); +}); +``` + +Gatekeeper 不需要改造成 Agent。它负责确定性引用验真,是 Graph 中的普通 Java Node。 + +### 2.7 条件边是改造核心 + +```java +StateGraph graph = new StateGraph("diagnosis_workflow", diagnosisStateStrategies()) + .addNode("planner", plannerNode) + .addNode("executor", executorNode) + .addNode("gatekeeper", gatekeeperNode) + .addNode("verifier", verifierNode) + .addNode("retry_guard", retryGuardNode) + .addNode("composer", composerNode) + .addNode("fallback", fallbackNode) + + .addEdge(START, "planner") + .addEdge("planner", "executor") + + .addConditionalEdges( + "executor", + edge_async(state -> switch (stringValue(state, "executor_status")) { + case "COMPLETED" -> "gatekeeper"; + case "INVALID_OUTPUT" -> retryAvailable(state) ? "retry_guard" : "fallback"; + case "TOOL_BLOCKED", "FAILED" -> "fallback"; + default -> "fallback"; + }), + Map.of( + "gatekeeper", "gatekeeper", + "retry_guard", "retry_guard", + "fallback", "fallback" + ) + ) + + .addConditionalEdges( + "gatekeeper", + edge_async(state -> switch (stringValue(state, "gatekeeper_status")) { + case "REJECT" -> "fallback"; + default -> "verifier"; + }), + Map.of("verifier", "verifier", "fallback", "fallback") + ) + + .addConditionalEdges( + "verifier", + edge_async(state -> switch (stringValue(state, "verdict")) { + case "LOW_CONFID" -> retryAvailable(state) ? "retry_guard" : "composer"; + case "PASS", "REJECT" -> "composer"; + default -> "fallback"; + }), + Map.of( + "retry_guard", "retry_guard", + "composer", "composer", + "fallback", "fallback" + ) + ) + + .addConditionalEdges( + "retry_guard", + edge_async(state -> shouldRetry(state) ? "planner" : "composer"), + Map.of("planner", "planner", "composer", "composer") + ) + + .addEdge("composer", END) + .addEdge("fallback", END); +``` + +### 2.8 编译和执行 + +```java +SaverConfig saverConfig = SaverConfig.builder() + .register(new MemorySaver()) + .build(); + +CompileConfig compileConfig = CompileConfig.builder() + .recursionLimit(20) + .saverConfig(saverConfig) + .build(); + +CompiledGraph compiledGraph = graph.compile(compileConfig); + +RunnableConfig runConfig = RunnableConfig.builder() + // 一个 diagnosis_run 对应一个 Graph thread,避免同一 session 下多个 run 混状态。 + .threadId(runId) + .addMetadata("sessionId", sessionId) + .addMetadata("runId", runId) + .build(); + +Map initialState = Map.of( + "diagnosis_context", diagnosisContext, + "round", 1 +); + +Optional finalState = compiledGraph.invoke(initialState, runConfig); +``` + +`MemorySaver` 只适合进程内检查点,不等价于重启后可恢复的持久化。当前项目已有数据库 Trace,可以先把 Graph 用于流程控制;只有真正需要跨进程暂停恢复时,再引入持久 Checkpointer 或显式恢复模型。 + +### 2.9 Chat 与 AIOps 怎样共用 + +入口适配不同,公共 Graph 接收统一 `DiagnosisContext`: + +```java +DiagnosisContext chatContext = chatAdapter.from(question, history); +DiagnosisContext aiOpsContext = aiOpsAdapter.from(alertPayload); + +DiagnosisResult chatResult = diagnosisGraph.execute(chatContext, sessionId, runId); +DiagnosisResult aiOpsResult = diagnosisGraph.execute(aiOpsContext, sessionId, runId); + +return chatOutputAdapter.render(chatResult); +return aiOpsOutputAdapter.render(aiOpsResult); +``` + +Chat 的普通问答仍走轻量链路;复杂诊断才进入公共 Graph。AIOps 在入口阶段固定主告警范围,Graph 内部继续复用证据收集和验证。 + +### 2.10 人工中断怎么放 + +普通工具失败不需要 interrupt,条件边即可。只有确实需要等待人工输入或审批时才增加节点: + +```java +CompileConfig compileConfig = CompileConfig.builder() + .saverConfig(saverConfig) + .interruptBefore("human_review") + .build(); +``` + +恢复时使用相同 `threadId` 和 checkpoint 信息,并通过 `RunnableConfig.builder(oldConfig).resume()` 或状态更新接口继续。具体恢复协议需要结合当前版本做集成测试,不能只凭文档假设。 + +## 3. 深度裂变 + +
+ +### 🔍 搜索内化:真正的改造对象不是 Agent,而是控制权 + +官方文档和本地 `1.1.2.0` JAR 均证明 Graph 节点既可以是 LLM,也可以是普通 Java 代码,条件边由状态决定目标节点。`ReactAgent` 本身已经是一个子图,并提供 `asNode(...)` 适配能力。 + +因此“从 Multi-Agent 改成 Graph”并不准确。更准确的是:把跨 Agent 的状态转换从高层 Flow 抽出来,交给父 Graph;ReactAgent 继续作为子图存在。 + +文档页面示例存在 `OverAllStaste` 拼写错误,实际类名是 `OverAllState`。网站主分支可能领先于本地依赖,因此最终应以项目锁定版本的 JAR 签名和编译测试为准。 + +
+ +## 4. 实战指南 + +### 4.1 最小迁移顺序 + +1. 定义统一的节点结果状态,不先改 Prompt。 +2. 把现有 Planner、Executor、Verifier 调用包装为 Graph Node。 +3. 将 Gatekeeper 和固定降级模板做成普通 Java Node。 +4. 先迁移当前两轮 LOW_CONFID 循环。 +5. 为 Executor 阻断、非法结构、Gatekeeper REJECT 增加条件边。 +6. 保留原 Sequential 链路作为回退,完成行为对比后再删除。 +7. 最后再考虑 HITL、并行和专科 SubAgent。 + +### 4.2 常见反模式 + +- 把每个异常都交给 LLM Supervisor 决策。 +- Graph State 存放所有原始日志和完整思考过程。 +- 把 `no_evidence` 当作 Executor 执行失败。 +- 同一 session 的多个 run 共用一个 Graph `threadId`。 +- 一开始就设计几十个节点和完整 Incident 状态机。 +- 未验证父子图消息传播就直接大量使用 `ReactAgent.asNode(true, true)`。 +- 把 `MemorySaver` 当成生产级持久化。 + +### 4.3 ROI + +收益:条件分支可见、失败可测试、门禁不可跳过、Trace 更容易与节点对齐。 +代价:需要维护状态契约、条件边和父子图上下文,并增加 Graph 级测试。 +判断标准:如果当前只有固定顺序且失败直接结束,SequentialAgent 更简单;当局部重试、降级、HITL 和多入口策略已经出现时,StateGraph 的控制收益开始超过复杂度。 + +## 5. 温故知新 + +### FAQ + +1. **Graph 会替代 ReactAgent 吗?** 不会,ReactAgent 可以作为 Graph 节点或由适配节点调用。 +2. **为什么不用 Supervisor 控制失败?** 失败跳转是确定性规则,不应增加一次 LLM 决策。 +3. **no_evidence 是否直接走 fallback?** 不一定。合法的 no-evidence 引用仍要经过 Gatekeeper 和 Verifier。 +4. **Composer 是否必须是 Agent?** 正常表达可以是轻量 Agent,系统失败场景应保留固定模板。 +5. **threadId 用 sessionId 还是 runId?** 当前模型下优先用 runId,避免同一会话多次运行状态串扰。 +6. **什么时候需要 Checkpointer?** 需要暂停、恢复、查看历史 Graph 状态时;普通 Trace 持久化不自动等于 Graph Checkpoint。 +7. **能否直接使用 agent.asNode?** 可以,但需要验证输入、outputKey、messages 和父子 Checkpointer 行为。 +8. **AIOps 是否要单独一张 Graph?** 可以先共用诊断核心,入口范围策略和输出报告保持独立。 + +### 自测题 + +1. 为什么 Executor 工具阻断不应该由 Verifier 判断是否重试? +2. `ReplaceStrategy` 和 `AppendStrategy` 在诊断状态中分别适合什么数据? +3. 为什么合法 `no_evidence` 仍然需要 Gatekeeper? +4. SupervisorAgent 和 StateGraph 条件边的决策权有什么区别? +5. 为什么当前 Graph `threadId` 更适合使用 runId? +6. 什么情况下才应该增加 `interruptBefore`? + +### 参考资源 + +- https://java2ai.com/docs/frameworks/graph-core/core/core-library +- https://java2ai.com/docs/frameworks/graph-core/quick-start +- Spring AI Alibaba 本地依赖:`spring-ai-alibaba-graph-core:1.1.2.0` +- 当前项目:`ChatService`、`AiOpsService`、`ExecutorGatekeeperService`、`VerifierInputHook` diff --git a/mvp/issues/README.md b/mvp/issues/README.md index b7cbe2d..89c78d8 100644 --- a/mvp/issues/README.md +++ b/mvp/issues/README.md @@ -1,6 +1,6 @@ # MVP Issues 索引 -**更新日期**:2026-07-10 +**更新日期**:2026-07-16 **状态**:按活跃问题、设计笔记、RAG 问题集和已归档问题整理 ## 目录约定 @@ -16,6 +16,7 @@ | 名称 | 标题 | 严重程度 | 状态 | 文件 | |---|---|---|---|---| +| ISS-011 | Chat 诊断 StateGraph 编排改造 | 高 | 待实现 | [active/ISS-011-chat-diagnosis-stategraph-orchestration.md](active/ISS-011-chat-diagnosis-stategraph-orchestration.md) | | ISS-003 | MVP 设计与实现 Review 收敛 | 高 | 待规划 | [active/ISS-003-mvp-design-implementation-review.md](active/ISS-003-mvp-design-implementation-review.md) | | ISS-004 | Executor 域级检索水位控制 | 低 | 待规划 | [active/ISS-004-executor-domain-hard-limit.md](active/ISS-004-executor-domain-hard-limit.md) | | executor-evidence-attribution-hallucination | Executor 证据归因幻觉 | 高 | 待规划 | [active/executor-evidence-attribution-hallucination.md](active/executor-evidence-attribution-hallucination.md) | diff --git a/mvp/issues/active/ISS-011-chat-diagnosis-stategraph-orchestration.md b/mvp/issues/active/ISS-011-chat-diagnosis-stategraph-orchestration.md new file mode 100644 index 0000000..9423dd3 --- /dev/null +++ b/mvp/issues/active/ISS-011-chat-diagnosis-stategraph-orchestration.md @@ -0,0 +1,770 @@ +# ISS-011 Chat 诊断 StateGraph 编排改造 + +**状态**:待实现 +**严重程度**:高 +**发现时间**:2026-07-16 +**来源**:OnCall / Agent 编排模拟面试、当前 Chat 复杂诊断调用链复核 +**预计实施周期**:2–3 个工作日 + +--- + +## 1. 背景 + +当前复杂 Chat 诊断在 `ChatService.executeChatComplex(...)` 中采用两层编排: + +```text +SequentialAgent +-> Planner +-> Executor +-> Verifier + +ChatService 外层循环 +-> 解析 Verifier PASS / LOW_CONFID / REJECT +-> 决定 Composer、补证据或安全降级 +``` + +这种实现已经具备 Planner、Executor、Gatekeeper、Verifier、Composer 的职责拆分,但流程控制分散在: + +- `SequentialAgent` 固定顺序。 +- `VerifierInputHook` 内的 Gatekeeper。 +- `ChatService` 外层最多两轮循环。 +- Composer 和固定降级逻辑。 + +随着 no-evidence、工具能力阻断、Executor 非法输出、Gatekeeper REJECT、Verifier LOW_CONFID 等状态增加,当前高层 Flow 抽象已经无法清晰表达阶段短路和定向重试。 + +--- + +## 2. 当前问题 + +### 2.1 Executor 语义失败后仍可能继续 Pipeline + +如果 Executor 抛出异常,当前外层 `catch` 可以终止 Run;但如果 Executor 正常返回一段错误文本、非法结构或工具阻断结果,`SequentialAgent` 仍会继续调用 Verifier。 + +这会造成: + +- Verifier 消费无效输入。 +- 增加无意义模型调用和 Token。 +- Trace 中难以区分执行失败与证据不足。 +- 最终降级发生得过晚。 + +### 2.2 不能按失败阶段精确重试 + +当前 LOW_CONFID 重试会重新运行: + +```text +Planner -> Executor -> Verifier +``` + +但真实需求可能是: + +| 失败位置 | 期望恢复位置 | +|---|---| +| Planner 输出非法 | 重试 Planner | +| Executor 输出结构非法 | 保留计划,只重试 Executor | +| Gatekeeper 引用验真失败 | 直接进入固定安全降级 | +| Verifier LOW_CONFID | 将 missing evidence 返回 Planner 进行有限补证据 | +| 工具权限或能力阻断 | 不重试,直接降级 | + +当前 `SequentialAgent + 外层 round` 无法自然表达这些回边。 + +### 2.3 Gatekeeper 藏在 Verifier Hook 中 + +当前链路是: + +```text +Executor +-> VerifierInputHook +-> ExecutorGatekeeperService +-> Verifier +``` + +Hook 适合加工和拦截 Verifier 输入,但不适合根据 Gatekeeper 结果跳回 Executor。Gatekeeper 作为诊断质量门禁,应该成为显式编排节点。 + +### 2.4 编排逻辑已经形成隐式状态机 + +`ChatService` 当前同时负责: + +- 构建 Agent。 +- 执行 SequentialAgent。 +- 控制 LOW_CONFID 轮次。 +- 构造 retry context。 +- 调用 Composer。 +- 固定模板降级。 +- 持久化 Run、self_evaluation 和汇总指标。 + +继续增加 `if/else` 会进一步扩大 `ChatService` 职责,降低流程可读性和可测试性。 + +--- + +## 3. 改造目标 + +使用 Spring AI Alibaba `StateGraph` 接管跨 Agent 的状态转换,现有 ReactAgent、Prompt、工具、证据协议和持久化能力继续复用。 + +目标职责: + +```text +StateGraph +-> 控制顺序、条件边、重试、终止和降级 + +ReactAgent +-> 完成 Planner、Executor、Verifier、Composer 节点内部语义任务 + +Java Node +-> 完成 Gatekeeper、重试判断、固定降级等确定性任务 +``` + +本次不使用 SupervisorAgent 替代固定诊断 Pipeline。SupervisorAgent 留给未来多个专科 SubAgent 之间的动态路由。 + +--- + +## 4. 目标流程 + +```mermaid +flowchart TD + Start["START"] --> Planner["Planner"] + Planner -- "成功" --> Executor["Executor"] + Planner -- "失败" --> Fallback["Fallback"] + + Executor -- "结构有效" --> Gatekeeper["Gatekeeper"] + Executor -- "失败或阻断" --> Fallback + + Gatekeeper -- "PASS" --> Verifier["Verifier"] + Gatekeeper -- "LOW_CONFID 且存在已验真 binding" --> VerifiedInput["Build Verified Verifier Input"] + Gatekeeper -- "LOW_CONFID 且零条已验真 binding" --> Fallback + Gatekeeper -- "REJECT" --> Fallback + VerifiedInput --> Verifier + + Verifier -- "PASS" --> Composer["Composer"] + Verifier -- "REJECT" --> Composer + Verifier -- "LOW_CONFID 且有预算" --> EvidenceRetry["Prepare Evidence Retry"] + Verifier -- "LOW_CONFID 且无预算" --> Composer + Verifier -- "执行失败" --> Fallback + EvidenceRetry --> Planner + + Composer --> End["END"] + Fallback --> End +``` + +### 4.1 本期只保留 Verifier LOW_CONFID 补证据 + +Gatekeeper REJECT 本期不重试,直接进入 Fallback。 + +Verifier LOW_CONFID 保留当前有限补证据能力: + +```text +目标:补充缺失证据 +返回:Planner +默认上限:1 次 +``` + +只有同时满足以下条件时才允许补证据: + +- Verifier 提供了可执行的 missing evidence。 +- LOW_CONFID 不是由 Gatekeeper verdict ceiling 导致。 +- 所需工具存在且当前 Agent 有权限。 +- 仍有 Run 级预算。 +- 本 Run 尚未执行过补证据轮次。 + +Prepare Evidence Retry Node 由代码实现,负责把已确认事实、证据缺口、已调用工具、已执行 Query、当前 Skill 和剩余预算整理为受限 retry context,然后返回 Planner。 + +Planner 第二轮使用 `EVIDENCE_GAP_ONLY` 模式,只生成增量补证据计划,不允许重新开始完整诊断、扩大服务范围或重复已经成功执行的查询。 + +--- + +## 5. Gatekeeper REJECT 策略 + +**决策状态**:已确认,2026-07-16。 + +本期不增加 EvidenceRepairNode,也不让 Gatekeeper REJECT 返回 Executor 重试。任何 Gatekeeper REJECT 都直接进入固定安全降级。 + +原因: + +- 第一版先验证显式 Graph 编排和阶段短路,不同时引入新的模型节点。 +- 证据引用错误不一定可以安全修复,自动重新绑定可能制造第二次幻觉。 +- 当前最重要的是保证不可信 Executor 输出不能进入 Verifier,而不是提高 REJECT 的自动恢复率。 +- 直接 Fallback 的状态语义简单,便于先建立稳定的路由和测试基线。 + +处理流程: + +```text +Executor +-> Gatekeeper + -> PASS / LOW_CONFID:进入 Verifier + -> REJECT:直接 Fallback +``` + +Fallback 最终表达只保留: + +最终表达只保留: + +- 已确认且能够独立验证的事实。 +- 无法验证的证据引用问题。 +- 当前诊断限制。 +- 建议人工检查的下一步。 + +--- + +### 5.1 Executor INVALID_OUTPUT 策略 + +**决策状态**:已确认,2026-07-16。 + +`INVALID_OUTPUT` 定义为 Executor Agent 调用已经返回,但无法解析为最小 `executor_evidence_v2` 结构。它对应当前解析状态中的 `missing` 或 `malformed`,包括: + +- 输出为空。 +- 输出不是 JSON。 +- JSON 被截断或语法非法。 +- JSON 根节点不是对象。 +- `claims` 不是数组。 + +以下情况不属于 `INVALID_OUTPUT`: + +- 合法的空 claims 和 missing info。 +- 合法 no-evidence。 +- 结构可解析但 evidence binding 错误,此类进入 Gatekeeper。 +- 引用真实但证据无法支持 claim,此类进入 Verifier。 + +本期处理方式: + +```text +Executor INVALID_OUTPUT +-> 不重试 Executor +-> 不调用 Gatekeeper +-> 不调用 Verifier +-> 不调用模型 Composer +-> 固定安全 Fallback +``` + +原因: + +- 当前没有可信结构化结果可供后续门禁消费。 +- 重试有工具权限的 Executor 可能重复调用工具并产生第二套证据。 +- 本期优先建立清晰、可验证的控制流,不同时引入格式修复模式。 + +Trace 至少记录: + +- `executor_status = INVALID_OUTPUT`。 +- missing / malformed 及解析错误摘要。 +- 当前 Run 已发生的工具调用数量和成功情况。 +- 跳转 Fallback 的原因。 +- Gatekeeper、Verifier 未执行的原因。 + +Executor 原始输出只允许保留在受控审计中,不得直接作为用户答案。 + +--- + +### 5.2 Gatekeeper LOW_CONFID 策略 + +**决策状态**:已确认,2026-07-16。 + +Gatekeeper LOW_CONFID 表示 Executor 输出存在非致命结构或引用缺口,但不代表所有 evidence binding 都不可信。本期按已验真 binding 数量处理: + +```text +Gatekeeper LOW_CONFID + -> 至少一条 checked binding 为 pass + -> 代码过滤失败 binding + -> 构造 verified executor output + -> Verifier + -> 最终 verdict 最高为 LOW_CONFID + + -> 零条 checked binding 为 pass + -> 固定 Fallback +``` + +Verifier Input Builder 必须: + +- 只保留 Gatekeeper `checked_bindings.status=pass` 对应的 evidence binding。 +- 删除失败 binding。 +- 没有有效 binding 的 claim 不得作为确认性 claim 进入 Verifier。 +- 不向 Verifier 传递 Executor 未经验真的自由文本作为事实来源。 +- 将 `verifier_verdict_ceiling` 设置为 `LOW_CONFID`。 + +即使模型 Verifier 返回 PASS,编排层也必须将有效 verdict 限制为 LOW_CONFID。 + +LOW_CONFID 进入 Verifier 的目标是保留部分真实证据,而不是让 Verifier 修复 Gatekeeper 失败。 + +--- + +## 6. Graph State 最小契约 + +Graph State 保存跨节点需要的结构化状态,不复制完整工具原文和模型思考过程。 + +| 状态 | 作用 | 策略 | +|---|---|---| +| `diagnosis_context` | 当前 Query、history、sessionId/runId 等统一上下文 | Replace | +| `planner_plan` | Planner 结构化计划 | Replace | +| `planner_status` | Planner 执行状态 | Replace | +| `planner_mode` | NORMAL / EVIDENCE_GAP_ONLY | Replace | +| `executor_output` | `executor_evidence_v2` | Replace | +| `executor_status` | COMPLETED / INVALID_OUTPUT / TOOL_BLOCKED / FAILED | Replace | +| `gatekeeper_result` | 当前轮验真结果 | Replace | +| `gatekeeper_status` | PASS / LOW_CONFID / REJECT | Replace | +| `verified_executor_output` | 只保留 Gatekeeper 通过 binding 的 Verifier 输入 | Replace | +| `verified_binding_count` | 当前可供 Verifier 使用的 binding 数量 | Replace | +| `verifier_verdict_ceiling` | PASS 或 LOW_CONFID | Replace | +| `verifier_output` | Verifier 结构化结果 | Replace | +| `verdict` | PASS / LOW_CONFID / REJECT / FAILED | Replace | +| `evidence_retry_count` | 补证据轮次 | Replace | +| `retry_context` | missing evidence 和补查约束 | Replace | +| `final_answer` | 最终用户表达 | Replace | +| `failure_reason` | 当前确定性失败原因 | Replace | + +说明: + +- `agent_step` 和 `tool_invocation` 继续作为完整 Trace 数据源。 +- Graph State 只保存控制流程所需的最新状态。 +- `no_evidence` 属于合法 Executor 输出,不等于 `FAILED`,仍然进入 Gatekeeper 和 Verifier。 + +--- + +## 7. 与当前代码的关系 + +### 7.1 继续复用 + +- Planner、Executor、Verifier、Composer Prompt。 +- Skill Registry、Planner Skill metadata 和 Executor `read_skill`。 +- `ExecutorGatekeeperService` 现有规则。 +- `ToolTraceSummaryService`。 +- Executor、Verifier、Composer 结构化解析逻辑。 +- `diagnosis_run`、`agent_step`、`tool_invocation` 和 `self_evaluation`。 +- Token、耗时和工具调用数回填。 +- 当前 API 请求和响应协议。 + +### 7.2 需要调整 + +- `ChatService.executeChatComplex(...)` 不再创建 `SequentialAgent`。 +- Gatekeeper 从 `VerifierInputHook` 的隐式执行迁为显式 Graph Node。 +- LOW_CONFID 外层 `for round` 迁入 Graph 条件边。 +- Composer 与固定 Fallback 成为独立节点。 +- ChatService 只负责 Run 生命周期和调用 Graph,不继续承载细粒度状态机。 + +### 7.3 ReactAgent 接入策略 + +第一阶段使用适配 Node 显式调用当前 ReactAgent,不直接全面使用 `ReactAgent.asNode(...)`。 + +原因: + +- 当前 Agent 根据 history 和 retry context 动态构造 Prompt。 +- 当前使用自定义 outputKey 和解析逻辑。 +- Hook、ThreadLocal 和父子 Graph `messages` 需要先验证。 + +Graph State 和 Agent 输入契约稳定后,再评估直接使用 `asNode(false, false)`。 + +### 7.4 已确认:父子状态使用输入白名单隔离 + +**决策状态**:已确认,2026-07-16。 + +外层 Graph 维护完整工作流状态,但每个 ReactAgent 不直接接收 `parentState.data()`。每个 Adapter Node 必须完成一次显式状态投影: + +```text +父 Graph Shared State +-> Adapter Node 选择允许字段 +-> Agent 专用 Input DTO +-> ReactAgent 私有执行上下文 +-> Parser 提取标准输出 +-> 只更新父 Graph 指定状态 +``` + +各节点输入边界: + +| 节点 | 允许读取 | 不允许直接读取 | +|---|---|---| +| Planner | query、history、retry context、Skill metadata | Executor、Gatekeeper、Verifier 的历史输出 | +| Executor | query、planner plan、selected skill、executor retry context | Verifier 结论、其他轮次未筛选 messages | +| Gatekeeper | executor structured output、当前 run 的 tool invocation | Planner 思考、其他 run 工具记录 | +| Verifier | query、executor structured output、gatekeeper result、verified tool trace | Planner 计划、Executor 思考、未经 Gatekeeper 验真的自由文本 | +| Composer | allowed claims、missing info、recommended actions | 原始工具结果、Executor 原始答案和未允许 claims | + +实现约束: + +- 第一阶段不使用 `asNode(true, true)`。 +- `asNode(false, false)` 也不作为严格隔离手段,因为它主要减少 `messages` 传播,不能替代字段白名单。 +- 每个 Adapter Node 使用专用 Input DTO,不复用一个包含所有字段的通用 AgentInput。 +- ReactAgent 只向父 Graph 返回结构化 Node Output,不回传内部完整 `messages` 和推理过程。 +- 子 Agent 的执行标识按 `runId + agentName + attempt` 隔离,避免不同 Agent 和重试轮次复用内部状态。 +- 数据隔离由代码保证,Prompt 约束只作为补充。 + +### 7.5 已确认:编排审计使用独立 `orchestration_trace` + +**决策状态**:已确认,2026-07-16。 + +Graph 路由记录不写入 `self_evaluation`。本期在 `diagnosis_run` 新增 nullable JSON 字段 `orchestration_trace`,用于保存当前 run 的紧凑编排摘要。 + +职责边界: + +```text +self_evaluation +-> 结果质量、证据可信度以及 Gatekeeper / Verifier 等评估结果 + +orchestration_trace +-> 节点跳转、条件边原因、重试、降级和终止摘要 + +agent_step +-> 真实模型 Agent 调用 + +tool_invocation +-> 真实工具调用 +``` + +最小结构: + +```json +{ + "version": "stategraph-v1", + "transitions": [ + { + "from": "gatekeeper", + "to": "fallback", + "reason_code": "gatekeeper_reject", + "attempt": 1 + } + ], + "final_node": "fallback", + "termination_reason": "gatekeeper_reject", + "degraded": true, + "evidence_retry_count": 0 +} +``` + +约束: + +- 只写入 `diagnosis_run`,不写入会话级 `diagnosis_session`,因为 Graph 执行边界是 `runId`。 +- 使用稳定的 `reason_code`,不保存完整 Prompt、模型思考、工具原文或 Graph State 快照。 +- `transitions` 受 Graph 最大循环次数约束,不作为无限增长的事件日志。 +- Trace API 将其作为与 `self_evaluation` 平级的可选字段返回;历史 run 可以为 `null`。 +- 第一版不新增 orchestration 明细表,后续只有在需要跨 run 检索单次节点事件时再单独评估。 + +--- + +## 8. 分阶段实施计划 + +## 阶段 0:设计冻结 + +**预计时间**:1–2 小时 +**目标**:完成讨论,不修改运行时实现。 + +任务: + +- 确认 Graph State 最小契约。 +- 确认所有条件边和最大重试次数。 +- 确认 Fallback 用户表达。 +- 确认旧 Sequential 测试的替换范围。 + +完成标准: + +- 本 issue 中所有“待讨论决策”已有明确结论。 +- 不存在未定义的循环和终止路径。 + +## 阶段 1:Graph 骨架和路由测试 + +**预计时间**:2–3 小时 +**目标**:使用 Fake Node 验证 Graph API 和所有路径,不接真实模型。 + +任务: + +- 创建诊断 Graph State 和状态常量。 +- 创建 Graph 拓扑。 +- 创建 Fake Planner、Executor、Gatekeeper、Verifier、Composer。 +- 为所有条件边编写路由测试。 + +完成标准: + +- Executor FAILED 后不调用 Gatekeeper、Verifier。 +- Executor INVALID_OUTPUT 直接进入固定 Fallback,不触发任何模型重试。 +- 任意 Gatekeeper REJECT 直接进入 Fallback,不调用 Verifier。 +- LOW_CONFID 只允许一次 Planner 补证据。 +- Graph 不存在无限循环。 + +## 阶段 2:接入真实节点 + +**预计时间**:3–4 小时 +**目标**:将现有 Agent 和代码服务接入 Graph。 + +任务: + +- 创建 Planner、Executor、Verifier、Composer Node Adapter。 +- 创建显式 Gatekeeper Node。 +- 创建 Verifier Input Builder Node,过滤未通过的 binding。 +- 创建 Evidence Retry Prepare Node。 +- 创建固定 Fallback Node。 +- 保持现有 Prompt、工具和证据协议不变。 + +完成标准: + +- Agent Hook 和 ToolCallback 正常工作。 +- Gatekeeper 只执行一次,不再由 Verifier Hook 重复触发。 +- no-evidence 继续进入 Gatekeeper 和 Verifier。 +- Gatekeeper LOW_CONFID 只向 Verifier 提供已验真 binding,并限制 verdict 上限。 +- Verifier LOW_CONFID 只有存在可执行缺口时才返回 Planner。 +- 第二轮 Planner 只生成 `EVIDENCE_GAP_ONLY` 增量计划。 + +## 阶段 3:替换 ChatService 编排 + +**预计时间**:2–3 小时 +**目标**:复杂 Chat 诊断正式调用 StateGraph。 + +任务: + +- 将 `executeChatComplex(...)` 改为构造初始 Graph State 并执行 CompiledGraph。 +- 使用 `runId` 作为 Graph `threadId`,`sessionId` 作为 metadata。 +- 增加数据库迁移,为 `diagnosis_run` 添加 nullable JSON 字段 `orchestration_trace`。 +- 将 Graph 的节点跳转、reason code、重试和终止摘要持久化到当前 run。 +- 保留 Run 创建、状态更新、耗时、Token、工具数和 Eval 调用。 +- 将 Graph 最终状态映射为现有 `ChatResult`。 + +完成标准: + +- `/api/chat` 请求和响应协议不变。 +- `diagnosis_run.agent_flow` 仍为 `CHAT`。 +- Trace 明细继续归属于当前 runId。 +- Trace API 将 `orchestration_trace` 作为与 `self_evaluation` 平级的可选字段返回。 +- Executor 失败路径不产生 Verifier Agent step。 + +## 阶段 4:创建新测试体系 + +**预计时间**:3–4 小时 +**目标**:以 Graph 路径和外部行为为中心创建测试,不迁移旧固定顺序断言。 + +新测试: + +- `DiagnosisGraphWorkflowTest`:纯路由测试。 +- `DiagnosisGraphNodeContractTest`:节点输入输出契约。 +- `ChatServiceGraphIntegrationTest`:ChatService、Run、Trace、自评估集成。 + +必须覆盖: + +- PASS 正常路径。 +- Planner 失败。 +- Executor FAILED / TOOL_BLOCKED / INVALID_OUTPUT。 +- 合法 no-evidence。 +- Gatekeeper REJECT 直接降级。 +- Gatekeeper LOW_CONFID 且存在已验真 binding。 +- Gatekeeper LOW_CONFID 且不存在已验真 binding。 +- Verifier LOW_CONFID 补证据。 +- Gatekeeper ceiling 导致的 LOW_CONFID 不触发补证据。 +- LOW_CONFID 缺少可执行 missing evidence 时不触发补证据。 +- 第二次 LOW_CONFID 不再补证据。 +- Verifier REJECT 安全表达。 +- Composer 失败固定降级。 +- 同 session 多 run 隔离。 + +旧测试处理: + +- `ChatServiceSequentialAgentTest` 不作为迁移约束,可以由新测试整体替换。 +- `VerifierInputHookTest` 根据 Gatekeeper 迁移结果删除或改写。 +- `ExecutorGatekeeperServiceTest`、Controller、Trace 和 Eval 契约测试继续保留。 + +## 阶段 5:回归、清理和文档 + +**预计时间**:1–2 小时 +**目标**:完成行为确认并删除旧编排结构。 + +任务: + +- 运行新的单元和集成测试。 +- 运行相关 diagnosis eval baseline。 +- 对比关键 Trace 路径。 +- 删除 `SequentialAgent` 复杂诊断编排和旧外层 round 状态机。 +- 删除不再需要的 Gatekeeper Hook 隐式执行逻辑。 +- 更新架构文档和 issue 状态。 + +完成标准: + +- 新测试全部通过。 +- 关键 Eval 没有非预期退化。 +- ChatService 不再维护隐式诊断状态机。 +- 不存在新旧两套长期并行的重复实现。 + +--- + +## 9. 测试策略 + +本改造允许忽略并替换与 SequentialAgent 强绑定的历史测试,但不允许丢失已经建立的安全边界。 + +测试分层: + +```text +Graph 路由测试 +-> 不调用模型,验证条件边和循环上限 + +Node 契约测试 +-> 验证 Agent / Java Node 的状态映射 + +Chat 集成测试 +-> 验证外部协议、Run、Trace、orchestration_trace 和 self_evaluation + +Eval baseline +-> 验证最终证据表达没有退化 +``` + +不再把“所有 Agent 固定按顺序调用”作为正确性标准。正确性标准改为: + +> 根据当前节点状态进入预期路径,并且所有安全门禁和外部协议保持成立。 + +--- + +## 10. 行为与协议影响 + +### 外部协议 + +保持不变: + +- `/api/chat` 请求和响应结构。 +- `sessionId/runId` 语义。 +- `executor_evidence_v2`。 +- Verifier 和 Composer 输出契约。 + +### Trace API 加法式变化 + +- Trace 聚合响应新增与 `self_evaluation` 平级的可选字段 `orchestration_trace`。 +- 现有字段语义不变,历史 run 或未经过 StateGraph 的 run 返回 `null`。 +- 这是有意的内部审计持久化和 Trace 查询协议扩展,不影响 `/api/chat` 消费方。 + +### 明确的内部行为变化 + +- Executor 失败后不再调用 Verifier。 +- Executor INVALID_OUTPUT 不重试,直接进入固定 Fallback。 +- Agent 调用顺序不再固定。 +- Gatekeeper REJECT 直接安全降级,不进行自动修复和重试。 +- Gatekeeper LOW_CONFID 只在存在已验真 binding 时进入 Verifier,且 verdict 最高为 LOW_CONFID。 +- LOW_CONFID 根据 missing evidence 返回 Planner,而不是无差别完整重跑。 +- 第二轮 Planner 使用增量补证据模式,不重新执行完整 Playbook。 +- Agent step 数量、Token 和耗时可能减少。 +- Trace 需要能解释条件边选择和终止原因。 + +这些是有意的编排行为变化,不是对外协议变化。 + +--- + +## 11. 验收标准 + +### 编排 + +- [ ] Executor FAILED / TOOL_BLOCKED 后不会执行 Gatekeeper 和 Verifier。 +- [ ] Executor INVALID_OUTPUT 不重试,不执行 Gatekeeper、Verifier 和模型 Composer。 +- [ ] Executor 合法 no-evidence 会继续执行 Gatekeeper 和 Verifier。 +- [ ] Gatekeeper REJECT 直接进入 Fallback,不执行 Verifier。 +- [ ] Gatekeeper LOW_CONFID 且零条已验真 binding 时直接进入 Fallback。 +- [ ] Gatekeeper LOW_CONFID 且存在已验真 binding 时,Verifier 只接收通过校验的 binding。 +- [ ] Gatekeeper LOW_CONFID 路径的最终 verdict 不得升级为 PASS。 +- [ ] Verifier LOW_CONFID 最多触发一次 Planner 补证据。 +- [ ] LOW_CONFID 补证据循环受次数和 Run 总预算限制。 +- [ ] Gatekeeper verdict ceiling 导致的 LOW_CONFID 不触发补证据。 +- [ ] 不存在可执行 missing evidence 时不触发补证据。 +- [ ] 第二轮 Planner 只输出增量计划,不扩大诊断范围或重复成功查询。 +- [ ] Composer 失败使用固定模板结束。 + +### 证据和安全 + +- [ ] Gatekeeper 规则语义不放宽。 +- [ ] Verifier 只消费已验真证据。 +- [ ] no-evidence 不得表达为已排除或问题不存在。 +- [ ] REJECT 降级不泄漏 Executor 原始答案和未验证根因。 + +### 数据与审计 + +- [ ] Graph 使用 runId 作为 threadId。 +- [ ] Agent step、tool invocation 和 self_evaluation 仍绑定正确 runId。 +- [ ] `orchestration_trace` 只写入当前 diagnosis run,不污染其他 run 或 session 级数据。 +- [ ] `orchestration_trace` 不包含 Prompt、模型思考、工具原文和 Graph State 快照。 +- [ ] Trace 能展示实际节点路径、重试原因和终止原因。 +- [ ] 历史 run 的 `orchestration_trace=null` 时 Trace API 仍能正常返回。 +- [ ] Run 最终状态、答案、耗时、Token 和工具调用数正确回填。 + +### 工程质量 + +- [ ] 新 Graph 测试覆盖所有分支。 +- [ ] `ChatServiceSequentialAgentTest` 已由新测试替换。 +- [ ] 不保留长期重复的 Sequential 和 Graph 两套实现。 +- [ ] 数据库 schema 仅新增 `diagnosis_run.orchestration_trace` nullable JSON 字段。 +- [ ] `/api/chat` 和证据协议不变;Trace API 仅新增可选的 `orchestration_trace` 字段。 + +--- + +## 12. 风险与缓解 + +### ReactAgent 父子 Graph 状态污染 + +风险:直接使用 `asNode(...)` 可能导致 `messages`、outputKey 或 checkpoint 传播不符合预期。 + +缓解:第一阶段使用适配 Node 显式调用 Agent;完成契约测试后再考虑直接子图节点。 + +### Gatekeeper 重复执行 + +风险:显式 Gatekeeper Node 与现有 `VerifierInputHook` 同时执行。 + +缓解:迁移时明确 Gatekeeper 只有一个入口;Verifier Hook 只负责构造输入或被替换。 + +### LOW_CONFID 循环失控 + +风险:Verifier LOW_CONFID 持续返回 Planner,形成重复规划和工具调用。 + +缓解:保留当前最多一次补证据约束,并设置 Run 级总预算与 Graph recursion limit。 + +### Trace 与 Graph Checkpoint 语义混淆 + +风险:把当前数据库 Trace 当作 Graph 恢复检查点,或把 `MemorySaver` 当成持久化。 + +缓解:本期 Graph 只负责控制流程,不声明跨重启恢复;HITL 和持久 Checkpointer 单独立项。 + +--- + +## 13. Out of scope + +本 issue 暂不包含: + +- AIOps 迁移到公共 Graph。 +- Chat 和 AIOps 入口统一。 +- SupervisorAgent 和专科 SubAgent。 +- 人工中断、审批和恢复。 +- 持久化 Checkpointer。 +- 并行工具或并行 Agent。 +- EvidenceRepairNode 和 Gatekeeper REJECT 自动修复。 +- Gatekeeper REJECT 返回 Executor 的重试回边。 +- 修改 Prompt 业务语义。 +- 修改 `executor_evidence_v2`、Verifier、Composer 协议。 +- 除 `diagnosis_run.orchestration_trace` 外的其他数据库表或字段。 + +这些能力应在本 issue 完成后根据实际收益单独讨论。 + +--- + +## 14. 已冻结决策 + +以下决策均已确认,不再作为开放问题: + +- 第一阶段保留现有 ReactAgent,通过 Adapter Node 调用。 +- 父 Graph State 不直接传给 ReactAgent,使用每个 Agent 独立的 Input DTO 做白名单投影。 +- 第一阶段不直接依赖 `ReactAgent.asNode(...)` 实现父子编排。 +- Gatekeeper REJECT 本期不重试,直接进入 Fallback。 +- 本期不增加 EvidenceRepairNode。 +- Executor INVALID_OUTPUT 本期不重试,直接进入固定 Fallback。 +- Gatekeeper LOW_CONFID 有已验真 binding 时进入 Verifier,零条时直接 Fallback。 +- Gatekeeper LOW_CONFID 路径的最终 verdict 上限为 LOW_CONFID。 +- Verifier LOW_CONFID 的补证据路径返回 Planner,并使用 `EVIDENCE_GAP_ONLY` 增量规划模式。 +- Gatekeeper ceiling、无可执行缺口、无预算或已经补查时不触发 LOW_CONFID 重试。 +- Graph 路由摘要写入独立的 `diagnosis_run.orchestration_trace`,不耦合进 `self_evaluation`。 +- 不保留 Sequential / StateGraph 双链路配置开关;在专用 Git 分支完成实现和验收后,直接以 StateGraph 替换旧复杂诊断编排。 +- 实现期间允许代码处于分支内的阶段性状态,但合并前必须删除旧 Sequential 编排和不再使用的迁移代码,不形成长期双轨维护。 + +--- + +## 15. 相关文件 + +- `src/main/java/com/superbiz/agent/service/ChatService.java` +- `src/main/java/com/superbiz/agent/service/ExecutorGatekeeperService.java` +- `src/main/java/com/superbiz/agent/hook/VerifierInputHook.java` +- `src/main/java/com/superbiz/agent/service/ToolTraceSummaryService.java` +- `src/main/java/com/superbiz/agent/hook/AgentLoggingHook.java` +- `src/test/java/com/superbiz/agent/service/ChatServiceSequentialAgentTest.java` +- `src/test/java/com/superbiz/agent/service/ExecutorGatekeeperServiceTest.java` +- `src/test/java/com/superbiz/agent/hook/VerifierInputHookTest.java` +- `mvp/architecture/agent-orchestration.md` +- `mvp/architecture/harness-quality-gates.md` +- `knowledge_20260716_Spring_AI_Alibaba_Graph_诊断编排改造/` + +## 16. 参考文档 + +- https://java2ai.com/docs/frameworks/graph-core/core/core-library +- https://java2ai.com/docs/frameworks/graph-core/quick-start +- 当前依赖:`spring-ai-alibaba-graph-core:1.1.2.0` +- 当前依赖:`spring-ai-alibaba-agent-framework:1.1.2.0`