老王发量很多,且阳光自信,一看就是刚入职没两年的热血青年,但确实有面试官的威严。
这是我的第一场面试,说不紧张那是不可能的。
但提前已经和同频道的宿友互面了两周,面对老王的压力,自认为能扛得住。😄
“我看你简历上连个Agent项目都没有,你难道不知道现在是AI时代吗?”老王第一次张嘴就开始给压力。
我倒是一点都没怂:“LangGraph4J+SpringAI做的这个工作流编排就是啊,王哥,你仔细看。”
“你小子,挺能被压力嘛,我就是测试一下你的心态。”老王一下子和蔼了起来,我们之间的感情好像升温了一般,空气也变得微妙了起来~
“王哥,你继续,我对 PaiAgent这个项目还是自信的,一手Vibe Coding完成的,在GitHub上也有快200 star 了。”
content
01、LangGraph4j 中的 State 是干什么用的?
“先聊聊 State,你们项目里 LangGraph4j 的 State 是怎么用的?”
我说:“王哥,State 在 LangGraph4j 里是整个工作流的‘记忆中枢’。”
你可以把它理解成一个贯穿所有节点的数据背包——每个节点执行完,都把结果往这个背包里塞一份,下一个节点从背包里拿上一个节点的输出来用。
在 PaiAgent 里,我们设计了一个 WorkflowState 类,里面有几个核心字段:
@Data
public class WorkflowState {
private String currentNodeId;
private Map globalContext = new HashMap<>();
private Map nodeOutputs = new HashMap<>();
private String status = "RUNNING";
private String errorMessage;
private Long startTime;
private String inputData;
}
currentNodeId 记录当前执行到哪个节点了,nodeOutputs 存每个节点的执行结果,globalContext 用来放跨节点的共享数据。
不过实际实现中,我们并没有直接把 WorkflowState 塞进 LangGraph4j 的 StateGraph。
LangGraph4j 要求用 AgentState,底层其实是一个 Map。所以我们在 StateManager 里做了一层转换——初始化的时候把 inputData、currentInput、nodeOutputs、status 这些字段放到一个 Map 里,传给 LangGraph4j:
public Map initializeState(String inputData) {
Map state = new HashMap<>();
state.put("inputData", inputData);
Map currentInput = new HashMap<>();
currentInput.put("input", inputData);
state.put("currentInput", currentInput);
state.put("nodeOutputs", new HashMap<>());
state.put("status", "RUNNING");
state.put("startTime", System.currentTimeMillis());
return state;
}
老王点头:“那 WorkflowState 和 AgentState 之间是什么关系?”
我说:“WorkflowState 是我们自己定义的业务模型,方便序列化和持久化。AgentState 是 LangGraph4j 框架的状态模型。StateManager 负责两者之间的转换,初始化时 WorkflowState 转 Map 给 LangGraph4j,执行完再从 Map 提取回 WorkflowState 用来保存执行记录。”
老王又追了一句:“那节点执行失败了,State 里怎么处理?”
我说:“NodeAdapter 里有错误处理逻辑。如果某个节点抛了异常,会把 State 的 status 设为 FAILED,errorMessage 记录具体的报错信息。LangGraphWorkflowEngine 在收到最终状态后,通过 stateManager.isSuccessful(finalState) 检查 status 字段,只有 SUCCESS 或 RUNNING 才算正常结束。FAILED 状态会触发 WORKFLOW_COMPLETE 事件带上失败信息,同时这条执行记录也会以 FAILED 状态写入数据库,方便后续排查。”
“整个 State 的生命周期就是:初始化 -> 节点依次更新 -> 最终状态持久化。每个环节出了问题都有兜底。”
02、节点之间的图是怎么构建的?参数怎么传递?
老王继续追问:“图的构建过程讲讲,你们的 GraphBuilder 具体做了哪些事?”
我说:“GraphBuilder 做三件事,加节点、加边、设入口出口。”
先创建一个 StateGraph,然后遍历工作流配置里的所有节点,逐个用 NodeAdapter 适配成 AsyncNodeAction,注册到图里:
public CompiledGraph buildGraph(WorkflowConfig config,
Consumer eventCallback) throws Exception {
StateGraph graph = new StateGraph<>(AgentState::new);
addNodes(graph, config.getNodes(), eventCallback);
addEdges(graph, config.getEdges());
setEntryAndExit(graph, config.getNodes(), config.getEdges());
return graph.compile();
}
入口和出口的识别也讲究——不是硬编码的,而是通过边的拓扑关系动态查找。没有入边的节点就是入口,没有出边的节点就是出口:
private WorkflowNode findEntryNode(List nodes, List edges) {
for (WorkflowNode node : nodes) {
boolean hasIncomingEdge = edges.stream()
.anyMatch(edge -> edge.getTarget().equals(node.getId()));
if (!hasIncomingEdge) return node;
}
return null;
}
找到入口后,加一条 StateGraph.START -> entryNode 的边;找...
真诚点赞 诚不我欺
回复