ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

第 17 章 · 编排 AI 流程:StateGraph 节点、条件边与意图路由

2026/9/24 7:39:38 拓冰建站 浏览量
第 17 章 · 编排 AI 流程:StateGraph 节点、条件边与意图路由 版本LangGraph4j 1.8.25目标在StateGraph上加节点、普通边、条件边跑通一张意图三路分支图。本章不接模型classify用规则写死只练拓扑。StateGraph是可变蓝图挂 schema、节点、边再compile()得到可执行的CompiledGraph。START/END是虚拟锚点业务逻辑只写在节点里。17.1 直线图节点 普通边addNode接收的是异步节点。同步逻辑写成NodeAction再用AsyncNodeAction.node_async包一层和第 15 章一样。import static org.bsc.langgraph4j.StateGraph.END; import static org.bsc.langgraph4j.StateGraph.START; import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async; ​ NodeActionMyState retrieve state - Map.of(docs, search(state.Stringvalue(query).orElse())); ​ NodeActionMyState generate state - Map.of(answer, ok); ​ var graph new StateGraph(schema, MyState::new) .addNode(retrieve, node_async(retrieve)) .addNode(generate, node_async(generate)) .addEdge(START, retrieve) .addEdge(retrieve, generate) .addEdge(generate, END);普通边是固定下一跳source → target。同一 source 挂多条无条件边编译后会扇出成并行不是二选一。互斥分支必须用条件边。17.2 条件边返回映射表的 keyEdgeAction根据状态返回一个String。这个字符串是映射表里的key框架再用映射翻成真正的节点 id。import static org.bsc.langgraph4j.action.AsyncEdgeAction.edge_async; ​ EdgeActionMyState byIntent state - state.Stringvalue(intent).orElse(unknown); ​ graph.addConditionalEdges(classify, edge_async(byIntent), Map.of( faq, faq, // key → 节点 id order, order, unknown, fallback ));本例里 key 和节点 id 同名读起来方便也可以写成faq → faq_handler只要EdgeAction返回的 key 能在表里查到。常用挂法对照API用途addNode(id, AsyncNodeAction)普通节点同步用node_async包addNode(id, AsyncNodeActionWithConfig)节点里要读RunnableConfigaddEdge(source, target)固定下一跳addConditionalEdges(source, edge, mappings)按状态选下一跳Command可以同时指定「下一跳 状态更新」new Command(fallback, Map.of(reason, unrecognized));条件边若用AsyncCommandAction返回的是Command用EdgeAction/AsyncEdgeAction时只负责选路状态仍由节点返回的 partial Map 更新。17.3 意图路由一整张能跑的图上图就是下面这份代码classify写出intent条件边按faq/order/unknown三路各自接到END。import static org.bsc.langgraph4j.StateGraph.END; import static org.bsc.langgraph4j.StateGraph.START; import static org.bsc.langgraph4j.action.AsyncEdgeAction.edge_async; import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async; ​ import java.util.ArrayList; import java.util.List; import java.util.Map; ​ import org.bsc.langgraph4j.CompiledGraph; import org.bsc.langgraph4j.StateGraph; import org.bsc.langgraph4j.action.EdgeAction; import org.bsc.langgraph4j.action.NodeAction; import org.bsc.langgraph4j.state.AgentState; import org.bsc.langgraph4j.state.Channel; import org.bsc.langgraph4j.state.Channels; ​ public class IntentRoutingGraph { ​ public static class RouteState extends AgentState { public static final String INTENT intent; public static final String REPLY reply; public static final String LOGS logs; ​ public static final MapString, Channel? SCHEMA Map.of( INTENT, Channels.base(() - ), REPLY, Channels.base(() - ), LOGS, Channels.appenderWithDuplicate(ArrayList::new) ); ​ public RouteState(MapString, Object initData) { super(initData); } } ​ public static void main(String[] args) throws Exception { NodeActionRouteState classify state - { String text state.Stringvalue(text).orElse(); String intent; if (text.contains(订单) || text.contains(物流)) { intent order; } else if (text.contains(怎么) || text.contains(什么)) { intent faq; } else { intent unknown; } return Map.of( RouteState.INTENT, intent, RouteState.LOGS, List.of(classify: intent) ); }; ​ NodeActionRouteState faq state - Map.of( RouteState.REPLY, 这是 FAQ 答复, RouteState.LOGS, List.of(faq) ); NodeActionRouteState order state - Map.of( RouteState.REPLY, 这是订单答复, RouteState.LOGS, List.of(order) ); NodeActionRouteState fallback state - Map.of( RouteState.REPLY, 没听清意图请换种说法, RouteState.LOGS, List.of(fallback) ); ​ EdgeActionRouteState byIntent state - state.Stringvalue(RouteState.INTENT).orElse(unknown); ​ CompiledGraphRouteState compiled new StateGraph(RouteState.SCHEMA, RouteState::new) .addNode(classify, node_async(classify)) .addNode(faq, node_async(faq)) .addNode(order, node_async(order)) .addNode(fallback, node_async(fallback)) .addEdge(START, classify) .addConditionalEdges(classify, edge_async(byIntent), Map.of( faq, faq, order, order, unknown, fallback)) .addEdge(faq, END) .addEdge(order, END) .addEdge(fallback, END) .compile(); ​ compiled.invoke(Map.of(text, 我的订单到哪了)) .ifPresent(s - { System.out.println(s.value(RouteState.INTENT).orElse()); System.out.println(s.value(RouteState.REPLY).orElse()); System.out.println(s.value(RouteState.LOGS).orElse(List.of())); }); // → order // → 这是订单答复 // → [classify:order, order] } }读代码时对照上图核对三件事classify只负责写出intent不自己跳转。byIntent读intent返回的字符串必须能在映射表里找到。三条业务边各自addEdge(..., END)图不会悬空。需要核对拓扑时System.out.println(compiled.getGraph( org.bsc.langgraph4j.GraphRepresentation.Type.MERMAID, Intent Routing).content());17.4 常见错误现象原因与处理本想二选一却两个分支都跑了互斥写成了多条addEdge改成addConditionalEdges路由报错或走不到目标节点映射漏了 key或EdgeAction返回值与 key 对不上图悬空、编译失败某条分支没接到后续节点或END条件边返回了节点 id却不在映射表里返回的应是key把该 id 配进映射或让返回值等于已有 key直线图先compile跑通再改成条件三路每步导出 Mermaid看边有没有接错。