
起因
我的代码审查Agent跑了两个月,单链路一直没问题。直到需要加RAG查最佳实践------同一个入口要走不同检索路径,单链路搞不定,必须上Graph。
但Spring AI Graph的资料太少了,官方文档偏概念,社区文章偏Demo。我花了2周从零啃到能写出第一个子图,这里先说整体架构和技术亮点,周末发完整代码。
最终形态:三层拼装的Supervisor Graph
我要搭的不是"一个Graph",而是一个三层拼装系统:
plaintext
yaml
Layer 3: Supervisor Graph(底层StateGraph手写)
意图识别 → 条件路由 → 子图分发
↓
Layer 2: SubGraph(高层API快搭 / 底层手写,按需选择)
├─ RAG问答子图:意图分类 → 条件路由 → keyword/hybrid双路 → rerank → 生成
├─ 代码审查子图:解析 → 并行扫描 → 风险评估 → HITL → 报告
└─ 通用对话子图:简单链路
↓
Layer 1: Node(最小执行单元,实现NodeAction/EdgeAction接口)
IntentAnalyzeNode / KeywordSearchNode / HybridSearchNode / ...
为什么要分三层?因为不同层的关注点不同:
- Layer 3 关心路由和编排,必须灵活,所以用底层StateGraph
- Layer 2 关心业务流程,能用高层API就别手写
- Layer 1 关心单一职责,每个Node只做一件事
这三层不是我自己发明的------LangGraph的图编排、Spring的DI分治、Erlang的Supervisor Tree,思想来源是这些。但用Spring AI Graph落地的时候,怎么拼、在哪层用什么API,没有现成答案。
3个架构亮点
亮点1:两层API混搭------不是全底层也不是全高层
Spring AI Alibaba提供两层API:
- 高层:SequentialAgent / ParallelAgent / LlmRoutingAgent
- 底层:StateGraph + Node + Edge
社区文章要么全用高层(搭个Demo没问题,复杂场景撑不住),要么全用底层(能干但开发慢)。
我的做法:Supervisor用底层手写,子图内部用高层快搭。
为什么?因为Supervisor需要两样东西,高层API给不了:
- 条件路由:根据意图动态分发到不同子图,LlmRoutingAgent只能做一次路由,不支持多级条件判断
- HITL中断恢复:高风险代码暂停等人工确认,高层API没有"暂停→等人→继续"的机制
java
dart
// Supervisor必须底层手写
StateGraph supervisor = new StateGraph(OverAllState.class)
.addNode("intent", intentRouterNode)
.addNode("code_review", codeReviewSubGraph)
.addNode("rag_qa", ragQaSubGraph)
.addNode("human_review", humanReviewNode)
.addConditionalEdge("intent", this::routeByIntent,
Map.of("code_review", "code_review", "rag_qa", "rag_qa"))
.addConditionalEdge("code_review", this::needHumanReview,
Map.of("yes", "human_review", "no", END));
但子图内部没必要全手写。代码审查的质量检查+安全扫描,ParallelAgent两行搞定。按需选择API层级,才是工程化的思路。
亮点2:有状态编排------从"一次性调用"到"可恢复流程"
大多数Agent Demo是这样的:输入 → 模型 → 输出,无状态,跑完就完了。
但真实场景不是一次调用。代码审查需要:解析→并行扫描→风险评估→可能要等人确认→生成报告。中间任何一步都可能失败,需要从断点恢复而不是从头重跑。
StateGraph的OverAllState就是这个问题的解法------每个Node执行完,状态持久化到共享容器。下一个Node从容器读状态,不依赖上游传递。
java
ini
// 每个子图注册自己需要的key,全部ReplaceStrategy
KeyStrategyFactory keyStrategyFactory = () -> {
HashMap<String, KeyStrategy> strategies = new HashMap<>();
strategies.put(DreamSaaSOverAllState.userInput, new ReplaceStrategy());
strategies.put(DreamSaaSOverAllState.queryIntent, new ReplaceStrategy());
strategies.put(DreamSaaSOverAllState.searchResult, new ReplaceStrategy());
strategies.put(DreamSaaSOverAllState.finalAnswer, new ReplaceStrategy());
return strategies;
};
Replace策略保证下游读到的始终是最新值。子图按需注册key,不跨层------RAG子图不需要riskLevel,代码审查子图不需要searchResult。
这和Java里的Event Sourcing是同一个思路:状态是第一公民,流程是状态驱动的。
亮点3:HITL不是confirm,是架构级的断点设计
"Human-in-the-Loop"很容易被理解成"弹个确认框"。但HITL在Graph架构里不是确认框,是架构级的断点。
区别在哪?
- 确认框:流程继续跑,弹个框让人点一下
- 架构断点:流程暂停,状态持久化,等人回复后从断点恢复
这意味着:
- Agent不会在等人的时候占着资源
- 人工确认的结果会写回State,影响后续路由
- 断点可以跨Session------今天确认不了,明天接着来
代码审查场景:检测到SQL注入风险 → 暂停 → 等安全工程师确认 → 确认后才进报告生成。不是AI说了算,架构层面保证人可控。
这个设计目前Spring AI Graph的Checkpoint机制还在完善中,我用的是手动实现的State快照方案,中篇会详细展开。
技术栈版本
spring-ai-bom 1.1.6 + spring-ai-alibaba 1.1.2.2。API还在迭代,跟着做的话版本对齐是前提。
连载计划
- 上篇(本周末):【Spring AI Graph:从0到Supervisor·上】条件路由与RAG子图
- 中篇(下周末):【Spring AI Graph:从0到Supervisor·中】HITL与并行执行
- 下篇(6/6):【Spring AI Graph:从0到Supervisor·下】Supervisor三层拼装
每篇都放完整代码,不是snippet也不是伪代码。读者看完能直接写出同样的东西。