Agent 与 Agentic AI
本节介绍如何使用 langchain4j-agentic 模块构建 Agentic AI 应用。请注意,整个模块均属于实验性功能,未来版本可能会发生变化。
Agentic 系统
尽管 AI Agent 尚无全球一致认可的定义,但一些新兴模式展示了如何协调并组合多个 AI 服务的能力,从而创建可完成更复杂任务的 AI 增强型应用。这些模式通常被称为“Agentic 系统”或“Agentic AI”。它们通常使用大语言模型(LLM)来编排任务执行、管理工具使用,并在交互过程中维护上下文。
根据 Anthropic 研究人员近期发表的文章,这些 Agentic 系统架构可归为两大类:工作流和纯 Agent。

本教程讨论的 langchain4j-agentic 模块提供了一组抽象和工具,帮助你构建工作流和纯 Agentic AI 应用。它支持定义工作流、管理工具使用,以及在与不同 LLM 的交互中维护上下文。
LangChain4j 中的 Agent
LangChain4j 中的 Agent 使用 LLM 执行一项或一组特定任务。与普通 AI 服务类似,可以通过一个只有单个方法的接口来定义 Agent,只需添加 @Agent 注解。
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("Generates a story based on the given topic")
String generateStory(@V("topic") String topic);
}
建议同时在该注解中提供对 Agent 用途的简短描述,尤其是在纯 Agentic 模式中使用时;其他 Agent 需要了解此 Agent 的能力,才能明智地决定如何以及何时使用它。构建 Agent 时,也可以通过 Agent builder 的 description 方法以编程方式提供此描述。
Agent 还必须拥有一个在 Agentic 系统内唯一标识它的名称。该名称可以在 @Agent 注解中指定,或通过 Agent builder 的 name 方法以编程方式指定。未指定时,名称取自标注 @Agent 的方法名。
现在可以使用 AgenticServices.agentBuilder() 方法构建此 Agent 的实例,并指定接口及要使用的聊天模型。
CreativeWriter creativeWriter = AgenticServices
.agentBuilder(CreativeWriter.class)
.chatModel(myChatModel)
.outputKey("story")
.build();
从本质上讲,Agent 就是普通 AI 服务,具备相同功能,但能够与其他 Agent 组合,以创建更复杂的工作流和 Agentic 系统。
与 AI 服务的另一主要差异是 outputKey 参数:它指定用于存储 Agent 调用结果的共享变量名称,使同一 Agentic 系统中的其他 Agent 可以使用该结果。或者,也可以直接在 @Agent 注解中声明输出名称,而非像本例一样以编程方式设置,从而省略代码中的该设置并在此处添加。
@Agent(outputKey = "story", description = "Generates a story based on the given topic")
AgenticServices 类提供了一组静态工厂方法,用于创建和定义 langchain4j-agentic 框架提供的各种 Agent。
AgenticScope 简介
langchain4j-agentic 模块引入了 AgenticScope 概念,它是参与同一 Agentic 系统的 Agent 之间共享的数据集合。AgenticScope 用于存储共享变量:一个 Agent 可以写入其生成的结果以进行通信,另一个 Agent 可以读取这些变量,汇集执行任务所需的信息。这使 Agent 能够有效协作,按需共享信息和结果。
AgenticScope 还会自动记录其他相关信息,例如所有 Agent 的调用序列及其响应。在调用 Agentic 系统的主 Agent 时会自动创建它,并在必要时通过回调以编程方式提供。讨论 langchain4j-agentic 实现的 Agentic 模式时,将通过实际示例说明 AgenticScope 的不同用法。
工作流模式
langchain4j-agentic 模块提供一组抽象,用于以编程方式编排多个 Agent 并创建 Agentic 工作流模式。这些模式可以组合为更复杂的工作流。
顺序工作流
顺序工作流是最简单的模式:多个 Agent 依次被调用,每个 Agent 的输出作为下一个 Agent 的输入。当一系列任务需要按特定顺序执行时,此模式十分有用。
例如,可以为前面定义的 CreativeWriter Agent 配置 AudienceEditor Agent,以编辑生成的故事,使其更适合特定受众。
public interface AudienceEditor {
@UserMessage("""
You are a professional editor.
Analyze and rewrite the following story to better align
with the target audience of {{audience}}.
Return only the story and nothing else.
The story is "{{story}}".
""")
@Agent("Edits a story to better fit a given audience")
String editStory(@V("story") String story, @V("audience") String audience);
}
再配合一个非常相似的 StyleEditor,为特定风格执行同样的工作。
public interface StyleEditor {
@UserMessage("""
You are a professional editor.
Analyze and rewrite the following story to better fit and be more coherent with the {{style}} style.
Return only the story and nothing else.
The story is "{{story}}".
""")
@Agent("Edits a story to better fit a given style")
String editStory(@V("story") String story, @V("style") String style);
}
请注意,此 Agent 的输入参数使用变量名注解。实际上,传给 Agent 的参数值并非直接提供,而是取自具有这些名称的 AgenticScope 共享变量。这使 Agent 能够访问工作流中先前 Agent 的输出。如果编译 Agent 类时启用了 -parameters 选项,从而在运行时保留方法参数名称,则可以省略 @V 注解,变量名会从参数名自动推断。
此时可以创建一个组合这三个 Agent 的顺序工作流:CreativeWriter 的输出会传给 AudienceEditor 和 StyleEditor 作为输入,最终输出是编辑后的故事。
CreativeWriter creativeWriter = AgenticServices
.agentBuilder(CreativeWriter.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
AudienceEditor audienceEditor = AgenticServices
.agentBuilder(AudienceEditor.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
StyleEditor styleEditor = AgenticServices
.agentBuilder(StyleEditor.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
UntypedAgent novelCreator = AgenticServices
.sequenceBuilder()
.subAgents(creativeWriter, audienceEditor, styleEditor)
.outputKey("story")
.build();
Map<String, Object> input = Map.of(
"topic", "dragons and wizards",
"style", "fantasy",
"audience", "young adults"
);
String story = (String) novelCreator.invoke(input);
这里的 novelCreator Agent 实际上是一个实现顺序工作流的 Agentic 系统,它依次调用三个子 Agent。由于此 Agent 的定义没有提供类型化接口,顺序 Agent builder 会返回 UntypedAgent 实例;它是可通过输入 map 调用的通用 Agent。
public interface UntypedAgent {
@Agent
Object invoke(Map<String, Object> input);
}
输入 map 中的值会复制到 AgenticScope 共享变量,以供子 Agent 访问。novelCreator Agent 的输出也取自名为“story”的 AgenticScope 共享变量;在小说创建和编辑工作流执行期间,其他所有 Agent 都曾重写此变量。
请注意,单个 Agent 也可以定义为 UntypedAgent 实例,无需提供类型化接口。例如,CreativeWriter Agent 可按如下方式定义:
UntypedAgent creativeWriter = AgenticServices.agentBuilder()
.chatModel(BASE_MODEL)
.description("Generate a story based on the given topic")
.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}}.
""")
.inputKey(String.class, "topic")
.returnType(String.class) // String is the default return type for untyped agents
.outputKey("story")
.build();
另一方面,工作流 Agent 也可以选择提供类型化接口,从而可使用强类型的输入和输出进行调用。此时可将 UntypedAgent 接口替换为更具体的接口,例如:
public interface NovelCreator {
@Agent
String createNovel(@V("topic") String topic, @V("audience") String audience, @V("style") String style);
}
这样便可按如下方式创建和使用 novelCreator Agent:
NovelCreator novelCreator = AgenticServices
.sequenceBuilder(NovelCreator.class)
.subAgents(creativeWriter, audienceEditor, styleEditor)
.outputKey("story")
.build();
String story = novelCreator.createNovel("dragons and wizards", "young adults", "fantasy");
循环工作流
一种更好地利用 LLM 能力的常见方法是:重复调用能够编辑或改进文本的 Agent,迭代地润色故事等文本。可使用循环工作流模式实现,即重复调用 Agent,直到满足某个条件。
可以使用 StyleScorer Agent,根据风格与要求的契合程度生成评分。
public interface StyleScorer {
@UserMessage("""
You are a critical reviewer.
Give a review score between 0.0 and 1.0 for the following
story based on how well it aligns with the style '{{style}}'.
Return only the score and nothing else.
The story is: "{{story}}"
""")
@Agent("Scores a story based on how well it aligns with a given style")
double scoreStyle(@V("story") String story, @V("style") String style);
}
然后可以在循环中结合此 Agent 和 StyleEditor,迭代改进故事,直到评分达到特定阈值(如 0.8),或达到最大迭代次数。
StyleEditor styleEditor = AgenticServices
.agentBuilder(StyleEditor.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
StyleScorer styleScorer = AgenticServices
.agentBuilder(StyleScorer.class)
.chatModel(BASE_MODEL)
.outputKey("score")
.build();
UntypedAgent styleReviewLoop = AgenticServices
.loopBuilder()
.subAgents(styleScorer, styleEditor)
.maxIterations(5)
.exitCondition( agenticScope -> agenticScope.readState("score", 0.0) >= 0.8)
.build();
这里 styleScorer Agent 将输出写入名为“score”的 AgenticScope 共享变量;循环的退出条件会访问并评估同一变量。
exitCondition 方法接受 Predicate<AgenticScope> 参数,默认会在每次 Agent 调用后评估它;条件一经满足就退出循环,以尽量减少 Agent 调用次数。不过,也可以通过在循环 builder 上配置 testExitAtLoopEnd(true),仅在一次循环结束时检查退出条件,从而在测试该条件前强制调用所有 Agent。或者,exitCondition 还可接受 BiPredicate<AgenticScope, Integer> 参数,其中第二个参数是当前循环迭代计数器。例如,以下循环定义:
UntypedAgent styleReviewLoop = AgenticServices
.loopBuilder()
.subAgents(styleScorer, styleEditor)
.maxIterations(5)
.testExitAtLoopEnd(true)
.exitCondition( (agenticScope, loopCounter) -> {
double score = agenticScope.readState("score", 0.0);
return loopCounter <= 3 ? score >= 0.8 : score >= 0.6;
})
.build();
会使循环在前 3 次迭代中评分至少为 0.8 时退出;否则会降低质量预期,在评分至少为 0.6 时终止循环,并且即使已满足退出条件,也会强制最后调用一次 styleEditor Agent。
配置好此 styleReviewLoop 后,可将其视为单个 Agent,并与 CreativeWriter Agent 按顺序组合,创建 StyledWriter Agent。
public interface StyledWriter {
@Agent
String writeStoryWithStyle(@V("topic") String topic, @V("style") String style);
}
从而实现结合故事生成和风格审查过程的更复杂工作流。
CreativeWriter creativeWriter = AgenticServices
.agentBuilder(CreativeWriter.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
StyledWriter styledWriter = AgenticServices
.sequenceBuilder(StyledWriter.class)
.subAgents(creativeWriter, styleReviewLoop)
.outputKey("story")
.build();
String story = styledWriter.writeStoryWithStyle("dragons and wizards", "comedy");
并行工作流
有时并行调用多个 Agent 很有用,尤其当它们可针对同一输入独立工作时。可以使用并行工作流模式实现:同时调用多个 Agent,并将它们的输出合并为单个结果。
例如,使用电影和美食专家,为具有特定氛围的美好夜晚生成若干方案;每个方案结合与该氛围相匹配的电影和餐点。
public interface FoodExpert {
@UserMessage("""
You are a great evening planner.
Propose a list of 3 meals matching the given mood.
The mood is {{mood}}.
For each meal, just give the name of the meal.
Provide a list with the 3 items and nothing else.
""")
@Agent
List<String> findMeal(@V("mood") String mood);
}
public interface MovieExpert {
@UserMessage("""
You are a great evening planner.
Propose a list of 3 movies matching the given mood.
The mood is {mood}.
Provide a list with the 3 items and nothing else.
""")
@Agent
List<String> findMovie(@V("mood") String mood);
}
由于两位专家的工作彼此独立,可以如下使用 AgenticServices.parallelBuilder() 方法并行调用它们:
FoodExpert foodExpert = AgenticServices
.agentBuilder(FoodExpert.class)
.chatModel(BASE_MODEL)
.outputKey("meals")
.build();
MovieExpert movieExpert = AgenticServices
.agentBuilder(MovieExpert.class)
.chatModel(BASE_MODEL)
.outputKey("movies")
.build();
EveningPlannerAgent eveningPlannerAgent = AgenticServices
.parallelBuilder(EveningPlannerAgent.class)
.subAgents(foodExpert, movieExpert)
.executor(Executors.newFixedThreadPool(2))
.outputKey("plans")
.output(agenticScope -> {
List<String> movies = agenticScope.readState("movies", List.of());
List<String> meals = agenticScope.readState("meals", List.of());
List<EveningPlan> moviesAndMeals = new ArrayList<>();
for (int i = 0; i < movies.size(); i++) {
if (i >= meals.size()) {
break;
}
moviesAndMeals.add(new EveningPlan(movies.get(i), meals.get(i)));
}
return moviesAndMeals;
})
.build();
List<EveningPlan> plans = eveningPlannerAgent.plan("romantic");
这里,EveningPlannerAgent 中定义的 AgenticScope output 函数可组合两个子 Agent 的输出,创建由符合给定氛围的电影和餐点组成的 EveningPlan 对象列表。output 方法虽然对并行工作流尤为重要,但实际上可用于任何工作流模式,用于定义如何将子 Agent 的输出组合为单个结果,而非仅从 AgenticScope 返回一个值。executor 方法还允许可选地提供用于并行执行子 Agent 的 Executor;否则默认使用内部缓存线程池。
并行映射工作流
并行映射工作流是并行工作流的一种变体:对集合中的每个元素并行执行一次相同的子 Agent。换言之,它映射列表中的所有元素,重复调用同一子 Agent 独立处理每一项。当需要对一批输入并发应用相同操作时,此模式很有用。
例如,创建一个为人员列表生成个性化星座运势的 Agent:
public interface PersonAstrologyAgent {
@SystemMessage("""
You are an astrologist that generates horoscopes based on the user's name and zodiac sign.
""")
@UserMessage("""
Generate the horoscope for {{person}}.
The person has a name and a zodiac sign. Use both to create a personalized horoscope.
""")
@Agent(description = "An astrologist that generates horoscopes for a person", outputKey = "horoscope")
String horoscope(@V("person") Person person);
}
使用 AgenticServices.parallelMapperBuilder(),可以创建一个将此 Agent 扇出到集合上的工作流,自动为每一项创建一个实例:
PersonAstrologyAgent personAstrologyAgent = AgenticServices
.agentBuilder(PersonAstrologyAgent.class)
.chatModel(BASE_MODEL)
.outputKey("horoscope")
.build();
BatchHoroscopeAgent agent = AgenticServices
.parallelMapperBuilder(BatchHoroscopeAgent.class)
.subAgents(personAstrologyAgent)
.itemsProvider("persons")
.executor(Executors.newFixedThreadPool(3))
.build();
List<Person> persons = List.of(
new Person("Mario", "aries"),
new Person("Luigi", "pisces"),
new Person("Peach", "leo"));
List<String> horoscopes = agent.generateHoroscopes(persons);