feat(graph): add diagnosis routing skeleton

This commit is contained in:
zhuyongxin
2026-07-17 11:17:13 +08:00
parent 581daffdad
commit 42ba204532
26 changed files with 2455 additions and 0 deletions
@@ -0,0 +1,169 @@
## Context
阶段 0 已归档 StateGraph 设计基线,当前仓库仍没有 Graph 实现。阶段 1 只引入可编译、可用 Fake Node 执行的内部骨架,不连接 Spring Bean、ChatService、真实 Agent 或数据库。
锁定依赖 `spring-ai-alibaba-graph-core:1.1.2.0` 已通过本地 JAR 验证:
- `StateGraph.addNode(... AsyncNodeActionWithConfig)`
- `StateGraph.addConditionalEdges(... AsyncEdgeActionWithConfig, mappings)`
- `CompileConfig.builder().recursionLimit(...)`
- `KeyStrategyFactoryBuilder`、`ReplaceStrategy`、`AppendStrategy`
- `CompiledGraph.invoke(input, RunnableConfig)`
- `RunnableConfig.builder().threadId(...)`
阶段 2 将消费 Node action ports,阶段 3 将消费 compiled graph。阶段 1 自身没有生产调用方。
## Goals / Non-Goals
**Goals:**
- 建立明确的状态、状态枚举、节点、route 和 reason code 常量。
- 建立 config-aware Node action ports 和可编译 Graph 工厂。
- 以冻结计数和 guard 实现完整条件边。
- 验证 AppendStrategy 和实际 CompiledGraph 行为。
- 从有界 events 构造确定性的 orchestration trace。
- 用 Fake Node 测试所有路由和终止边界。
**Non-Goals:**
- 不接 ReactAgent、Gatekeeper Service、ToolTrace 或 Prompt。
- 不修改 ChatService、Hook、ThreadLocal 或生产 Spring 配置。
- 不持久化 trace,不修改 DB/DTO/API。
- 不删除旧 Sequential 实现或测试。
- 不运行 Maven E2E。
## Decisions
### 1. Package and class boundaries
新代码位于 `com.superbiz.agent.graph.diagnosis`:
| 类型 | 职责 |
|---|---|
| `DiagnosisGraphState` | 26 个 state key、默认 Replace + events Append strategy、typed reads |
| `DiagnosisGraphStatus` | Planner/Executor/Gatekeeper/Verifier/Composer status、Verdict、PlannerMode |
| `DiagnosisGraphTopology` | Node id、route key、reason code 常量 |
| `DiagnosisGraphActions` | 八个 `AsyncNodeActionWithConfig` ports |
| `DiagnosisGraphRouter` | 纯确定性 edge guard,不修改 state |
| `DiagnosisGraphFactory` | 注册 Node/Edge、retry wrapper、evidence retry wrapper、recursion limit |
| `OrchestrationEvent` | node/outcome/reasonCode/attempt |
| `OrchestrationTransition` | from/to/reasonCode/attempt |
| `DiagnosisOrchestrationTrace` | version/transitions/finalNode/terminationReason/degraded/evidenceRetryCount |
| `DiagnosisOrchestrationTraceBuilder` | events → transitions/summary |
Fake Node 和 script fixture 只存在于 test 源集。
替代方案:把 router 和状态读取放进 ChatService 或每个 Adapter。拒绝,因为会复制 guard 并阻碍 Fake Node 独立验证。
### 1.1 Direct dependency ownership
代码直接 import Graph Core API,因此 `pom.xml` 显式声明 `com.alibaba.cloud.ai:spring-ai-alibaba-graph-core`。版本继续由现有 Spring AI Alibaba BOM 管理为 1.1.2.0,不重复写版本。依赖 Agent Framework 的传递依赖虽可编译,但会让上游依赖图调整无意中破坏本模块,拒绝。
### 2. State strategy and typed reads
`KeyStrategyFactoryBuilder.defaultStrategy(new ReplaceStrategy())`,仅对 `orchestration_events` 使用 `new AppendStrategy()`。Node 每次返回 `List.of(event)`,Graph 合并后保持有序列表。
typed reads 对 absent、null、错误类型和未知 enum 返回安全默认,不抛出边路由异常。未知 status/verdict 的路由默认 Fallback。
### 3. Config-aware action ports
所有 port 使用 `AsyncNodeActionWithConfig`,即使 Fake Node 当前只需要 state。原因是阶段 2 必须读取 `RunnableConfig.threadId/metadata` 并向 Agent 传递 run context;现在锁定接口可以避免随后重写 Graph topology。
`DiagnosisGraphActions` 构造时对八个 action 做 non-null 校验。
### 4. Retry counter ownership
Edge 只选 route,不修改 state。Factory 用 wrapper 管理计数:
- Planner 重入前,如果上次 `planner_status` 是 INVALID_OUTPUT/RETRYABLE_FAILED,count + 1。
- Verifier/Composer 同理。
- 第一次失败后的 state count 为 0,允许 self-loop;重入执行后 count 为 1,再失败则 Fallback。
- Evidence Retry Node 完成时将 `evidence_retry_count + 1`、`planner_retry_count=0`、`planner_mode=EVIDENCE_GAP_ONLY`。
- Verified Input Node 完成时重置 `verifier_retry_count=0`,因为它产生新的 verified input。
- 技术重试不修改 evidence count。
替代方案:由每个 Fake/real action 自行维护计数。拒绝,因为遗漏会造成无限 self-loop,骨架必须拥有控制计数。
### 5. Conditional routing
| Source | Route keys |
|---|---|
| Planner | executor / retry_planner / fallback |
| Executor | gatekeeper / fallback |
| Gatekeeper | verified_input / fallback |
| Verifier | composer / retry_verifier / evidence_retry / fallback |
| Composer | end / retry_composer / fallback |
Verified Input 固定到 Verifier,Evidence Retry 固定到 Planner,Fallback 固定到 END。
Verifier evidence-retry guard 同时要求:
- `verifier_status=COMPLETED`
- `effective_verdict=LOW_CONFID`
- `verifier_verdict_ceiling=PASS`
- `verifier_output.facts_checked` 至少一项具有非空 fact 且 verification 为 `no_evidence` 或 `indirect_support`
- `evidence_retry_count < 1`
否则 LOW_CONFID 到 Composer。router 只检查现有 Verifier 输出,不选择工具或构造 retry context;后者属于阶段 2 Evidence Retry Node。
### 6. Recursion limit
合法最坏路径低于 20 次 Node 执行;compile recursion limit 固定 32。测试断言 compiled graph 的 max iterations/compile config,并覆盖第二次技术失败和第二次 LOW_CONFID 均终止。
recursion limit 是最后保险,不替代业务 guard。
### 7. Event and trace contract
`OrchestrationEvent` 构造时拒绝 blank node/outcome/reason 或 attempt<1。每个 Fake/真实 Node attempt 返回一个 event;AppendStrategy 累积实际路径。
Builder:
1. 要求 events 非空且类型正确。
2. 相邻 events 生成 transition,transition reason/attempt 取 source event。
3. 最后 event 决定 finalNode 和 terminationReason。
4. final node 为 Fallback 时 degraded=true;其他质量由 effective verdict 表达。
5. evidence retry count 从 state 读取。
6. `toMap()` 使用冻结 snake_case 字段,transition 同样提供 map。
不从应用日志推导路径,也不在 state 维护第二份 transitions。
### 8. Test architecture
`DiagnosisGraphRoutingTest` 使用真实 `CompiledGraph` + scriptable Fake actions:
- 每个 node 有 outcome 队列和调用计数。
- Fake action 只输出本 node 的状态字段、必要 guard payload 和一个 event。
- 每个用例使用唯一 `RunnableConfig.threadId`。
- 断言 node sequence、调用次数、最终 state、events、trace transitions。
- 参数化覆盖同类技术失败,独立用例覆盖 evidence retry/counter reset 和 unknown fail-closed。
`DiagnosisOrchestrationTraceBuilderTest` 覆盖空 events、非法 event、顺序、fallback degraded 和 map shape。
## Interface Impact
- 等级:L2 internal interface。
- 新接口消费者:阶段 2 adapters、阶段 3 orchestrator、阶段 4 test suite。
- 构建消费者:Maven 直接声明 Graph Core,版本仍由既有 BOM 统一管理。
- 当前生产消费者:无。
- 外部 API/DTO/数据库/状态:无变化。
- 兼容策略:后续 actions 实现 ports;Graph State 不直接传给 Agent。
- 回滚:revert 本阶段提交,无数据迁移。
## Risks / Trade-offs
- [Graph merge 行为与假设不同] → 使用真实 CompiledGraph 单元测试,不 mock StateGraph。
- [计数 wrapper 与 action 更新冲突] → wrapper 最后写入控制计数,actions 不拥有 retry counters。
- [Verifier gap 解析过早耦合] → router 只识别现有 facts_checked 最小字段,retry context 构造留到阶段 2。
- [未接生产入口被误认为完成] → proposal/spec/acceptance 明确 skeleton-only,阶段 3 才切换。
- [事件无限追加] → 所有业务循环有硬上限且 recursion limit=32。
## Migration Plan
1. 添加纯 Java 状态、router、actions、factory 和 trace types。
2. 先用 Fake actions 编译和执行完整 Graph。
3. 阶段 1 archive + commit 后,阶段 2 基于 ports 实现真实 nodes。
4. 如需回滚,revert 阶段 1 commit;当前生产路径不受影响。
## Open Questions
无。