版本:LangGraph4j 1.8.25(Java 17+) 目标:用 BOM +
langgraph4j-core,跑通一张只有一个节点的图:START → greet → END。 本章不接模型、不落盘。
上图就是本章要跑通的全部:输入里带 name,greet 节点写回 greeting 和 logs,到 END 后取出最终状态。
15.1 依赖
| 项 | 值 |
|---|---|
| LangGraph4j | 1.8.25 |
| Java | 17+(推荐 21) |
版本只在 BOM 里写一次。业务依赖不要再手写 <version>,避免和 BOM 锁的版本不一致。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.bsc.langgraph4j</groupId>
<artifactId>langgraph4j-bom</artifactId>
<version>1.8.25</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.bsc.langgraph4j</groupId>
<artifactId>langgraph4j-core</artifactId>
</dependency>
</dependencies>
Gradle:implementation(platform("org.bsc.langgraph4j:langgraph4j-bom:1.8.25")),再 implementation("org.bsc.langgraph4j:langgraph4j-core")。
本章只引 langgraph4j-core。
15.2 一张能跑的图
下面这段可以放进 main。读的时候对照上图:schema 声明键怎么合并,greet 只返回要改的键,边把 START 和 END 接上,compile 之后才能 invoke。
import static org.bsc.langgraph4j.StateGraph.END;
import static org.bsc.langgraph4j.StateGraph.START;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.bsc.langgraph4j.CompiledGraph;
import org.bsc.langgraph4j.StateGraph;
import org.bsc.langgraph4j.action.AsyncNodeAction;
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 HelloGraph {
public static class HelloState extends AgentState {
public HelloState(Map<String, Object> initData) {
super(initData);
}
public Optional<String> greeting() {
return value("greeting");
}
}
public static void main(String[] args) throws Exception {
Map<String, Channel<?>> schema = Map.of(
"greeting", Channels.base(() -> ""),
"logs", Channels.appenderWithDuplicate(ArrayList::new)
);
NodeAction<HelloState> greet = state -> {
String name = state.<String>value("name").orElse("world");
return Map.of(
"greeting", "Hello, " + name + "!",
"logs", List.of("greet-executed")
);
};
CompiledGraph<HelloState> compiled = new StateGraph<>(schema, HelloState::new)
.addNode("greet", AsyncNodeAction.node_async(greet))
.addEdge(START, "greet")
.addEdge("greet", END)
.compile();
compiled.invoke(Map.of("name", "LangGraph4j"))
.ifPresent(s -> System.out.println(s.greeting().orElse("")));
// → Hello, LangGraph4j!
}
}
这段代码里有五点:
-
节点返回部分更新,只交出要改的键。这里是
greeting和logs。 -
返回的
Map由 Channel 合并进状态。节点里不要改state.data()里已有的集合。 -
greeting用Channels.base,后写覆盖前写。logs用appenderWithDuplicate,每次追加,允许重复。 -
name只出现在invoke的输入里,不必写进 schema。没有 Channel 的键按覆盖合入。 -
addNode接收的是AsyncNodeAction。同步逻辑先写成NodeAction,再用AsyncNodeAction.node_async包一层。
无参 compile() 使用默认 CompileConfig。单次运行最多走 25 步(recursionLimit)。这张图只有一个节点,碰不到这个上限。
15.3 再看一遍执行
invoke 只给最终状态。要看经过了哪个节点,用 stream:
compiled.stream(Map.of("name", "stream-user"))
.forEachAsync(out -> System.out.println(out.node() + " → " + out.state().data()))
.join();
导出图结构(核对边是否接上):
import org.bsc.langgraph4j.GraphRepresentation;
System.out.println(compiled.getGraph(
GraphRepresentation.Type.MERMAID, "Hello Graph").content());
15.4 构造方法,以及跑不起来时看什么
StateGraph 有四种构造。本章用第一种。
| 构造 | 何时用 |
|---|---|
(channels, AgentStateFactory) | 有 schema,更新走 Channel |
(channels, StateSerializer) | 还要规定状态怎么序列化 |
(AgentStateFactory) | 不声明 schema,Channel 表为空。没声明的键按覆盖写入 |
(StateSerializer) | 同上,但序列化方式自己指定 |
| 现象 | 处理 |
|---|---|
| 图不从入口走 | 补 addEdge(START, "greet") |
| 编译或运行报找不到出口 | 补 addEdge("greet", END) |
| 依赖版本互相打架 | 只在 BOM 锁 1.8.25,业务依赖去掉手写版本 |
资料里的 MessageChannel 编译失败 | 1.8.25 已没有这个类;列表用 Channels.appender 或 appenderWithDuplicate |
compile() 得到的 CompiledGraph 可以反复 invoke。改节点或改边之后,要重新 compile。