在AI驱动的应用开发中,工作流引擎是串联复杂业务逻辑的核心。LangGraph4j作为Java生态中的轻量级工作流框架,能够帮助开发者高效构建可编排、可扩展的AI任务流程。本文将通过一个完整的项目改造案例,深入讲解如何使用LangGraph4j实现从Demo到生产级工作流的跃迁。
快速上手:LangGraph4j工作流Demo实践
在开始项目改造之前,我们先通过一个简单的Demo熟悉LangGraph4j的核心概念。首先,需要在项目中引入相关依赖:
org.bsc.langgraph4j
langgraph4j-core
1.6.0-rc2
参考官方文档,我们可以快速搭建一个最简工作流。下图展示了工作流的基本结构:

工作流构建通常包含三个关键步骤:
- 定义状态:状态是工作流中数据流转的载体,通常使用一个类或Map来表示。例如,我们可以定义一个包含用户输入、中间结果和最终输出的状态类。
- 定义请求节点和响应节点:节点是工作流中的执行单元,每个节点负责处理特定的业务逻辑,并从状态中读取或写入数据。
- 定义工作流结构:通过连接节点,形成有向无环图(DAG),LangGraph4j会自动管理节点的执行顺序和状态传递。
以下是状态定义的核心代码:
// Define the state for our graph
class SimpleState extends AgentState {
public static final String MESSAGES_KEY = "messages";
// Define the schema for the state.
// MESSAGES_KEY will hold a list of strings, and new messages will be appended.
public static final Map> SCHEMA = Map.of(
MESSAGES_KEY, Channels.appender(ArrayList::new)
);
public SimpleState(Map initData) {
super(initData);
}
public List messages() {
return this.>value("messages")
.orElse( List.of() );
}
}
请求节点和响应节点的实现如下:
// Node that adds a greeting
class GreeterNode implements NodeAction {
@Override
public Map apply(SimpleState state) {
System.out.println("GreeterNode executing. Current messages: " + state.messages());
return Map.of(SimpleState.MESSAGES_KEY, "Hello from GreeterNode!");
}
}
// Node that adds a response
class ResponderNode implements NodeAction {
@Override
public Map apply(SimpleState state) {
System.out.println("ResponderNode executing. Current messages: " + state.messages());
List 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.");
}
}
最后,将节点组装成工作流:
public class SimpleGraphApp {
public static void main(String[] args) throws GraphStateException {
// Initialize nodes
GreeterNode greeterNode = new GreeterNode();
ResponderNode responderNode = new ResponderNode();
// Define the graph structure
var stateGraph = new StateGraph<>(SimpleState.SCHEMA, initData -> new SimpleState(initData))
.addNode("greeter", node_async(greeterNode))
.addNode("responder", node_async(responderNode))
// Define edges
.addEdge(START, "greeter") // Start with the greeter node
.addEdge("greeter", "responder")
.addEdge("responder", END) // End after the responder node
;
// Compile the graph
var compiledGraph = stateGraph.compile();
// 打印出工作流可视化编排---文本绘图方法能力
GraphRepresentation graph = stateGraph.getGraph(GraphRepresentation.Type.MERMAID, "demo");
System.out.println(graph.toString());
// Run the graph
// The `stream` method returns an AsyncGenerator.
// For simplicity, we'll collect results. In a real app, you might process them as they arrive.
// Here, the final state after execution is the item of interest.
for (var item : compiledGraph.stream( Map.of( SimpleState.MESSAGES_KEY, "Let's, begin!" ) ) ) {
System.out.println( item );
}
}
}
执行Demo后,可以看到工作流按照预期顺序运行:

