大模型负责它擅长的不确定性任务,工作流负责确定性的流程控制。
LangGraph4j 多 Agent 工作流:把 AI 放进确定性的业务流水线
Spring AI 的 ChatClient 很适合解决:
用户输入
↓
LLM
↓
Tool Calling
↓
最终回答
这类“一进一出”的对话任务。
但有些任务不是一次模型调用就结束,而是:
步骤 A
↓
判断
↓
步骤 B
↓
判断
↓
步骤 C
↓
步骤 D
例如岗位数据采集:
判断输入类型
↓
AI 提取岗位
↓
业务规则清洗
↓
发布岗位
这种 多步骤 + 条件分支 + 状态传递 的任务,更适合工作流引擎。
LangGraph4j 就是用来解决这类问题的。
1. ChatClient 和 LangGraph4j 的区别
ChatClient 关注的是:
怎么完成一次 LLM 调用。
LangGraph4j 关注的是:
多个处理步骤应该按照什么顺序执行。
可以简单理解成:
ChatClient
= 一个节点里的 AI 能力
LangGraph4j
= 把很多节点组织成流程
所以两者不是替代关系。
反而经常是:
LangGraph4j
↓
Node A
↓
Node B → ChatClient → LLM
↓
Node C
↓
Node D
AI 只是整个工作流中的某一个环节。
2. LangGraph4j 的三个核心概念
理解 LangGraph4j,只需要抓住:
Node
Edge
State
Node:节点
一个节点就是一个独立处理步骤。
例如:
task_classify
任务分类
task_gather
数据采集
draft_washer
数据清洗
draft_publish
数据发布
代码类似:
.addNode(
"task_gather",
node_async(taskGatherAgent::apply)
)
节点只负责自己的事情,不需要知道整个流程怎么走。
Edge:边
边决定:
当前节点执行完成以后去哪。
固定边:
A → B
表示 A 执行完成一定进入 B。
条件边:
→ B
A
→ C
根据当前状态决定下一步。
例如:
.addConditionalEdges(
"task_classify",
edge_async(state ->
state.getTask() == null
? END
: "gather"
),
...
)
也就是:
分类成功
→ 继续采集
分类失败
→ END
条件边是工作流非常重要的能力。
3. State:节点之间怎么传数据?
节点之间不应该:
nodeA.callNodeB();
而是通过一个共享状态传递数据。
例如:
INPUT
→ 原始输入
TASK
→ 分类后的任务
GATHER
→ AI 提取结果
WASHER
→ 清洗后的记录
PUBLISH
→ 最终发布结果
整个执行过程就是不断丰富 State:
开始
State
{
input
}
分类完成
State
{
input,
task
}
采集完成
State
{
input,
task,
gather
}
清洗完成
State
{
input,
task,
gather,
washer
}
发布完成
State
{
input,
task,
gather,
washer,
publish
}
所以可以把 State 理解成:
整条工作流共享的数据总线。
4. 一个完整的岗位采集工作流
整个流程可以画成:
START
↓
Task Classify
│
├── 无效 → END
│
↓
Task Gather
│
├── 无数据 → END
│
↓
Draft Washer
│
├── 无有效数据 → END
│
↓
Draft Publish
↓
END
这里非常重要的一点是:
流程怎么走,不是让 LLM 决定,而是业务代码决定。
例如:
task == null
gatherList.isEmpty()
validIds.isEmpty()
全部是确定性判断。
这和让大模型回答:
“你觉得下一步应该执行哪个节点?”
完全不同。
5. AI 应该放在哪?
这个设计里,大模型只负责:
非结构化内容
↓
LLM
↓
结构化岗位数据
比如:
网页
图片
招聘公告
Excel 内容
交给大模型提取:
List<Job>
后面的:
公司类型标准化
招聘类型转换
年份校验
数据发布
全部使用 Java 业务代码。
这体现了一个非常重要的 Agent 设计原则:
确定性的事情,不要交给 LLM。
例如:
if (list.isEmpty())
Java 可以 100% 确定。
就没必要问模型:
“请判断这些数据是否为空。”
否则只会增加:
Token
延迟
成本
不确定性
6. LangGraph4j 真正解决的是编排
你可以把整个系统理解为:
LangGraph4j
│
┌──────────┼──────────┐
↓ ↓ ↓
Java Node AI Node Java Node
│ │ │
│ ChatClient │
│ ↓ │
│ LLM │
│ │
└──────── State ──────┘
所以:
LangGraph4j 不是让所有步骤都变成 AI,而是把 AI 节点和普通业务节点组织在一张图里。
这才是最值得沉淀的理解。
7. Java 使用 LangGraph4j 要注意 State 序列化
LangGraph4j 的状态底层可以理解成:
Map<String, Object>
如果里面只放:
String
Integer
Boolean
通常没什么问题。
但实际项目经常需要:
GatherTaskEntity
GatherVo
List<Job>
这种复杂对象。
因此需要给这些类型注册序列化器:
serializer.mapper().register(
GatherTaskEntity.class,
new JsonSerializer<>(
GatherTaskEntity.class
)
);
否则跨节点传递时可能出现:
类型丢失
反序列化失败
状态恢复失败
所以 Java 场景下要特别记住:
State 中放复杂 POJO 时,要提前设计好序列化。
8. 为什么工作流还需要 SSE?
工作流和普通请求不一样。
一次执行可能持续:
10 秒
30 秒
甚至几分钟
如果前端只发送:
POST /workflow/run
然后一直等结果,用户根本不知道执行到哪了。
因此可以通过 SSE 推送:
任务分类开始
↓
任务分类完成
数据采集开始
↓
已提取 23 条岗位
数据清洗开始
↓
18 条通过
发布开始
↓
发布完成
于是:
LangGraph4j Node
↓
Workflow Event
↓
SSE
↓
Web 管理后台
管理员就能实时看到整个流程。
这属于工作流非常重要的:
Observability / 可观测性。
9. 对话 Agent 和工作流 Agent 怎么选?
这个地方最值得以后做项目时直接套用。
对话型 Agent
例如:
用户:
帮我找武汉 Java 岗位
Agent:
好的……
用户:
25K 以上
Agent:
……
重点是:
自然语言理解
多轮上下文
Tool Calling
用户交互
适合:
Spring AI
ChatClient
ChatMemory
ReAct
工作流型 Agent
例如:
上传招聘文件
↓
识别
↓
采集
↓
清洗
↓
发布
重点是:
步骤明确
输入输出明确
有条件分支
需要状态传递
需要失败终止
适合:
LangGraph4j
可以直接记一个判断方法:
任务核心是“和人交流”,优先 ChatClient;任务核心是“把事情按步骤做完”,优先 Workflow。
10. 两套 Agent 可以同时存在
真实项目通常不是二选一。
完全可以:
AI Application
│
┌───────────┴───────────┐
↓ ↓
Conversational Workflow
Agent Agent
│ │
ChatClient LangGraph4j
│ │
用户岗位推荐 岗位数据采集
│ │
└──────────┬────────────┘
↓
Database
工作流负责:
把岗位数据采集进来
对话 Agent 负责:
把这些岗位推荐给用户
两个系统职责不同,但可以共享底层数据。
11. 最值得沉淀的架构原则
这一篇真正重要的不是:
addNode()
addEdge()
addConditionalEdges()
而是这几个原则:
确定性流程
→ Workflow 控制
不确定性理解
→ LLM 处理
节点之间
→ State 传递
流程分支
→ Conditional Edge
复杂 POJO
→ Serializer
长任务进度
→ SSE
核心思想可以概括为:
不要让 LLM 控制所有事情,让工作流控制流程,让 LLM 只处理真正需要智能判断的部分。
最后总结
LangGraph4j 最适合解决:
多步骤
+
条件分支
+
共享状态
+
流程可观测
它和 Spring AI 的关系可以理解成:
Spring AI
→ 提供 AI 能力
LangGraph4j
→ 编排 AI 能力和业务能力
最终:
Workflow
↓
确定性 Java
↓
LLM
↓
确定性 Java
↓
最终结果
最值得记住的一句话是:
工作流引擎的价值,不是让 AI 参与更多步骤,而是把 AI 限制在它真正擅长的不确定性环节,把流程控制、校验、清洗、发布这些确定性任务交还给业务代码。
把你前面学的内容继续串起来,现在整个体系已经越来越完整:
ChatClient
→ 单次 AI 调用编排
Advisor
→ 增强调用过程
ReAct
→ 控制模型与工具循环
MCP
→ 扩展工具能力
LangGraph4j
→ 编排多个业务 / AI 节点
State
→ 在节点之间传递数据
Conditional Edge
→ 控制工作流分支