最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Spring AI Graph:零基础构建Supervisor首部曲_RAG子图与Supervisor路由实践避坑指南
时间:2026-05-30 14:15:01 编辑:袖梨 来源:一聚教程网
智能路由系统Supervisor通过统一入口自动分发请求至RAG/代码审查/通用对话三大子模块,实现精准任务处理。本文将深入解析其核心架构与实现细节。
一个入口,自动分发到 RAG / 代码审查 / 通用对话三个子图——这就是 Supervisor 做的事。
整体架构

系统根据用户输入内容自动判断任务类型,将请求智能路由至对应处理模块。
一、RAG 子图:intent→route→search→rerank→generate
1.1 状态定义
系统采用状态机模式进行流程控制,核心状态定义如下:
java
public class DreamSaaSOverAllState extends BaseOverAllState {
public static final String userInput = "userInput";
public static final String queryIntent = "queryIntent"; // keyword / semantic / hybrid
public static final String queryIntentSource = "queryIntentSource"; // llm / heuristic / default
public static final String searchResult = "searchResult";
public static final String rerankedResult = "rerankedResult";
public static final String finalAnswer = "finalAnswer";
public static final String errorMessage = "errorMessage";
public static final String route = "route"; // Supervisor 路由:ragRoute / reviewCodeRoute / ch@tRoute // ReAct 专用
public static final String reasoningTrace = "reasoningTrace";
public static final String reactRound = "reactRound";
public static final String reactAction = "reactAction"; // FINAL_ANSWER / CONTINUE_THINKING
public static final String reactDraftAnswer = "reactDraftAnswer";
}
状态键采用统一管理机制,确保子图间数据传递准确无误。其中route字段专用于路由控制,reactDraftAnswer则用于暂存ReAct模式的中间结果。
1.2 意图分析:keyword 还是 hybrid?
系统采用两级策略进行意图判断:
java
private ClassifyOutcome classify(String userInput) {
if (!StringUtils.hasText(userInput)) {
return new ClassifyOutcome(INTENT_HYBRID, "default");
}
try {
ChatClient client = ch@tClientSelector.selectClient(null);
String raw = client.prompt()
.system(SYSTEM_PROMPT)
.user("用户 query:n" + userInput)
.call()
.content();
String intent = normalizeIntent(raw);
return new ClassifyOutcome(intent, "llm");
} catch (RuntimeException ex) {
// LLM 挂了走规则兜底
String intent = heuristicClassify(userInput);
return new ClassifyOutcome(intent, "heuristic");
}
}static String heuristicClassify(String userInput) {
String q = userInput.trim();
// 短词 + 非问句 → keyword
if (q.length() <= 24 && !looksLikeQuestion(q)) {
return INTENT_KEYWORD;
}
// 含疑问词/问号 → hybrid
if (containsAny(q, "什么", "如何", "怎么", "为什么", "为何", "哪些", "是否", "吗", "?", "?")) {
return INTENT_HYBRID;
}
// 含中文 → hybrid
if (q.matches("(?s).*[u4e00-u9fff].*")) {
return INTENT_HYBRID;
}
// 纯英文数字 → keyword
if (q.matches("[A-Za-z0-9_.-/]+")) {
return INTENT_KEYWORD;
}
return INTENT_HYBRID;
}private record ClassifyOutcome(String intent, String source) {}
提示词设计简洁高效,确保输出标准化:
plaintext
你是检索路由分类器。根据用户 query 判断检索策略,只输出一个词:
- keyword:专有名词、产品名、类名、API、错误码、缩写、短术语 lookup
- hybrid:需要理解语义的一般问题、操作步骤、原因解释、对比分析
禁止输出其它文字。
1.3 条件边路由:addConditionalEdges
系统核心亮点在于动态路由机制:
java
stateGraph.addConditionalEdges(
RagQaGraphNames.INTENT_ANALYZE,
AsyncEdgeAction.edge_async(retrievalRouteNode), // 条件边决策器
Map.of(
IntentAnalyzeNode.INTENT_KEYWORD, RagQaGraphNames.KEYWORD_SEARCH,
IntentAnalyzeNode.INTENT_HYBRID, RagQaGraphNames.HYBRID_SEARCH));
addConditionalEdges方法接收三个关键参数:
- 当前节点标识:指定决策点
- 边动作实现:返回目标节点标识
- 映射关系:将决策结果映射到实际处理节点
java
public String apply(OverAllState state) {
String intent =
Objects.toString(
state.value(DreamSaaSOverAllState.queryIntent, IntentAnalyzeNode.INTENT_HYBRID), "")
.trim();
// 返回值匹配 addConditionalEdges 的 Map key,决定下一步去哪
return IntentAnalyzeNode.INTENT_KEYWORD.equals(intent)
? IntentAnalyzeNode.INTENT_KEYWORD
: IntentAnalyzeNode.INTENT_HYBRID;
}
该机制实现了节点执行后的动态路径选择,是图编排能力的核心体现。
1.4 完整 RAG 子图配置
java
@Bean
public CompiledGraph ragQaSubGraph() throws GraphStateException {
// 1. 定义状态策略(Replace 覆盖,不是累加)
KeyStrategyFactory keyStrategyFactory = () -> {
HashMap strategies = new HashMap<>();
strategies.put(userInput, new ReplaceStrategy());
strategies.put(queryIntent, new ReplaceStrategy());
strategies.put(searchResult, new ReplaceStrategy());
// ... 其他字段
return strategies;
}; StateGraph stateGraph = new StateGraph(keyStrategyFactory);
// 2. 注册节点
stateGraph.addNode(INTENT_ANALYZE, AsyncNodeAction.node_async(intentAnalyzeNode));
stateGraph.addNode(KEYWORD_SEARCH, AsyncNodeAction.node_async(keywordSearchNode));
stateGraph.addNode(HYBRID_SEARCH, AsyncNodeAction.node_async(hybridSearchNode));
stateGraph.addNode(RERANK, AsyncNodeAction.node_async(rerankNode));
stateGraph.addNode(GENERATE, AsyncNodeAction.node_async(generateNode)); // 3. 定义边
stateGraph.addEdge(START, INTENT_ANALYZE);
// 4. 条件边:intent 分析结果决定走哪条检索路径
stateGraph.addConditionalEdges(
INTENT_ANALYZE,
AsyncEdgeAction.edge_async(retrievalRouteNode),
Map.of(INTENT_KEYWORD, KEYWORD_SEARCH, INTENT_HYBRID, HYBRID_SEARCH));
// 5. 后续固定路径
stateGraph.addEdge(KEYWORD_SEARCH, RERANK);
stateGraph.addEdge(HYBRID_SEARCH, RERANK);
stateGraph.addEdge(RERANK, GENERATE);
stateGraph.addEdge(GENERATE, END); return stateGraph.compile();
}
实际数据处理流程如下:
plaintext
START → [intentAnalyze] →
├─ keyword → [keywordSearch] → [rerank] → [generate] → END
└─ hybrid → [hybridSearch] → [rerank] → [generate] → END
二、Supervisor 主图:意图分发到子图
2.1 IntentRouterNode:比 RAG 更粗粒度的分类
主图与子图的意图识别存在粒度差异:
- RAG子图:区分keyword/hybrid检索策略
- Supervisor:区分ragRoute/reviewCodeRoute/ch@tRoute任务类型
java
private static final String system_prompt = """
你是一个专业的意图分析助手,根据用户输入的问题分析用户的意图
输出下面枚举值的其中一个,不能包含其他内容
ragRoute、reviewCodeRoute、ch@tRoute,分别对应:文档检索、代码审查、大模型对话
必须严格遵守要求,只能输出 ragRoute、reviewCodeRoute、ch@tRoute 这三个值的其中一个
""";@Override
public Map<String, Object> apply(OverAllState state) {
// 规则优先,避免 LLM 波动
String ruleRoute = ruleBasedRoute(userInput);
if (ruleRoute != null) {
return Map.of(DreamSaaSOverAllState.route, ruleRoute);
}
// LLM 兜底
String content = client.prompt().system(system_prompt).user(userInput).call().content();
return Map.of(DreamSaaSOverAllState.route, normalizeIntent(content));
}static String ruleBasedRoute(String userInput) {
if (!StringUtils.hasText(userInput)) {
return null;
}
String q = userInput.trim();
if (containsAny(q, "查文档", "检索", "知识库", "RAG", "rag", "文档搜索", "搜文档", "查资料")) {
return SupervisorGraphNames.RAG_ROUTE;
}
if (containsAny(q, "代码审查", "code review", "review code", "审查代码", "CR ", " cr")) {
return SupervisorGraphNames.REVIEW_CODE_ROUTE;
}
return null; // 走 LLM
}
2.2 IntentRouteDispatcher:条件边决定去哪个子图
java
@Override
public String apply(OverAllState state) {
String route = state.value(DreamSaaSOverAllState.route, "");
if (route.contains(SupervisorGraphNames.RAG_ROUTE)) return SupervisorGraphNames.RAG_ROUTE;
if (route.contains(SupervisorGraphNames.REVIEW_CODE_ROUTE)) return SupervisorGraphNames.REVIEW_CODE_ROUTE;
return SupervisorGraphNames.CHAT_ROUTE; // 默认走通用对话
}
2.3 Supervisor 图配置
java
@Bean
public CompiledGraph supervisorGraph(
IntentRouterNode intentRouterNode,
IntentRouteDispatcher intentRouteDispatcher,
CodeReviewSubGraphStubNode codeReviewSubGraphStubNode,
@Qualifier("ragQaSubGraph") CompiledGraph ragQaSubGraph,
@Qualifier("generalChatSubGraph") CompiledGraph generalChatSubGraph) { StateGraph stateGraph = new StateGraph(keyStrategyFactory);
// 注册节点(子图作为节点嵌入)
stateGraph.addNode(DreamSaaSOverAllState.route, AsyncNodeAction.node_async(intentRouterNode));
stateGraph.addNode(SupervisorGraphNames.RAG_ROUTE, ragQaSubGraph); // RAG子图
stateGraph.addNode(SupervisorGraphNames.REVIEW_CODE_ROUTE, AsyncNodeAction.node_async(codeReviewSubGraphStubNode)); // 暂用占位
stateGraph.addNode(SupervisorGraphNames.CHAT_ROUTE, generalChatSubGraph); // Chat子图 // 定义边
stateGraph.addEdge(START, DreamSaaSOverAllState.route);
// 条件边:路由结果决定去哪个子图
stateGraph.addConditionalEdges(
DreamSaaSOverAllState.route,
AsyncEdgeAction.edge_async(intentRouteDispatcher),
Map.of(
SupervisorGraphNames.RAG_ROUTE, SupervisorGraphNames.RAG_ROUTE,
SupervisorGraphNames.REVIEW_CODE_ROUTE, SupervisorGraphNames.REVIEW_CODE_ROUTE,
SupervisorGraphNames.CHAT_ROUTE, SupervisorGraphNames.CHAT_ROUTE)); // 每个子图结束后统一到 END
stateGraph.addEdge(SupervisorGraphNames.RAG_ROUTE, END);
stateGraph.addEdge(SupervisorGraphNames.REVIEW_CODE_ROUTE, END);
stateGraph.addEdge(SupervisorGraphNames.CHAT_ROUTE, END); return stateGraph.compile();
}
设计亮点在于子图可作为节点嵌入主图,Supervisor只需关注任务分发,无需了解子图内部实现细节。
三、Chat 子图:ReAct 模式
Chat模块采用ReAct交互模式:
plaintext
START → [think] → [act] → [route] →
├─ CONTINUE → [think] (循环,最多2轮)
└─ FINAL_ANSWER → [format] → END
java
// Chat 子图配置
stateGraph.addNode(ChatSubGraphNames.THINK, AsyncNodeAction.node_async(ch@tReActThinkNode));
stateGraph.addNode(ChatSubGraphNames.ACT, AsyncNodeAction.node_async(ch@tReActActNode));
stateGraph.addNode(ChatSubGraphNames.FORMAT, AsyncNode
相关文章