Langgraph4j Getting Started

Langgraph4j Getting Started

欢迎来到 LangGraph4j!本指南将帮助您理解 LangGraph4j 的核心概念、进行安装,并构建您的第一个应用程序。

引言

LangGraph4j 是一个用于通过大型语言模型 (LLMs) 构建有状态、多代理应用的 Java 库。它受到 Python 库 LangGraph 的启发,并旨在与 Langchain4j 和 Spring AI 等流行的 Java LLM 框架无缝协作。

其核心在于,LangGraph4j 允许您定义循环图 (cyclical graphs),在图中,不同的组件(代理、工具或自定义逻辑)可以以有状态的方式进行交互。这对于构建需要记忆、上下文以及不同“代理”之间协作或交接任务能力的复杂应用程序至关重要。

核心功能与优势

LangGraph4j 提供了多项功能和优势:

  • 有状态执行 (Stateful Execution): 在图节点之间管理和更新共享状态,从而实现记忆和上下文感知。
  • 循环图 (Cyclical Graphs): 与传统的有向无环图 (DAGs) 不同,LangGraph4j 支持循环,这对于基于代理的架构至关重要,因为在这些架构中控制流可以回溯(例如,代理重试任务或请求澄清)。
  • 显式控制流 (Explicit Control Flow): 清晰地定义图节点之间转换的路径和条件。
  • 模块化 (Modularity): 使用更小的、可复用的组件(节点)来构建复杂的系统。
  • 灵活性 (Flexibility): 可与各种 LLM 提供商和自定义 Java 逻辑集成。
  • 可观测性与调试 (Observability & Debugging):
    • 检查点 (Checkpoints): 在任何时间点保存图的状态,以便后续重放或检查。这对于调试和理解复杂的交互非常有价值。
    • 图可视化 (Graph Visualization): 使用 PlantUML 或 Mermaid 生成图的可视化表示,以理解其结构。
  • 异步与流式支持 (Asynchronous & Streaming Support): 通过非阻塞操作和从 LLMs 流式传输结果来构建响应迅速的应用程序。
  • Playground & Studio: 一个 Web UI,用于可视化地检查、运行和调试您的图。

核心概念详解

理解这些概念是有效使用 LangGraph4j 的关键:

StateGraph<S extends AgentState>

StateGraph 是您用来定义应用程序结构的主要类。您可以在其中添加节点和边来创建图。它由一个 AgentState 进行参数化。

AgentState

AgentState(或继承自它的类)代表图的共享状态。它本质上是一个在节点间传递的映射 (Map<String, Object>)。每个节点都可以从该状态中读取数据,并返回对状态的更新。

  • 模式 (Schema): 状态的结构由一个“模式”定义,该模式是一个 Map<String, Channel.Reducer>。映射中的每个键对应状态中的一个属性。
  • Channel.Reducer: 归约器 (reducer) 定义了状态属性的更新方式。例如,新值可能会覆盖旧值,或者被添加到一个现有值的列表中。
  • Channel.Default: 为尚未设置的状态属性提供一个默认值。
  • Channel.Appender / MessageChannel.Appender: 一种常见的归约器类型,它将新值追加到与状态属性关联的列表中。这对于累积消息、工具调用或其他数据序列非常有用。MessageChannel.Appender 专为聊天消息设计,还可以通过 ID 处理消息删除。

节点 (Nodes)

节点是图中执行操作的构建块。一个节点通常是一个函数(或实现 NodeAction<S>AsyncNodeAction<S> 的类),它会:

  1. 接收当前的 AgentState 作为输入。
  2. 执行某些计算(例如,调用 LLM、执行工具、运行自定义业务逻辑)。
  3. 返回一个 Map<String, Object>,代表对状态的更新。这些更新随后会根据模式中定义的归约器应用到 AgentState 上。

节点可以是同步的或异步的 (CompletableFuture)。

边 (Edges)