提示:在Python、JavaScript或TypeScript中,类似的工作流框架(如LangChain或Temporal)也遵循相似的节点-状态模式,但LangGraph4j在Java生态中提供了更原生的集成体验。
项目改造思路:从AI生成网站场景出发
理解了基础Demo后,我们将其应用到实际项目中。以AI生成代码网站场景为例,用户输入一段提示词后,系统需要完成以下复杂流程:
- 用户输入提示词 → Agent通过工具调用从不同渠道获取图片素材
- 内容图片:从Pexels网页搜索
- 插画图片:从undraw抓取
- 文本绘图并上传到COS(对象存储)
- AI生成或通过MCP服务设计Logo等图片
- 提示词增强:将图片内容关联到原始提示词
- 智能路由Agent选择模式生成网站(原生HTML、多文件、Vue工程)
- 利用搜索到的图片和确认的模式生成网站
- 文件保存、项目构建与打包
这个流程涉及多个外部服务调用和条件分支,非常适合使用工作流进行编排。在Go或C++项目中,类似的编排通常需要手动管理状态机,而LangGraph4j提供了声明式的解决方案。
为了加速开发,我们可以使用AI生成工作流结构。以下是一个提示词示例:
帮我生成 LangGraph4j 工作流的代码
## 工作流的流程描述
// ... 补充具体的流程
## 要求
先生成基础的工作流结构代码,每个工作节点中只输出一句信息就够了,不用真正实现具体的业务逻辑。
## 参考信息
官方文档:@https://langgraph4j.github.io/langgraph4j/core/low_level/
示例工作流实现:@https://github.com/langgraph4j/langgraph4j-examples/blob/main/langchain4j/adaptive-rag/src/main/java/dev/langchain4j/adaptiverag/AdaptiveRag.java
生成的简单工作流图代码如下:
import lombok.extern.slf4j.Slf4j;
import org.bsc.langgraph4j.CompiledGraph;
import org.bsc.langgraph4j.GraphRepresentation;
import org.bsc.langgraph4j.GraphStateException;
import org.bsc.langgraph4j.NodeOutput;
import org.bsc.langgraph4j.action.AsyncNodeAction;
import org.bsc.langgraph4j.prebuilt.MessagesState;
import org.bsc.langgraph4j.prebuilt.MessagesStateGraph;
import java.util.Map;
import static org.bsc.langgraph4j.StateGraph.END;
import static org.bsc.langgraph4j.StateGraph.START;
import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async;
/**
* 简化版网站生成工作流应用 - 使用 MessagesState
*/
@Slf4j
public class SimpleWorkflowApp {
/**
* 创建工作节点的通用方法
*/
static AsyncNodeAction> makeNode(String message) {
return node_async(state -> {
log.info("执行节点: {}", message);
return Map.of("messages", message);
});
}
public static void main(String[] args) throws GraphStateException {
// 创建工作流图
CompiledGraph> workflow = new MessagesStateGraph()
// 添加节点
.addNode("image_collector", makeNode("获取图片素材"))
.addNode("prompt_enhancer", makeNode("增强提示词"))
.addNode("router", makeNode("智能路由选择"))
.addNode("code_generator", makeNode("网站代码生成"))
.addNode("project_builder", makeNode("项目构建"))
// 添加边
.addEdge(START, "image_collector") // 开始 -> 图片收集
.addEdge("image_collector", "prompt_enhancer") // 图片收集 -> 提示词增强
.addEdge("prompt_enhancer", "router") // 提示词增强 -> 智能路由
.addEdge("router", "code_generator") // 智能路由 -> 代码生成
.addEdge("code_generator", "project_builder") // 代码生成 -> 项目构建
.addEdge("project_builder", END) // 项目构建 -> 结束
// 编译工作流
.compile();
log.info("开始执行工作流");
GraphRepresentation graph = workflow.getGraph(GraphRepresentation.Type.MERMAID);
log.info("工作流图: \n{}", graph.content());
// 执行工作流
int stepCounter = 1;
for (NodeOutput> step : workflow.stream(Map.of())) {
log.info("--- 第 {} 步完成 ---", stepCounter);
log.info("步骤输出: {}", step);
stepCounter++;
}
log.info("工作流执行完成!");
}
}
自定义状态与工作流上下文设计
LangGraph4j官方提供的状态类默认是MessagesState消息列表结构,但在我们的场景中,需要维护的状态包含多个字段(如用户输入、图片URL列表、生成模式等),因此必须自定义一个WorkflowContext状态上下文类。
为了实现与LangGraph4j工作流图所需的AgentState兼容,我们可以将WorkflowContext对象作为一个key/value存入MessagesState中。在需要时,通过state.data().getKey()获取具体字段。下图展示了状态设计思路:


