第 15 章 · 第一个 AI 工作流:Hello Graph 从 START 跑到 END

0 阅读2分钟

版本:LangGraph4j 1.8.25(Java 17+) 目标:用 BOM + langgraph4j-core,跑通一张只有一个节点的图:START → greet → END。 本章不接模型、不落盘。

上图就是本章要跑通的全部:输入里带 namegreet 节点写回 greetinglogs,到 END 后取出最终状态。


15.1 依赖

LangGraph4j1.8.25
Java17+(推荐 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 只返回要改的键,边把 STARTEND 接上,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!
    }
}

这段代码里有五点:

  1. 节点返回部分更新,只交出要改的键。这里是 greetinglogs

  2. 返回的 Map 由 Channel 合并进状态。节点里不要改 state.data() 里已有的集合。

  3. greetingChannels.base,后写覆盖前写。logsappenderWithDuplicate,每次追加,允许重复。

  4. name 只出现在 invoke 的输入里,不必写进 schema。没有 Channel 的键按覆盖合入。

  5. 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.appenderappenderWithDuplicate

compile() 得到的 CompiledGraph 可以反复 invoke。改节点或改边之后,要重新 compile