边定义了节点之间的控制流。

  • 普通边 (Normal Edges): 从一个节点到另一个节点的无条件转换。在节点 A 完成后,控制权总是传递给节点 B。您可以使用 addEdge(sourceNodeName, destinationNodeName) 来定义它们。
  • 条件边 (Conditional Edges): 下一个节点根据当前的 AgentState 动态确定。在一个源节点完成后,会执行一个 EdgeAction<S>(或 AsyncEdgeAction<S>)函数。该函数接收当前状态并返回下一个要执行的节点的名称。这允许实现分支逻辑(例如,如果一个代理决定使用工具,则转到 “execute_tool” 节点;否则,转到 “respond_to_user” 节点)。条件边通过 addConditionalEdges(...) 定义。
  • 入口点 (Entry Points): 您也可以使用 addConditionalEntryPoint(...) 为您的图定义条件入口点。

编译 (Compilation)

StateGraph 中定义了所有节点和边之后,您需要将其 compile() 成一个 CompiledGraph<S extends AgentState>。这个编译后的图是您的逻辑的一个不可变的、可运行的表示。编译过程会验证图的结构(例如,检查是否存在孤立节点)。

检查点 (Persistence)

LangGraph4j 允许您在任何步骤保存(Checkpoint)图的状态。这对于以下场景极为有用:

  • 调试: 检查不同时间点的状态以了解发生了什么。
  • 恢复: 将图恢复到先前的状态并继续执行。
  • 长时间运行的进程: 持久化长时间运行的代理交互的状态。

您通常会使用一个 CheckpointSaver 的实现(例如,用于内存存储的 MemorySaver,或者您可以为持久化存储实现自己的版本)。

安装

要在您的项目中使用 LangGraph4j,您需要将其添加为依赖项。

Maven:

请确保您使用的是 Java 17 或更高版本。

最新稳定版 (推荐):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<properties>
<langgraph4j.version>1.6.2</langgraph4j.version> <!-- 请检查实际的最新版本 -->
</properties>