状态与工作流上下文的映射关系如下:

以下是图片资源对象、图片枚举类以及自定义WorkflowContext上下文类的代码实现:
@Getter
public enum ImageCategoryEnum {
CONTENT("内容图片", "CONTENT"),
LOGO("LOGO图片", "LOGO"),
ILLUSTRATION("插画图片", "ILLUSTRATION"),
ARCHITECTURE("架构图片", "ARCHITECTURE");
private final String text;
private final String value;
ImageCategoryEnum(String text, String value) {
this.text = text;
this.value = value;
}
/**
* 根据 value 获取枚举
*
* @param value 枚举值的value
* @return 枚举值
*/
public static ImageCategoryEnum getEnumByValue(String value) {
if (ObjUtil.isEmpty(value)) {
return null;
}
for (ImageCategoryEnum anEnum : ImageCategoryEnum.values()) {
if (anEnum.value.equals(value)) {
return anEnum;
}
}
return null;
}
}
/**
* 图片资源对象
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ImageResource implements Serializable {
/**
* 图片类别
*/
private ImageCategoryEnum category;
/**
* 图片描述
*/
private String description;
/**
* 图片地址
*/
private String url;
@Serial
private static final long serialVersionUID = 1L;
}
/**
* 工作流上下文 - 存储所有状态信息
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WorkflowContext implements Serializable {
/**
* WorkflowContext 在 MessagesState 中的存储key
*/
public static final String WORKFLOW_CONTEXT_KEY = "workflowContext";
/**
* 当前执行步骤
*/
private String currentStep;
/**
* 用户原始输入的提示词
*/
private String originalPrompt;
/**
* 图片资源字符串
*/
private String imageListStr;
/**
* 图片资源列表
*/
private List imageList;
/**
* 增强后的提示词
*/
private String enhancedPrompt;
/**
* 代码生成类型
*/
private CodeGenTypeEnum generationType;
/**
* 生成的代码目录
*/
private String generatedCodeDir;
/**
* 构建成功的目录
*/
private String buildResultDir;
/**
* 错误信息
*/
private String errorMessage;
@Serial
private static final long serialVersionUID = 1L;
// ========== 上下文操作方法 ==========
/**
* 从 MessagesState 中获取 WorkflowContext
*/
public static WorkflowContext getContext(MessagesState state) {
return (WorkflowContext) state.data().get(WORKFLOW_CONTEXT_KEY);
}
/**
* 将 WorkflowContext 保存到 MessagesState 中
*/
public static Map saveContext(WorkflowContext context) {
return Map.of(WORKFLOW_CONTEXT_KEY, context);
}
}
修改工作流图结构,引入状态并完善简单工作流图(不进行状态流转)的代码如下:
/**
* 简化版带状态定义的工作流 - 只定义状态结构,不实现具体流转
*/
@Slf4j
public class SimpleStatefulWorkflowApp {
/**
* 创建带状态感知的工作节点
*/
static AsyncNodeAction> makeStatefulNode(String nodeName, String message) {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: {} - {}", nodeName, message);
// 只记录当前步骤,不做具体的状态流转
if (context != null) {
context.setCurrentStep(nodeName);
}
return WorkflowContext.saveContext(context);
});
}
public static void main(String[] args) throws GraphStateException {
// 创建工作流图
CompiledGraph> workflow = new MessagesStateGraph()
// 添加节点 - 使用带状态感知的节点
.addNode("image_collector", makeStatefulNode("image_collector", "获取图片素材"))
.addNode("prompt_enhancer", makeStatefulNode("prompt_enhancer", "增强提示词"))
.addNode("router", makeStatefulNode("router", "智能路由选择"))
.addNode("code_generator", makeStatefulNode("code_generator", "网站代码生成"))
.addNode("project_builder", makeStatefulNode("project_builder", "项目构建"))
// 添加边
.addEdge(START, "image_collector")
.addEdge("image_collector", "prompt_enhancer")
.addEdge("prompt_enhancer", "router")
.addEdge("router", "code_generator")
.addEdge("code_generator", "project_builder")
.addEdge("project_builder", END)
// 编译工作流
.compile();
// 初始化 WorkflowContext - 只设置基本信息
WorkflowContext initialContext = WorkflowContext.builder()
.originalPrompt("创建一个小楼的个人博客网站")
.currentStep("初始化")
.build();
log.info("初始输入: {}", initialContext.getOriginalPrompt());
log.info("开始执行工作流");
// 显示工作流图
GraphRepresentation graph = workflow.getGraph(GraphRepresentation.Type.MERMAID);
log.info("工作流图:\n{}", graph.content());
// 执行工作流
int stepCounter = 1;
for (NodeOutput> step : workflow.stream(Map.of(WorkflowContext.WORKFLOW_CONTEXT_KEY, initialContext))) {
log.info("--- 第 {} 步完成 ---", stepCounter);
// 显示当前状态
WorkflowContext currentContext = WorkflowContext.getContext(step.state());
if (currentContext != null) {
log.info("当前步骤上下文: {}", currentContext);
}
stepCounter++;
}
log.info("工作流执行完成!");
}
}
✅ 最佳实践:在设计状态时,建议将所有需要跨节点共享的数据都定义在WorkflowContext中,避免在节点间传递冗余参数。这类似于TypeScript中的Record<string, unknown>模式,但Java的强类型系统能提供更好的编译时检查。
⚙️ 工作节点开发:从Mock到真实业务
在开发真实工作节点之前,先使用Mock假数据来模拟状态流转,验证工作流结构的正确性。
- 图片收集节点:模拟从Pexels和undraw获取图片URL。
- 提示词增强节点:将图片描述合并到原始提示词中。
- 智能路由节点:根据提示词内容选择生成模式(HTML/多文件/Vue)。
- 代码生成节点:根据模式和图片生成网站代码。
- 项目构建节点:模拟打包和部署过程。
各节点的Mock实现如下:
@Slf4j
public class ImageCollectorNode {
public static AsyncNodeAction> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 图片收集");
// TODO: 实际执行图片收集逻辑
// 简单的假数据
List imageList = Arrays.asList(
ImageResource.builder()
.category(ImageCategoryEnum.CONTENT)
.description("假数据图片1")
.url("https://www.codefather.cn/logo.png")
.build(),
ImageResource.builder()
.category(ImageCategoryEnum.LOGO)
.description("假数据图片2")
.url("https://www.codefather.cn/logo.png")
.build()
);
// 更新状态
context.setCurrentStep("图片收集");
context.setImageList(imageList);
log.info("图片收集完成,共收集 {} 张图片", imageList.size());
return WorkflowContext.saveContext(context);
});
}
}
@Slf4j
public class PromptEnhancerNode {
public static AsyncNodeAction> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 提示词增强");
// TODO: 实际执行提示词增强逻辑
// 简单的假数据
String enhancedPrompt = "这是增强后的假数据提示词";
// 更新状态
context.setCurrentStep("提示词增强");
context.setEnhancedPrompt(enhancedPrompt);
log.info("提示词增强完成");
return WorkflowContext.saveContext(context);
});
}
}
@Slf4j
public class RouterNode {
public static AsyncNodeAction> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 智能路由");
// TODO: 实际执行智能路由逻辑
// 简单的假数据
CodeGenTypeEnum generationType = CodeGenTypeEnum.HTML;
// 更新状态
context.setCurrentStep("智能路由");
context.setGenerationType(generationType);
log.info("路由决策完成,选择类型: {}", generationType.getText());
return WorkflowContext.saveContext(context);
});
}
}
@Slf4j
public class CodeGeneratorNode {
public static AsyncNodeAction> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 代码生成");
// TODO: 实际执行代码生成逻辑
// 简单的假数据
String generatedCodeDir = "/tmp/generated/fake-code";
// 更新状态
context.setCurrentStep("代码生成");
context.setGeneratedCodeDir(generatedCodeDir);
log.info("代码生成完成,目录: {}", generatedCodeDir);
return WorkflowContext.saveContext(context);
});
}
}
@Slf4j
public class ProjectBuilderNode {
public static AsyncNodeAction> create() {
return node_async(state -> {
WorkflowContext context = WorkflowContext.getContext(state);
log.info("执行节点: 项目构建");
// TODO: 实际执行项目构建逻辑
// 简单的假数据
String buildResultDir = "/tmp/build/fake-build";
// 更新状态
context.setCurrentStep("项目构建");
context.setBuildResultDir(buildResultDir);
log.info("项目构建完成,结果目录: {}", buildResultDir);
return WorkflowContext.saveContext(context);
});
}
}
将上述节点应用到工作流图中,模拟完整流转:
@Slf4j
public class WorkflowApp {
public static void main(String[] args) throws GraphStateException {
// 创建工作流图
CompiledGraph> workflow = new MessagesStateGraph()
// 添加节点 - 使用真实的工作节点
.addNode("image_collector", ImageCollectorNode.create())
.addNode("prompt_enhancer", PromptEnhancerNode.create())
.addNode("router", RouterNode.create())
.addNode("code_generator", CodeGeneratorNode.create())
.addNode("project_builder", ProjectBuilderNode.create())
// 添加边
.addEdge(START, "image_collector")
.addEdge("image_collector", "prompt_enhancer")
.addEdge("prompt_enhancer", "router")
.addEdge("router", "code_generator")
.addEdge("code_generator", "project_builder")
.addEdge("project_builder", END)
// 编译工作流
.compile();
// 初始化 WorkflowContext - 只设置基本信息
WorkflowContext initialContext = WorkflowContext.builder()
.originalPrompt("创建一个小楼的个人博客网站")
.currentStep("初始化")
.build();
log.info("初始输入: {}", initialContext.getOriginalPrompt());
log.info("开始执行工作流");
// 显示工作流图
GraphRepresentation graph = workflow.getGraph(GraphRepresentation.Type.MERMAID);
log.info("工作流图:\n{}", graph.content());
// 执行工作流
int stepCounter = 1;
for (NodeOutput> step : workflow.stream(Map.of(WorkflowContext.WORKFLOW_CONTEXT_KEY, initialContext))) {
log.info("--- 第 {} 步完成 ---", stepCounter);
// 显示当前状态
WorkflowContext currentContext = WorkflowContext.getContext(step.state());
if (currentContext != null) {
log.info("当前步骤上下文: {}", currentContext);
}
stepCounter++;
}
log.info("工作流执行完成!");
}
}
Mock验证通过后,依次开发真实工作节点。每个节点需要对接真实的外部服务(如Pexels API、Undraw API、COS SDK、AI模型API等)。以图片收集节点为例,真实实现需要处理HTTP请求、错误重试和结果缓存。
最后,将真实节点替换到工作流中:
@Slf4j
public class CodeGenWorkflow {
/**
* 创建完整的工作流
*/
public CompiledGraph> createWorkflow() {
try {
return new MessagesStateGraph()
// 添加节点 - 使用完整实现的节点
.addNode("image_collector", ImageCollectorNode.create())
.addNode("prompt_enhancer", PromptEnhancerNode.create())
.addNode("router", RouterNode.create())
.addNode("code_generator", CodeGeneratorNode.create())
.addNode("project_builder", ProjectBuilderNode.create())
// 添加边
.addEdge(START, "image_collector")
.addEdge("image_collector", "prompt_enhancer")
.addEdge("prompt_enhancer", "router")
.addEdge("router", "code_generator")
.addEdge("code_generator", "project_builder")
.addEdge("project_builder", END)
// 编译工作流
.compile();
} catch (GraphStateException e) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "工作流创建失败");
}
}
/**
* 执行工作流
*/
public WorkflowContext executeWorkflow(String originalPrompt) {
CompiledGraph> workflow = createWorkflow();
// 初始化 WorkflowContext
WorkflowContext initialContext = WorkflowContext.builder()
.originalPrompt(originalPrompt)
.currentStep("初始化")
.build();
GraphRepresentation graph = workflow.getGraph(GraphRepresentation.Type.MERMAID);
log.info("工作流图:\n{}", graph.content());
log.info("开始执行代码生成工作流");
WorkflowContext finalContext = null;
int stepCounter = 1;
for (NodeOutput> step : workflow.stream(
Map.of(WorkflowContext.WORKFLOW_CONTEXT_KEY, initialContext))) {
log.info("--- 第 {} 步完成 ---", stepCounter);
// 显示当前状态
WorkflowContext currentContext = WorkflowContext.getContext(step.state());
if (currentContext != null) {
finalContext = currentContext;
log.info("当前步骤上下文: {}", currentContext);
}
stepCounter++;
}
log.info("代码生成工作流执行完成!");
return finalContext;
}
}
⚠️ 注意事项:真实节点开发时,务必处理超时和异常情况。LangGraph4j支持节点级别的错误处理,可以在节点执行失败时回滚状态或跳转到补偿节点。在Python的LangChain中也有类似机制,但Java版本通过try-catch和CompensatingNode实现。
单元测试与效果验证
工作流开发完成后,编写单元测试是保证质量的关键。测试需要覆盖正常流程、边界条件和异常场景。
@SpringBootTest
class CodeGenWorkflowTest {
@Test
void testTechBlogWorkflow() {
WorkflowContext result = new CodeGenWorkflow().executeWorkflow("创建一个技术博客网站,需要展示编程教程和系统架构");
Assertions.assertNotNull(result);
System.out.println("生成类型: " + result.getGenerationType());
System.out.println("生成的代码目录: " + result.getGeneratedCodeDir());
System.out.println("构建结果目录: " + result.getBuildResultDir());
}
@Test
void testCorporateWorkflow() {
WorkflowContext result = new CodeGenWorkflow().executeWorkflow("创建企业官网,展示公司形象和业务介绍");
Assertions.assertNotNull(result);
System.out.println("生成类型: " + result.getGenerationType());
System.out.println("生成的代码目录: " + result.getGeneratedCodeDir());
System.out.println("构建结果目录: " + result.getBuildResultDir());
}
@Test
void testVueProjectWorkflow() {
WorkflowContext result = new CodeGenWorkflow().executeWorkflow("创建一个Vue前端项目,包含用户管理和数据展示功能");
Assertions.assertNotNull(result);
System.out.println("生成类型: " + result.getGenerationType());
System.out.println("生成的代码目录: " + result.getGeneratedCodeDir());
System.out.println("构建结果目录: " + result.getBuildResultDir());
}
@Test
void testSimpleHtmlWorkflow() {
WorkflowContext result = new CodeGenWorkflow().executeWorkflow("创建一个简单的个人主页");
Assertions.assertNotNull(result);
System.out.println("生成类型: " + result.getGenerationType());
System.out.println("生成的代码目录: " + result.getGeneratedCodeDir());
System.out.println("构建结果目录: " + result.getBuildResultDir());
}
}
执行测试后,可以观察工作流的运行效果:


多测试几个不同的提示词,验证工作流的鲁棒性:

延伸思考:除了Java,LangGraph4j的类似思想也可以应用于其他语言。例如,在Go中可以使用workflow包实现轻量级状态机;在C++中可以通过std::variant和std::visit模拟多态节点。但LangGraph4j提供的声明式图结构和内置的状态管理,能显著减少样板代码。
总结与展望
本文从LangGraph4j的Demo实践出发,逐步深入到项目级AI工作流改造的全流程。通过自定义WorkflowContext状态上下文、Mock节点验证、真实节点开发以及单元测试,我们构建了一个可扩展的AI生成网站工作流。LangGraph4j不仅简化了复杂业务流程的编排,还提供了与Java生态无缝集成的能力。
在未来的项目中,你可以将类似的工作流模式应用于:
- 多步骤数据处理管道
- AI Agent的决策与工具调用
- 微服务间的编排与补偿
掌握LangGraph4j,就是掌握了一种高效构建AI应用的基础设施能力。
[AFFILIATE_SLOT_2]
浙公网安备 33010602011771号