LangGraph4j : 底层本质上是一个 类型安全的状态机 + 有向图执行引擎
有向图:
- State : 状态 : 整个图运行时候的上下文,,所有节点
共享同一个状态对象,节点之间不传参数,,而是只修改State - Node : 节点 :每个节点的执行逻辑,,是个
Function<S,S>,就是传入一个 State,执行完成之后返回一个新的State - Edge : 边 ,,分为,有条件的边,,无条件边,,有条件边(根据状态决定要去哪里,通过这个状态去判断接下来要干什么)
- Graph : 图 : 把所有的节点连接起来,,是一个
执行器
LangGraph4j,是状态驱动执行,执行流程由状态决定,,,也叫显示状态驱动模型
为什么要用State: 因为可以将State状态序列化到数据库中,,如果系统崩了,可以重新加载
ai的agent,,天然就是一个有向图,就是 ai思考 ---》 决策 ---》 调用工具 ---》 调用工具是否成 ---》 再思考,, 就是一个天然的 有向图循环
LangGraph4j极简版实现:
把业务计算(Node节点的执行逻辑)和 流程控制决策(Edge的决策逻辑)分离开来,,,Node负责计算,,Edge负责决策,, 这样职责清晰,,满足单一职责原则SRP,,,

llm,,传入action,,根据router的判断逻辑,去判断要 调工具,,还是直接回复,,
Action有很多种状态,,比如结束状态,工具调用状态,默认执行状态,,抛出异常的状态,,重试的状态,根据这些状态,,去执行不同的逻辑,
java
package com.cj.graph02;
public enum Action {
CALL_TOOL, // 工具调用
CONTINUE, // 下一个
END, // 结束
RETRY, // 重试
ERROR;// 错误
}
java
package com.cj.graph02;
public class AgentState {
public String input;
public String result;
// 当前节点输出的动作信号,,, 后面根据这个action去判断要走哪里
public Action action;
// 模拟对话轮数
public int step=0;
// 新增错误信息
public Exception error;
// 重试次数
public int retryCount = 0;
// 最大重试次数
public int maxRetry = 3;
@Override
public String toString() {
return "AgentState{" +
"input='" + input + '\'' +
", result='" + result + '\'' +
", action=" + action +
", step=" + step +
", error=" + error +
", retryCount=" + retryCount +
", maxRetry=" + maxRetry +
'}';
}
}
java
public interface Node <S>{
/**
* 修改state,, 设置action
* @param state
* @return
*/
S execute(S state);
}
java
package com.cj.graph02;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class MiniLangGraph <S extends AgentState>{
// 所有的节点
private final Map<String,Node<S>> nodes = new HashMap<>();
// 条件路由
private final Map<String, Function<S,String>> routers = new HashMap<>();
//入口
private String entryPoint;
public void addNode(String name,Node<S> node){
nodes.put(name,node);
}
public void addRouter(String nodeName,Function<S,String> router){
routers.put(nodeName,router);
}
public void setEntryPoint(String name){
this.entryPoint = name;
}
public S run(S state){
String current = entryPoint;
while (current != null){
System.out.println("当前节点:"+current);
Node<S> node = nodes.get(current);
try {
// 只计算
state =node.execute(state);
} catch (Exception e) {
System.out.println("节点异常:"+e.getMessage());
//
state.error = e;
state.action = Action.ERROR;
}
Function<S, String> router = routers.get(current);
current = router.apply(state);
}
return state;
}
}
测试:
java
package com.cj.graph02;
public class Main {
public static void main(String[] args) {
MiniLangGraph<AgentState> graph = new MiniLangGraph<>();
graph.addNode("llm",(state)->{
System.out.println("llm thinking...");
if (state.error != null && state.retryCount >=state.maxRetry){
state.result = "工具多次调用失败,无法完成请求";
state.action = Action.END;
return state;
}
if (state.input.contains("天气")){
state.action = Action.CALL_TOOL;
}else{
state.result = "普通回答";
state.action = Action.END;
}
return state;
});
graph.addRouter("llm",state -> {
switch (state.action){
case CALL_TOOL:
return "tool";
case END:
return null;
default:
throw new RuntimeException("未知action");
}
});
graph.addRouter("tool",state->{
// 把重试交给router去做,,
if (state.action == Action.ERROR){
if (state.retryCount < state.maxRetry){
state.retryCount++;
System.out.println("重试次数:"+state.retryCount);
state.action = Action.RETRY;
return "tool";
}else{
System.out.println("超过最大重试次数");
state.action = Action.END;
return "llm"; // 交给llm 判断多次失败,,走哪里
}
}
return null;
});
graph.addNode("tool",(state)->{
// todo 执行失败
state.action = Action.ERROR;
state.error = new Exception("test error");
System.out.printf("执行了tools ,,retryCount为" + state.retryCount);
return state;
});
graph.setEntryPoint("llm");
AgentState state = new AgentState();
state.input="帮我查天气";
AgentState finalState = graph.run(state);
System.out.println("finalState = " + finalState);
}
}
状态模式:state pattern : 允许一个对象在其内部状态改变的时候,,改变他的行为,,对象看起来似乎修改了他的类,,
比如说,,两个状态来回切换,,对应了两种不同的行为
java
public interface State {
String init();
String reply(String input);
}
java
public class ConnectedState implements State{
@Override
public String init(){
return "hello ,i am cc";
}
@Override
public String reply(String input){
if (input.endsWith("?")){
return "yes";
}
return "hehe";
}
}
java
public class DisconnectedState implements State{
@Override
public String init(){
return "bye";
}
@Override
public String reply(String input){
return "";
}
}
java
public class BotContext {
private State state;
public String chat(String input){
if ("hello".equals(input)){
state = new ConnectedState();
return state.init();
}else if("bye".equals(input)){
state = new DisconnectedState();
return state.init();
}
return state.reply(input);
}
}
测试:
java
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
BotContext bot = new BotContext();
while (true){
System.out.print("> ");
String input = scanner.nextLine();
String output = bot.chat(input);
System.out.println(output.isEmpty()?"(no reply)":"< "+output);
}
}
}
这里面用到的设计模式:
- 状态模式 State pattern : 改变这个状态,,他会执行不同的东西,
- 策略模式 Strategy pattern : Node下面有很多种执行策略
- 命令模式 command pattern : Node也可以理解成一个可执行对象,,Node就是一个带上下文的Command
- 责任链模式 chain of responsibility : 根据状态不同,,不断的往下面传递
- 模版方法 template method : 在执行的时候调用
Function<S,S>,这里是模板方法 - ioc控制反转 inversion of control,,, 原本应该是Node去控制下一个Node节点是谁,,现在把控制流程抽离了出来,,Node不知道谁会调用它,,Executor去控制流程