<!-- 可选:添加物料清单 (BOM) 来管理 langgraph4j 模块版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.bsc.langgraph4j</groupId>
<artifactId>langgraph4j-bom</artifactId>
<version>${langgraph4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.bsc.langgraph4j</groupId>
<artifactId>langgraph4j-core</artifactId>
</dependency>
<!-- 如果需要,添加其他 langgraph4j 模块,例如 langgraph4j-langchain4j -->
</dependencies>

(注意:请随时访问 Maven 中央仓库查询最新的版本号。)

开发快照版: 如果您想使用最新的未发布功能,可以使用快照版本。

1
2
3
4
5
<dependency>
<groupId>org.bsc.langgraph4j</groupId>
<artifactId>langgraph4j-core</artifactId>
<version>1.6-SNAPSHOT</version> <!-- 或当前的快照版本号 -->
</dependency>

您可能需要在 settings.xmlpom.xml 中配置 Sonatype OSS 快照仓库:

1
2
3
4
5
6
7
8
9
<repositories>
<repository>
<id>sonatype-oss-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

您的第一个图 - 简单示例

让我们创建一个非常简单的图,它有两个节点:greeterrespondergreeter 节点将向状态中添加一条问候消息。responder 节点将根据问候语添加一条响应消息。

1. 定义状态: 我们的状态将持有一个消息列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.bsc.langgraph4j.state.AgentState;
import org.bsc.langgraph4j.state.Channels;
import org.bsc.langgraph4j.state.Channel;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;

// 为我们的图定义状态
class SimpleState extends AgentState {
public static final String MESSAGES_KEY = "messages";

// 定义状态的模式。
// MESSAGES_KEY 将持有一个字符串列表,新消息将被追加到其中。
public static final Map<String, Channel<?>> SCHEMA = Map.of(
MESSAGES_KEY, Channels.appender(ArrayList::new)
);

public SimpleState(Map<String, Object> initData) {
super(initData);
}

public List<String> messages() {
return this.<List<String>>value("messages")
.orElse( List.of() );
}
}

2. 定义节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.bsc.langgraph4j.action.NodeAction;
import java.util.Collections;
import java.util.Map;

// 添加问候语的节点
class GreeterNode implements NodeAction<SimpleState> {
@Override
public Map<String, Object> apply(SimpleState state) {
System.out.println("GreeterNode 正在执行。当前消息: " + state.messages());
return Map.of(SimpleState.MESSAGES_KEY, "Hello from GreeterNode!");
}
}

// 添加响应的节点
class ResponderNode implements NodeAction<SimpleState> {
@Override
public Map<String, Object> apply(SimpleState state) {
System.out.println("ResponderNode 正在执行。当前消息: " + state.messages());
List<String> currentMessages = state.messages();
if (currentMessages.contains("Hello from GreeterNode!")) {
return Map.of(SimpleState.MESSAGES_KEY, "Acknowledged greeting!");
}
return Map.of(SimpleState.MESSAGES_KEY, "No greeting found.");
}
}

3. 定义并编译图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.bsc.langgraph4j.StateGraph;
import org.bsc.langgraph4j.GraphStateException;
import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async;
import static org.bsc.langgraph4j.StateGraph.START;
import static org.bsc.langgraph4j.StateGraph.END;

import java.util.List;
import java.util.Map;

public class SimpleGraphApp {

public static void main(String[] args) throws GraphStateException {
// 初始化节点
GreeterNode greeterNode = new GreeterNode();
ResponderNode responderNode = new ResponderNode();

// 定义图的结构
var stateGraph = new StateGraph<>(SimpleState.SCHEMA, initData -> new SimpleState(initData))
.addNode("greeter", node_async(greeterNode))
.addNode("responder", node_async(responderNode))
// 定义边
.addEdge(START, "greeter") // 从 greeter 节点开始
.addEdge("greeter", "responder")
.addEdge("responder", END) // 在 responder 节点后结束
;
// 编译图
var compiledGraph = stateGraph.compile();

// 运行图
// `stream` 方法返回一个 AsyncGenerator。
// 为简单起见,我们将收集结果。在实际应用中,您可能会在结果到达时即时处理它们。
// 在这里,我们关心的是执行后的最终状态项。

for (var item : compiledGraph.stream( Map.of( SimpleState.MESSAGES_KEY, "Let's, begin!" ) ) ) {
System.out.println( item );
}
}
}

解释:

  • 我们定义了 SimpleState,其中 MESSAGES_KEY 使用 AppenderChannel 来累积字符串。
  • GreeterNode 添加了一条 “Hello” 消息。
  • ResponderNode 检查是否存在问候语并添加一条确认消息。
  • 我们定义了 StateGraph,添加了节点,并用边指定了流程:START -> greeter -> responder -> END
  • stateGraph.compile() 创建了可运行的 CompiledGraph
  • compiledGraph.stream(initialState) 执行该图。我们遍历流以获取最终状态。流中的每一项都代表一个节点执行后的状态。

这个例子展示了基本的工作流程:定义状态、定义节点、用边连接它们、编译并运行。

运行您的图

如示例所示,您通常使用以下执行方法之一来运行一个编译后的图:

  • stream(S initialState, RunnableConfig config): 执行图并返回一个 AsyncGenerator<S>。每个产生 (yield) 的项都是一个节点完成后的状态 S。这对于观察每一步的状态或流式处理部分结果很有用。
  • invoke(S initialState, RunnableConfig config): 执行图并返回一个 CompletableFuture<S>,该 Future 在图到达 END 节点后,以最终状态完成。

RunnableConfig 可用于传入运行时配置。

Studio 🤩 - 可视化运行您的图

Langgraph4j Studio 是一个可嵌入的 Web 应用程序,用于可视化和实验图:

要探索 Langgraph4j Studio,请访问 Studio

额外福利: 内置集成

LangChain4j

作为验证 LangChain4j 集成的默认用例,我们使用 LangGraph4j 实现了 AgentExecutor(即 ReACT 代理)。在项目的模块中,您可以找到带有测试的完整工作代码。欢迎随时检出并将其用作参考。以下是 AgentExecutor 的使用示例。

定义工具 (Define Tools)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestTool {

@Tool("tool for test AI agent executor")
String execTest(@P("test message") String message) {
return format( "test tool ('%s') executed with result 'OK'", message);
}

@Tool("return current number of system thread allocated by application")
int threadCount() {
return Thread.getAllStackTraces().size();
}

}

运行代理 (Run Agent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var model = OllamaChatModel.builder()
.modelName( "qwen2.5:7b" )
.baseUrl("http://localhost:11434")
.supportedCapabilities(Capability.RESPONSE_FORMAT_JSON_SCHEMA)
.logRequests(true)
.logResponses(true)
.maxRetries(2)
.temperature(0.0)
.build();

var agent = AgentExecutor.builder()
.chatModel(model)
.toolsFromObject(new TestTool())
.build()
.compile();

for (var item : agent.stream( Map.of( "messages", "perform test twice and return number of current active threads" ) ) ) {
System.out.println( item );
}

Spring AI

作为验证 Spring AI 集成的默认用例,我们使用 LangGraph4j 实现了 AgentExecutor(即 ReACT 代理)。在项目的模块中,您可以找到带有测试的完整工作代码。欢迎随时检出并将其用作参考。以下是 AgentExecutor 的使用示例。

定义工具 (Define Tools)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestTool {

@Tool(description = "tool for test AI agent executor")
String execTest( @ToolParam(description ="test message") String message ) {
return format( "test tool ('%s') executed with result 'OK'", message);
}

@Tool(description = "return current number of system thread allocated by application")
int threadCount() {
return Thread.getAllStackTraces().size();
}

}

运行代理 (Run Agent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var model = OllamaChatModel.builder()
.ollamaApi(OllamaApi.builder().baseUrl("http://localhost:11434").build())
.defaultOptions(OllamaOptions.builder()
.model("qwen2.5:7b")
.temperature(0.1)
.build())
.build();

var agent = AgentExecutor.builder()
.chatModel(model)
.toolsFromObject(new TestTool())
.build()
.compile()
;

for (var item : agent.stream( Map.of( "messages", "perform test twice and return number of current active threads" ) ) ) {
System.out.println( item );
}

关键能力概览

LangGraph4j 包含了构建复杂代理应用的丰富功能:

  • 异步操作: 节点和边可以是异步的(返回 CompletableFuture),从而允许非阻塞的 I/O 操作,尤其是在处理 LLM 调用时。
  • 流式处理: 原生支持通过节点从 LLM 流式传输响应,实现实时输出。请参阅 how-tos/llm-streaming.ipynb
  • 检查点 (持久化与时间旅行): 保存和加载图的状态。这使您可以恢复长时间运行的任务,通过检查中间状态进行调试,甚至“时间旅行”到先前的状态。请参阅 how-tos/persistence.ipynbhow-tos/time-travel.ipynb
  • 图可视化: 生成图的 PlantUML 或 Mermaid 图表以可视化其结构,这有助于理解和调试。请参阅 how-tos/plantuml.ipynb
  • Playground & Studio: LangGraph4j 自带一个可嵌入的 Web UI (Studio),允许您实时可视化、运行和与您的图进行交互。这对于开发和调试非常出色。
  • 子图 (Child Graphs): 通过在父图的节点内嵌套更小的、可复用的图来构建复杂的图。这促进了模块化和可复用性。请参阅 how-tos/subgraph-as-nodeaction.ipynbhow-tos/subgraph-as-compiledgraph.ipynbhow-tos/subgraph-as-stategraph.ipynb 示例。
  • 并行执行: 配置图的某些部分以并行执行多个节点,从而提高可以并发运行的任务的性能。请参阅 how-tos/parallel-branch.ipynb
  • 线程 (多轮对话): 在单个图实例中管理不同的、并行的执行线程,每个线程都有自己的检查点历史。这对于同时处理多个用户会话或对话至关重要。

后续步骤

既然您对 LangGraph4j 有了基本的了解,您可以按照以下方式继续您的学习之旅:

  1. 探索 how-tos: 仓库中的 how-tos/ 目录包含了 Jupyter notebooks(可使用 IJava 等 Java 内核运行),通过代码示例演示了各种功能。
  2. 研究示例: 查看 samples/ 目录,获取更完整的应用程序示例,包括与 Langchain4j 和 Spring AI 的集成。
  3. 查阅 Javadocs: 有关类和方法的详细信息,请参阅 API 文档 (Javadocs)。(如果官方项目文档网站发生变化,链接可能需要更新)
  4. 动手实践! 学习的最佳方式是实践。尝试修改示例或构建您自己的简单图。

我们希望本指南能帮助您开始使用 LangGraph4j。祝您构建愉快!

作者

wuhunyu

发布于

2025-09-02

更新于

2025-09-11

许可协议