langchain4j-agentic是 LangChain4j 的实验性模块,提供构建多智能体系统的完整框架。本章解析 @Agent 架构、五种工作流模式、SupervisorAgent 自主规划、以及 Planner 接口的扩展机制。
8.1 核心理念:从单一 AI Service 到多智能体系统
ini
单一 AI Service:
User → [AI Service] → Answer
多智能体系统:
User → [路由器] → [医疗专家] → [验证器] → Answer
↘ [法律专家] ↗
↘ [技术专家] ↗
LangChain4j 参考 Anthropic 的分类:Workflows (确定性编排)和 Pure Agents(LLM 自主规划)。
8.2 定义 Agent
Agent 本质上就是带有 @Agent 注解的 AI Service 接口:
java
public interface CreativeWriter {
@UserMessage("""
You are a creative writer.
Generate a draft of a story no more than
3 sentences long around the given topic.
Return only the story and nothing else.
The topic is {{topic}}.
""")
@Agent(
value = "Generates a creative story based on the given topic",
outputKey = "story" // 输出存入 AgenticScope 的 "story" 变量
)
String generateStory(@V("topic") String topic);
}
@Agent 注解
java
public @interface Agent {
String value() default ""; // 描述(其他 Agent / Supervisor 依赖这个做决策)
String name() default ""; // 唯一标识(默认取方法名)
String outputKey() default ""; // 输出在共享状态中的键名
boolean optional() default false; // 缺少输入参数时跳过而非抛异常
boolean async() default false; // 异步执行
}
构建 Agent
java
CreativeWriter writer = AgenticServices
.agentBuilder(CreativeWriter.class)
.chatModel(model)
.outputKey("story")
.name("creativeWriter")
.build();
Untyped Agent(无定义接口,完全动态):
java
UntypedAgent agent = AgenticServices.agentBuilder()
.description("Generates a story")
.userMessage("Write a story about {{topic}}")
.inputKey(String.class, "topic") // 从 scope 读 "topic"
.returnType(String.class)
.outputKey("story") // 写到 scope 的 "story"
.chatModel(model)
.build();
Map<String, Object> result = agent.invoke(Map.of("topic", "dragons"));
8.3 AgenticScope:共享状态
AgenticScope 是所有 Agent 之间的共享数据容器:
java
// Agent A 产出 → scope.writeState("story", storyText)
// Agent B 消费 → scope.readState("story")
// 类型安全的访问(TypedKey)
scope.readState(Category.class); // 编译期就知道类型
scope.writeState(StoryKey.class, story); // 类型检查
// 弱类型访问
scope.readState("story", "default"); // 需要手动类型转换
TypedKey:告别字符串 Key
java
// 定义强类型 Key
public static class UserRequest implements TypedKey<String> {}
public static class Category implements TypedKey<RequestCategory> {
@Override
public RequestCategory defaultValue() {
return RequestCategory.UNKNOWN;
}
}
// 在 Agent 中使用
@Agent(typedOutputKey = Category.class)
RequestCategory classify(@K(UserRequest.class) String request);
// 在条件逻辑中使用(无需类型转换)
.subAgents(
scope -> scope.readState(Category.class) == RequestCategory.MEDICAL,
medicalExpert
)
8.4 五种工作流模式
1. Sequential(顺序执行)
java
UntypedAgent pipeline = AgenticServices.sequenceBuilder()
.subAgents(creativeWriter, audienceEditor, styleEditor) // A → B → C
.outputKey("story")
.build();
Map<String, Object> result = pipeline.invoke(Map.of(
"topic", "dragons and wizards",
"style", "fantasy",
"audience", "young adults"
));
2. Loop(循环执行)
java
UntypedAgent reviewLoop = AgenticServices.loopBuilder()
.subAgents(styleScorer, styleEditor) // 评分 → 润色 → 评分 → ...
.maxIterations(5)
.exitCondition((scope, loopCounter) -> {
double score = scope.readState("score", 0.0);
if (loopCounter <= 3) return score >= 0.8; // 前3轮要求严格
return score >= 0.6; // 之后放宽
})
.testExitAtLoopEnd(true) // 在每轮结束时检查(而非开始)
.build();
// 循环可以作为任何工作流的子 Agent
UntypedAgent full = AgenticServices.sequenceBuilder()
.subAgents(creativeWriter, reviewLoop)
.outputKey("story")
.build();
3. Parallel(并行执行)
java
UntypedAgent planner = AgenticServices.parallelBuilder()
.subAgents(foodExpert, movieExpert)
.executor(Executors.newFixedThreadPool(2))
.outputKey("plans")
.output(scope -> {
List<String> movies = scope.readState("movies", List.of());
List<String> meals = scope.readState("meals", List.of());
// 合并两个并行 Agent 的输出
return IntStream.range(0, Math.min(movies.size(), meals.size()))
.mapToObj(i -> new EveningPlan(movies.get(i), meals.get(i)))
.toList();
})
.build();
4. Parallel Mapper(批量并行)
java
UntypedAgent batchProcessor = AgenticServices.parallelMapperBuilder()
.subAgent(horoscopeAgent) // 同一个 Agent
.itemsProvider("persons") // 遍历 persons 列表,每个元素并发执行
.build();
// 输入 {"persons": [Person("张三"), Person("李四"), Person("王五")]}
// 输出: ["张三的运势...", "李四的运势...", "王五的运势..."]
⚠️ 子 Agent 不能使用 ChatMemory(同一个 Agent 实例会被多次并发调用)。
5. Conditional(条件分支)
java
// Step 1: 分类器(也是一个 Agent)
UntypedAgent router = AgenticServices.agentBuilder()
.userMessage("Categorize this request: {{request}}")
.returnType(RequestCategory.class)
.outputKey("category")
.chatModel(model)
.build();
// Step 2: 条件路由
UntypedAgent experts = AgenticServices.conditionalBuilder()
.subAgents(
scope -> scope.readState("category", RequestCategory.UNKNOWN) == RequestCategory.MEDICAL,
medicalExpert
)
.subAgents(
scope -> scope.readState("category", RequestCategory.UNKNOWN) == RequestCategory.LEGAL,
legalExpert
)
.subAgents(
scope -> scope.readState("category", RequestCategory.UNKNOWN) == RequestCategory.TECHNICAL,
technicalExpert
)
.build();
// Step 3: 组合
UntypedAgent fullSystem = AgenticServices.sequenceBuilder()
.subAgents(router, experts)
.outputKey("response")
.build();
8.5 工作流组合
所有工作流模式都可以互相嵌套:
java
// 顺序 { 创意写作 → 循环{ 评分 → 润色 } → 并行{ 审核A, 审核B } }
UntypedAgent novelCreator = AgenticServices.sequenceBuilder()
.subAgents(creativeWriter, reviewLoop, reviewPanel)
.outputKey("novel")
.build();
8.6 Pure Agentic AI:SupervisorAgent
SupervisorAgent 让 LLM 自己决定调用哪些 Agent、以什么顺序、何时结束:
java
SupervisorAgent supervisor = AgenticServices.supervisorBuilder()
.chatModel(plannerModel) // 需要一个强推理能力的模型做规划
.subAgents(withdrawAgent, creditAgent, exchangeAgent)
.responseStrategy(SupervisorResponseStrategy.SUMMARY)
.contextGenerationStrategy(SupervisorContextStrategy.CHAT_MEMORY)
.supervisorContext("""
Policies:
- Prefer internal tools
- Currency in USD
- No external APIs without explicit approval
""")
.build();
String result = supervisor.chat("Transfer 100 EUR from Mario's account to Georgios");
Supervisor 的规划过程
ini
User: "Transfer 100 EUR from Mario to Georgios"
↓
Supervisor 生成 Plan:
1. AgentInvocation(agentName="exchange", arguments={"amount":"100","from":"EUR","to":"USD"})
2. AgentInvocation(agentName="withdraw", arguments={"name":"Mario","amount":"92"})
3. AgentInvocation(agentName="credit", arguments={"name":"Georgios","amount":"92"})
4. AgentInvocation(agentName="done", arguments={"summary":"Transferred 100 EUR..."})
↓
按 Plan 顺序执行
三种响应策略
java
public enum SupervisorResponseStrategy {
LAST, // 返回最后一个 Agent 的输出(默认)
SUMMARY, // 返回 Supervisor 生成的摘要(适合事务场景)
SCORED // 通知 Scorer Agent 对比 LAST vs SUMMARY,选更好的
}
8.7 跨切面特性
可选 Agent
java
@Agent(optional = true)
String generateStory(@V("topic") String topic);
// 如果 topic 不在 scope 中,跳过而非抛异常
异步 Agent
java
@Agent(async = true)
String fetchExternalData(@V("query") String query);
// 在独立线程中执行,不阻塞后续 Agent(前提是后续 Agent 不依赖它的输出)
动态模型选择
java
StoryEditor editor = AgenticServices.agentBuilder(StoryEditor.class)
.chatModel(scope -> {
// 根据上一轮的评分动态切换模型
double score = scope.readState("score", 0.0);
return score > 7.5 ? cheapModel : expensiveModel;
})
.outputKey("story")
.build();
错误处理
java
UntypedAgent agent = AgenticServices.sequenceBuilder()
.subAgents(writer, editor, formatter)
.errorHandler(errorContext -> {
if (errorContext.agentName().equals("generateStory")
&& errorContext.exception() instanceof MissingArgumentException ex) {
// 写入默认值并重试
errorContext.agenticScope().writeState("topic", "dragons and wizards");
return ErrorRecoveryResult.retry();
}
if (errorContext.agentName().equals("formatStory")) {
// 跳过非关键 Agent 的错误,返回已有结果
return ErrorRecoveryResult.result(errorContext.agenticScope().readState("story"));
}
return ErrorRecoveryResult.throwException();
})
.outputKey("story")
.build();
跨 Agent 补偿
java
UntypedAgent transferWorkflow = AgenticServices.sequenceBuilder()
.subAgents(creditAgent, debitAgent, notificationAgent)
.compensateOnError(true) // 任一 Agent 失败,前面的全部回滚
.outputKey("result")
.build();
// 与 @Tool 的 @CompensateFor 机制复用同一套注解
8.8 可观测性
AgentListener
java
AgentListener listener = new AgentListener() {
@Override
public void beforeAgentInvocation(AgentRequest request) {
log.info("Agent {} starting", request.agentName());
}
@Override
public void afterAgentInvocation(AgentResponse response) {
log.info("Agent {} done in {}ms, tokens: {}",
response.agentName(),
response.duration().toMillis(),
response.tokenUsage().totalTokenCount());
}
@Override
public boolean inheritedBySubagents() {
return true; // 传播到所有子 Agent
}
};
// 注册
AgenticServices.agentBuilder(CreativeWriter.class)
.chatModel(model)
.listener(listener)
.build();
AgentMonitor(内置监控)
java
// 方式1:手动注册
AgentMonitor monitor = new AgentMonitor();
// 注册到 agentBuilder: .listener(monitor)
// 方式2:通过接口扩展自动创建
public interface StyledWriter extends MonitoredAgent {
@Agent("Write a creative story")
String generateStory(@V("topic") String topic, @V("style") String style);
}
StyledWriter writer = AgenticServices.agentBuilder(StyledWriter.class)
.chatModel(model)
.outputKey("story")
.build();
AgentMonitor monitor = writer.agentMonitor();
// 查看执行记录
MonitoredExecution execution = monitor.successfulExecutions().get(0);
System.out.println(execution); // 树形嵌套输出
// 生成 HTML 报告
HtmlReportGenerator.generateReport(monitor, Path.of("agent-execution.html"));
HtmlReportGenerator.generateTopology(writer, Path.of("agent-topology.html"));
8.9 声明式 API
除了编程式 Builder,也可以用注解声明工作流:
java
@ParallelAgent(
outputKey = "plans",
subAgents = { FoodExpert.class, MovieExpert.class }
)
public interface EveningPlannerAgent {
List<EveningPlan> plan(@V("mood") String mood);
@ParallelExecutor
static Executor executor() {
return Executors.newFixedThreadPool(2);
}
@Output
static List<EveningPlan> createPlans(
@V("movies") List<String> movies,
@V("meals") List<String> meals
) {
// 合并并行结果
return IntStream.range(0, Math.min(movies.size(), meals.size()))
.mapToObj(i -> new EveningPlan(movies.get(i), meals.get(i)))
.toList();
}
}
// 一键实例化
EveningPlannerAgent planner = AgenticServices.createAgenticSystem(EveningPlannerAgent.class, model);
注解速查
| 注解 | 适用范围 | 用途 |
|---|---|---|
@Output |
所有模式 | 从 scope 组装最终输出 |
@ActivationCondition |
Conditional | 子 Agent 激活条件 |
@ExitCondition |
Loop | 循环退出条件 |
@ParallelExecutor |
Parallel / ParallelMapper | 线程池 |
@ErrorHandler |
所有模式 | 错误处理逻辑 |
@ChatModelSupplier |
所有 LLM Agent | ChatModel 提供者 |
@ToolsSupplier |
所有 LLM Agent | 工具对象提供者 |
@SupervisorRequest |
Supervisor | Supervisor 指令 |
8.10 自定义 Agentic 模式:Planner 接口
所有内置模式都基于 Planner 接口。你也可以实现自己的:
java
public interface Planner {
default void init(InitPlanningContext ctx) {}
default Action firstAction(PlanningContext ctx) { return nextAction(ctx); }
Action nextAction(PlanningContext ctx);
}
Action 返回值:
call(agent1, agent2, ...)--- 调用(并行执行多个)done()--- 完成
参考实现:ParallelPlanner
java
public class ParallelPlanner implements Planner {
private List<AgentInstance> agents;
@Override
public void init(InitPlanningContext ctx) {
this.agents = ctx.subagents();
}
@Override
public Action firstAction(PlanningContext ctx) {
return call(agents); // 所有 Agent 并行执行一次
}
@Override
public Action nextAction(PlanningContext ctx) {
return done(); // 然后结束
}
}
参考实现:GOAP(Goal-Oriented Action Planning)
一种算法化的规划器------计算从当前状态到目标的最短路径:
java
// 每个 Agent 声明的 inputKey = 前置条件(需要什么)
// outputKey = 后置条件(产出什么)
// 目标:AgenticScope 的 outputKey
// 调用时构造依赖图,Dijkstra 求最短路径
// 例如: inputs {name, sign} → path [extractPerson, extractSign, horoscope, findStory, write]
参考实现:P2P(Peer-to-Peer)
去中心化模式------所有 Agent 平等,由 scope 状态驱动:
erlang
AgenticScope 状态变化 → 检查每个 Agent 的激活条件 → 激活满足条件的 Agent → 更新状态 → ...
8.11 实验性模块说明
langchain4j-agentic 当前标记为实验性,API 和行为可能在后续版本中变化。生产使用前评估稳定性。