数据截至 (上游 commit daa7624a2755)
agentic:多智能体编排收敛成同一个 Planner 循环
30 秒导读:
langchain4j-agentic是 LangChain4j 后加的多智能体编排层。它最值得学的不是"支持了哪几种工作流", 而是它把所有工作流压成了同一套东西:一个执行循环 + 一块共享黑板 + 一个可替换的Planner。 顺序、并行、循环、条件四种"工作流",在源码里只是四个几十行的Planner实现;连 LLM 自主调度的 supervisor 也是一个Planner。
本章讲编排层。单个 agent 怎么从 Java 接口变成一次 LLM 调用,见 02-ai-services.md; agent 手里的工具怎么来的,见 03-tool-calling.md。
1. 这是什么(零基础也能懂)
一句话定义: 让多个 AI agent 按某种编排方式协作完成一个任务的 Java 层。
解决什么问题: 一个 agent 干不完的活,要拆给几个 agent 干。比如"写一篇小说": 先让写手出初稿、再让编辑按受众改写、再让另一个编辑按文体改写、最后让评分员打分,分不够就回炉重来。
这里面有四类烦人的事,都不是"调模型"本身:
| 烦人的事 | 具体是什么 |
|---|---|
| 谁下一个跑 | 顺序?并行?循环到满意为止?看条件分支? |
| 数据怎么传 | 上一个 agent 的输出,怎么变成下一个 agent 的入参 |
| 中途挂了怎么办 | 跑到第 3 个 agent 时进程崩了,重启后要能接着跑 |
| 人要插一脚 | 中间需要人确认/补信息,流程得停下来等 |
它能做什么(功能):
- 五种内置编排:顺序、并行、并行 map、循环、条件路由,外加 LLM 自主调度的 supervisor。
- 一块所有 agent 共享的键值黑板(
AgenticScope),兼作执行轨迹记录仪。 - 可持久化 + 崩溃恢复:黑板和"跑到第几步"都能落盘,重启后从断点续跑。
- 人机协同(human-in-the-loop)、异步 agent、流式输出透传。
- 接外部 agent:A2A 协议远端 agent、MCP 工具都能当成本地 agent 用。
- 可观测性:监听器、执行监控器、生成 HTML 拓扑图和执行报告。
用起来什么样: 定义两个 agent 接口,拼成一个顺序工作流,一次调用跑完。
// 示意,非源码;接口定义与用法取自 langchain4j-agentic/src/test/java/dev/langchain4j/agentic/Agents.java:137-171
public interface CreativeWriter {
@UserMessage("Generate a story about {{topic}}.")
@Agent(description = "Generate a story", outputKey = "story") // 输出写进黑板的 "story" 键
String generateStory(@V("topic") String topic);
}
public interface AudienceEditor {
@UserMessage("Rewrite the story for {{audience}}: {{story}}")
@Agent(description = "Edit for audience", outputKey = "story") // 读黑板的 story,改完写回 story
String editStory(@V("story") String story, @V("audience") String audience);
}
UntypedAgent novelCreator = AgenticServices.sequenceBuilder() // 顺序编排
.subAgents(creativeWriter, audienceEditor)
.outputKey("story")
.build();
String story = (String) novelCreator.invoke(
Map.of("topic", "dragons and wizards", "audience", "young adults"));
重点看两件事:agent 之间没有直接互相调用,它们只跟黑板打交道;outputKey / @V 就是黑板的写键和读键。
真实用法见测试 langchain4j-agentic/src/test/java/dev/langchain4j/agentic/WorkflowAgentsIT.java:116-157(check_sequential_agents)。
一句话直觉: 把它想成一间会议室——黑板挂在墙上(AgenticScope),
门口站着一个主持人(Planner)决定"下一个谁上台",台上的人只读黑板、写黑板,谁也不认识谁。
2. 顶层全景(它大概怎么转)
一次 novelCreator.invoke(...) 的控制流,从左到右读:
用户调用
│
▼
┌──────────────┐ 建 proxy ┌────────────────────────────┐
│ AgenticServices│ ──────────▶ │ ① 执行引擎(唯一的一个) │
│ 各种 builder │ │ PlannerBasedInvocationHandler│
└──────────────┘ └──────────┬─────────────────┘
│ 问"下一步调谁"
▼
┌────────────────────┐
│ ② 主持人 Planner │ ← 顺序/并行/循环/条件/supervisor
│ firstAction │ 只在这里不一样
│ nextAction │
└──────────┬─────────┘
返回 Action:call / done / noOp
│
▼
┌────────────────────┐
│ ③ 调用器 AgentExecutor│ ── 反射调 @Agent 方法
└──────────┬─────────┘
│ 读入参 / 写结果 / 记一笔
▼
┌───── ───────────────┐
│ ④ 黑板 AgenticScope │ state + agentInvocations
└────────────────────┘
跑完一个 agent 后,③ 会回头通知 ①(onSubagentInvoked),① 再问 ② 要下一个 Action,如此往复直到 done。
部件职责一句话:
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
AgenticServices | 静态门面,所有 builder 的入口 | langchain4j-agentic/src/main/java/dev/langchain4j/agentic/AgenticServices.java |
PlannerBasedInvocationHandler | 唯一的执行引擎,JDK 动态代理的 InvocationHandler | .../internal/PlannerBasedInvocationHandler.java |
Planner | 决策接口,回答"下一步调谁" | .../planner/Planner.java |
Action | 决策的词汇表:call / done / noOp | .../planner/Action.java |
AgentExecutor | 把 Action 落地成一次真实的方法反射调用 | .../internal/AgentExecutor.java |
AgentInvoker | 从黑板取参数、调方法、发监听事件 | .../internal/AgentInvoker.java |
AgenticScope | 共享黑板 + 调用轨迹 | .../scope/AgenticScope.java |
主线走一遍(高层):
AgenticServices.sequenceBuilder()拿到一个 builder,.build()时把SequentialPlanner::new交给统一的build(Supplier<Planner>)(internal/AbstractServiceBuilder.java:170-181)。- 那里 new 出
PlannerBasedInvocationHandler,包成 JDK 动态代理,返回给你当 agent 接口用。 - 你调接口方法 →
invoke分发到executeAgentMethod(internal/PlannerBasedInvocationHandler.java:196-207):把入参写进黑板,建Planner,启动循环。 - 循环反复问
Planner、执行 agent、把结果写回黑板,直到Action.isDone()。 - 按
outputKey从黑板取最终结果返回。
3. 入口:AgenticServices 门面与 @Agent 标注
这一节讲"你写的东西怎么被识别成 agent"。
3.1 一张 builder 清单
AgenticServices 是纯静态门面,私有构造(AgenticServices.java:78),全部能力就是下面这些工厂方法:
| 方法 | 产出 | 源码位置 |
|---|---|---|
agentBuilder() / agentBuilder(Class) | 单个 agent(无编排) | AgenticServices.java:119、:129 |
sequenceBuilder() | 顺序工作流 | AgenticServices.java:143、:153 |
parallelBuilder() | 并行工作流 | AgenticServices.java:160、:170 |
parallelMapperBuilder() | 对集合每项复制一份子 agent 并行跑 | AgenticServices.java:178、:189 |
loopBuilder() | 循环工作流 | AgenticServices.java:196、:206 |
conditionalBuilder() | 条件路由 | AgenticServices.java:213、:223 |
supervisorBuilder() | LLM 自主调度 | AgenticServices.java:231、:241 |
plannerBuilder() | 自带 Planner,自定义编排 | AgenticServices.java:248、:257 |
a2aBuilder(url) | 把远端 A2A agent 包成本地 agent | AgenticServices.java:268、:280 |
humanInTheLoopBuilder() | 人机协同 agent | AgenticServices.java:136 |
createAgenticSystem(Class) | 从注解声明式地建整套系统 | AgenticServices.java:323-384 |
注意 plannerBuilder() 的存在:它把内部机制直接开放为公开 API——你自己写个 Planner 实现就能定义新编排。
这也是"所有工作流都是 Planner"这条结论最直接的证据。测试里就有一个"两两配对并行"的自定义 planner
(langchain4j-agentic/src/test/java/dev/langchain4j/agentic/CustomPlannerIT.java:21-79,ParallelInPairsPlanner)。
工作 流 builder 还有一层 SPI 间接:workflowAgentsBuilder() 用 ServiceLoader 找 WorkflowAgentsBuilder 实现,
找不到才用默认的 WorkflowAgentsBuilderImpl.INSTANCE(AgenticServices.java:83-99)。Quarkus/Spring 集成靠这个换实现。
3.2 @Agent:方法级的标注
agent 不是类,是方法。@Agent 只能打在方法上(Agent.java:14-16),关键属性:
| 属性 | 作用 | 行号 |
|---|---|---|
description / value | 给 LLM 看的能力描述(supervisor 靠它选人) | Agent.java:31、:39 |
outputKey | 结果写进黑板的哪个键 | Agent.java:46 |
async | 异步调用,不阻塞后续 agent | Agent.java:55 |
optional | 入参在黑板里缺失时静默跳过而不是报错 | Agent.java:63 |
summarizedContext | 哪些别的 agent 参与构造本 agent 的上下文 | Agent.java:78 |
3.3 AgentInvoker:方法与黑板之间的适配层
AgentInvoker 负责"把黑板上的键值,凑成这个方法的实参"。它是接口,invoke 是带监听事件的默认实现
(internal/AgentInvoker.java:32-44):调用前发 beforeAgentInvocation,调用后发 afterAgentInvocation。
真正反射调用在 internalInvoke(internal/AgentInvoker.java:46-57),里面有个细节:
调用前把黑板塞进 LangChain4jManaged.setCurrent(...),finally 里再清掉——这样被调 agent 内部也能拿到当前 AgenticScope。
按 agent 来源分出几个实现:
| 实现 | 用于 | 取参数的方式 |
|---|---|---|
MethodAgentInvoker | 普通 @Agent 接口方法 | 按 @V/参数名从黑板取(internal/MethodAgentInvoker.java:14-16) |
UntypedAgentInvoker | UntypedAgent.invoke(Map) | 整个 Map 直接进黑板 |
MapperAgentInvoker | parallelMapper 的每个实例 | 固定绑一个集合元素 + 下标 |
SpecAgentInvoker | 非 AI agent(普通 Java 方法) | 由 AgentSpecsProvider 描述 |
分发点在 AgentInvoker.fromMethod(internal/AgentInvoker.java:73-79)。
4. 核心:唯一的执行引擎
这节是本章的心脏。 无论你用哪个 builder,跑起来的都是同一个 PlannerLoop.loop()。