前言
LangChain4j 是 Java 生态中比较成熟的 LLM 应用开发框架,提供了丰富的多智能体协调能力。本文以一个《部落冲突》玩家分析系统为载体,深入剖析 LangChain4j 中 @SupervisorAgent、AgenticServices、工具绑定机制、内存管理、消息编排等核心组件的工作原理与实现细节。以下是具体的实现。
💡 前情回顾 如果你想先了解框架Spring AI的落地应用,欢迎阅读我之前的文章:
一、核心组件全景图
二、Supervisor 的并行协调机制
2.1 注解定义与元数据
@SupervisorAgent 是 LangChain4j 提供的核心协调注解,用于声明一个接口为监督者智能体:
@SupervisorAgent(
subAgents = {
BattleTacticsAgent.class,
DataSummaryAgent.class,
DefenseTacticsAgent.class,
UpgradePlannerAgent.class
}
)
@SystemMessage("""
# Role:部落冲突(CoC)高级参谋长
## 核心职责
1. 分析用户的请求意图,找出需要执行哪些分析模块。
2. 并行调用对应的子 Agent 来获取数据。
3. 收集所有子 Agent 的结果,严格按照 JSON 格式输出最终报告。
""")
String answer(@V("openId") String openId, @UserMessage String request);
注解参数解析:
| 参数 | 类型 | 作用 |
|---|---|---|
subAgents | Class<?>[] | 声明该Supervisor管辖的子Agent类型数组 |
supervisorResponseStrategy | SupervisorResponseStrategy | 结果聚合策略,默认实现为JSON拼接 |
supervisorResponseStrategy | SupervisorResponseStrategy | 结果聚合策略,为可选的 enum 值,默认 LAST |
maxAgentsInvocations | int | 单次执行中最大子Agent调用次数,默认 10 |
注意:LangChain4j 1.12 版本中,
@SupervisorAgent的推荐用法是用@SubAgent注解嵌套声明子Agent及其 outputKey。上面的数组写法当前可用,但后续可能会变为 Deprecated,建议关注官方文档的更新。
2.2 Supervisor 的工作原理
当调用 supervisor.answer(openId, request) 时,LangChain4j 内部经历以下流程:
2.3 SupervisorResponseStrategy 聚合策略
LangChain4j 提供了三种内置聚合策略:
public enum SupervisorResponseStrategy {
LAST, // 默认:仅返回最后调用的子Agent的最终响应
SUMMARY, // 返回Supervisor与其子Agent交互的摘要
SCORED // 使用内部LLM对最后响应和摘要分别打分,返回得分高者
}
在业务代码中,可以这样配置:
@Bean
public SuperAgent cocSupervisor(ChatModel chatModel,
DataSummaryAgent dataSummaryAgent,
UpgradePlannerAgent upgradePlannerAgent,
BattleTacticsAgent battleTacticsAgent,
DefenseTacticsAgent defenseTacticsAgent) {
return AgenticServices.supervisorBuilder(SuperAgent.class)
.chatModel(chatModel)
.subAgents(dataSummaryAgent, upgradePlannerAgent,
battleTacticsAgent, defenseTacticsAgent)
// 可选:指定聚合策略(默认为 LAST)
.supervisorResponseStrategy(SupervisorResponseStrategy.SUMMARY)
.build();
}
3.3 子Agent并行调用
当 Supervisor 分析出需要多个子Agent并行执行时:
// Supervisor内部的并行执行逻辑(伪代码)
public String answer(String openId, String request) {
// 1. 分析意图,确定需要调用的子Agent
List<SubAgent> targetAgents = analyzeIntent(request);
// 2. 并行执行所有子Agent
List<Future<String>> futures = new ArrayList<>();
for (SubAgent agent : targetAgents) {
futures.add(executor.submit(() ->
agent.answer(buildSubPrompt(openId, agent))
));
}
// 3. 等待所有结果
List<String> results = new ArrayList<>();
for (Future<String> future : futures) {
results.add(future.get(timeout, TimeUnit.SECONDS));
}
// 4. 聚合结果
return mergeStrategy.merge(request, results);
}
3.4 变量传递机制
@V 注解实现 Supervisor 到子Agent的变量传递:
@SupervisorAgent(subAgents = {...})
@SystemMessage("""
当前请求的玩家 openId 是:{{openId}}。
当你在调用任何子 Agent 时,必须将 "{{openId}}" 作为参数传给他们。
""")
String answer(@V("openId") String openId, @UserMessage String request);
内部实现中,Supervisor 会将 @V("openId") 绑定的参数自动注入到子Agent调用中:
// Supervisor构建子Agent调用时的参数绑定
public String callSubAgent(SubAgent agent, String openId) {
// 查找子Agent接口中接受openId的方法
Method method = findMethodWithOpenIdParam(agent.getClass());
// 将Supervisor接收的openId传递给子Agent
return agent.answer(openId, buildSubTaskPrompt(openId, agent));
}
3.5 串行依赖处理
某些场景下子Agent之间存在依赖关系,可以通过 Supervisor 的系统消息描述:
@SystemMessage("""
## 执行规则
1. 首先调用 DataSummaryAgent 获取玩家基础数据
2. 基于基础数据,调用 UpgradePlannerAgent 生成规划
3. 最后调用 BattleTacticsAgent 推荐进攻流派
## 依赖关系说明
UpgradePlannerAgent 必须等待 DataSummaryAgent 完成!
""")
虽然默认是并行执行,但通过系统消息的约束引导,LLM会选择串行执行流程。
三、AgenticServices 工厂解析
3.1 整体架构
AgenticServices 是 LangChain4j 提供的能力工厂类,通过 Builder 模式创建各类 AI 服务:
public class AgenticServices {
// 通用AI服务构建器
public static <T> AiServicesBuilder<T> builder(Class<T> interfaceClass) {
return new AiServicesBuilder<>(interfaceClass);
}
// 工具型Agent构建器(用于带@Tool注解的Agent)
public static <T> AgentBuilder<T, ?> agentBuilder(Class<T> agentServiceClass) {
return new AgentBuilder<>(agentServiceClass);
}
// Supervisor构建器
public static <T> SupervisorAgentService<T> supervisorBuilder(Class<T> agentServiceClass) {
return new SupervisorAgentService<>(agentServiceClass);
}
}
3.2 AgentBuilder 内部机制
public class AgentBuilder<T, SELF extends AgentBuilder<T, SELF>> {
private final Class<T> agentServiceClass;
private ChatModel chatModel;
private SystemMessageProvider systemMessageProvider;
private List<Object> tools = new ArrayList<>();
private ChatMemory chatMemory;
private ChatMemoryProvider chatMemoryProvider;
private ContentRetriever contentRetriever;
// 关键方法:注册可调用工具
public SELF tools(Object... tools) {
this.tools.addAll(Arrays.asList(tools));
return self();
}
// 关键方法:注册系统消息提供器
public SELF systemMessageProvider(
SystemMessageProvider provider) {
this.systemMessageProvider = provider;
return self();
}
// 最终build()方法
public T build() {
// 1. 通过动态代理创建接口实现
// 2. 绑定chatModel、tools、memory等组件
// 3. 注册到Spring容器(如在@Configuration中使用@Bean)
return (T) Proxy.newProxyInstance(
agentServiceClass.getClassLoader(),
new Class[]{agentServiceClass},
new AgentInvocationHandler(this)
);
}
}
说明:LangChain4j 内部的具体类名和包路径可能随版本变化(如
AgenticServicesBuilder可能更名为AgentBuilder),但 Builder 模式的核心逻辑保持一致。上面代码是基于 1.12.2-beta22 版本行为的抽象描述。
3.3 动态代理机制
LangChain4j 使用 Java 动态代理实现接口,当调用 agent.answer() 时:
public class AgentInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 提取@SystemMessage和@UserMessage注解
SystemMessage sysMsg = method.getAnnotation(SystemMessage.class);
UserMessage userMsg = method.getAnnotation(UserMessage.class);
// 2. 从@V注解提取变量绑定
Map<String, Object> variables = extractVariables(method, args);
// 3. 获取系统消息(可能通过provider动态获取)
String systemMessage = systemMessageProvider.provide(memoryId);
// 4. 构建最终Prompt
String prompt = buildPrompt(systemMessage, userMsgTemplate, variables);
// 5. 调用ChatModel
AiMessage response = chatModel.sendUserMessage(prompt);
// 6. 检查是否需要执行工具
if (response.hasToolCalls()) {
return executeToolCalls(response.toolCalls(), tools);
}
// 7. 直接返回文本结果
return response.text();
}
}
四、工具绑定机制(@Tool)
4.1 注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tool {
String name() default "";
String description() default "";
}
4.2 工具注册与调用流程
// 1. 定义工具类
public class PlayerDataTool {
@Tool(name = "queryPlayerBuildInfo",
description = "根据openid查询玩家所有建筑升级数据")
public String queryPlayerBuildInfo(String openid) {
// 实现查询逻辑
return buildInfoDao.selectByOpenid(openid);
}
@Tool(name = "queryBuildLevel",
description = "根据openid和建筑名称查询玩家该建筑当前等级")
public String queryBuildLevel(String openid, String buildName) {
// 实现查询逻辑
return buildLevelDao.selectLevel(openid, buildName);
}
}
// 注:@Tool注解的name和description属性均可省略。
// 省略name时默认使用方法名;省略description时使用name作为tool description。
// 2. 注册到Agent
@Bean
public DataSummaryAgent dataSummaryAgent(ChatModel chatModel) {
return AgenticServices.agentBuilder(DataSummaryAgent.class)
.chatModel(chatModel)
.tools(playerDataTool) // ← 工具绑定
.build();
}
4.3 工具调用的内部实现
当 LLM 返回 AiMessage 包含工具调用时:
// AgentInvocationHandler.invoke() 中的工具执行逻辑
if (response.hasToolCalls()) {
List<ToolExecution> executions = new ArrayList<>();
for (ToolCall toolCall : response.toolCalls()) {
// 1. 根据toolCall.name()查找对应的@Tool方法
Method toolMethod = findToolMethod(toolCall.name());
// 2. 反序列化参数
Object[] params = deserializeArguments(toolCall.arguments(), toolMethod);
// 3. 反射调用
Object result = toolMethod.invoke(toolObject, params);
// 4. 包装结果
executions.add(new ToolExecution(toolCall.id(), result));
}
// 5. 将工具结果反馈给LLM生成最终回复
return chatModel.sendToolResults(executions);
}
4.4 工具选择的LLM引导
LangChain4j 会自动将 @Tool 方法的签名和描述注入到 System Prompt 中:
你是一个AI助手。你可以使用以下工具:
- queryPlayerBuildInfo(openid: String): String
根据openid查询玩家所有建筑升级数据
- queryBuildLevel(openid: String, buildName: String): String
根据openid和建筑名称查询玩家该建筑当前等级
当你需要查询玩家数据时,请调用合适的工具。
五、系统消息与内存管理
5.1 SystemMessage 注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemMessage {
String value() default "";
}
在接口定义时使用:
public interface BattleTacticsAgent {
@SystemMessage("""
# Role:部落冲突进攻战术专家
# 任务
分析当前版本主流进攻流派,给出配兵建议和打法思路。
# 输出格式
### ⚔️ 流派名称
* **兵种与数量**:...
""")
String recommendTactics(String openid);
}
5.2 动态 SystemMessageProvider
最强大的特性是可以动态提供 System Message:
@Bean
public DataSummaryAgent dataSummaryAgent(ChatModel chatModel) {
return AgenticServices.agentBuilder(DataSummaryAgent.class)
.chatModel(chatModel)
.systemMessageProvider(memoryId -> {
// memoryId 可以用于区分不同会话
// 此处从Redis动态加载Prompt
return stringRedisTemplate.opsForValue()
.get("ai:prompt:module1:" + memoryId);
})
.tools(playerDataTool)
.build();
}
应用场景:
- 多租户Prompt隔离:不同用户加载不同Prompt模板
- AB测试:对不同用户群使用不同策略
- Prompt热更新:无需重启即可更新Prompt内容
- 会话历史注入:根据memoryId加载历史上下文
5.3 Memory 组件
LangChain4j 的 Memory 用于维护多轮对话状态:
public interface ChatMemory {
// 获取对话历史
List<ChatMessage> messages();
// 添加消息到历史
void add(ChatMessage message);
// 清除历史
void clear();
}
默认实现:MessageWindowChatMemory
public class MessageWindowChatMemory implements ChatMemory {
private final int maxMessages;
public MessageWindowChatMemory(int maxMessages) {
this.maxMessages = maxMessages;
}
@Override
public List<ChatMessage> messages() {
List<ChatMessage> all = store.getMessages();
// 只返回最近N条消息(滑动窗口)
return all.subList(Math.max(0, all.size() - maxMessages), all.size());
}
}
在Agent中使用Memory:
// 带Memory的Agent配置
@Bean
public DataSummaryAgent dataSummaryAgent(ChatModel chatModel,
ChatMemory chatMemory) {
return AgenticServices.agentBuilder(DataSummaryAgent.class)
.chatModel(chatModel)
.chatMemory(chatMemory) // ← Memory绑定
.tools(playerDataTool)
.build();
}
// Agent 接口中通过 @MemoryId 标记会话标识参数
public interface DataSummaryAgent {
String answer(@MemoryId String memoryId, @V("openId") String openId);
}
// 调用时传入 memoryId
String result = agent.answer("user123", openId);
5.4 消息编排流程
六、Spring Boot集成
6.1 配置类结构
@Configuration
public class AiConfig {
// 1. 注入ChatModel(由Spring Boot自动配置或自定义)
@Autowired
private ChatModel chatModel;
// 2. 注入工具Bean
@Resource
private PlayerDataTool playerDataTool;
@Resource
private TavilyTools tavilyTools;
// 3. 按需注入Memory
@Autowired
private ChatMemory chatMemory;
// 4. 创建各子Agent
@Bean
public DataSummaryAgent dataSummaryAgent(ChatModel chatModel) {
return AgenticServices.agentBuilder(DataSummaryAgent.class)
.chatModel(chatModel)
.systemMessageProvider(memoryId ->
stringRedisTemplate.opsForValue().get("ai:prompt:module1"))
.tools(playerDataTool)
.build();
}
// 5. 创建Supervisor(注入所有子Agent)
@Bean
public SuperAgent cocSupervisor(ChatModel chatModel,
DataSummaryAgent dataSummaryAgent,
UpgradePlannerAgent upgradePlannerAgent,
BattleTacticsAgent battleTacticsAgent,
DefenseTacticsAgent defenseTacticsAgent) {
return AgenticServices.supervisorBuilder(SuperAgent.class)
.chatModel(chatModel)
.subAgents(dataSummaryAgent, upgradePlannerAgent,
battleTacticsAgent, defenseTacticsAgent)
.build();
}
}
6.2 ChatModel配置
LangChain4j支持多种ChatModel实现:
langchain4j:
open-ai:
chat-model:
base-url: https://api.minimaxi.com/v1
api-key: ${COC_API_KEY}
model-name: MiniMax-M2.7
temperature: 0.2
timeout: PT3M
log-requests: true
log-responses: true
七、流程图:完整请求处理链路
八、总结与展望:LangChain4j vs LangGraph vs Spring AI
8.1 LangChain4j 的多智能体能力总结
通过上述各组件的有机组合,LangChain4j 提供了一条从简单单 Agent 到复杂多 Agent 协调的完整演进路径:
| 能力层级 | 核心组件 | 适用场景 |
|---|---|---|
| 简单问答 | AiServices + @SystemMessage | 单轮问答、文本生成 |
| 工具调用 | AgenticServices.agentBuilder() + @Tool | 需要查询数据库、调用 API 等工具的场景 |
| 多 Agent 协调 | @SupervisorAgent + AgenticServices.supervisorBuilder() | 意图分发、并行子任务执行、结果聚合 |
| 动态提示词 | SystemMessageProvider | 多租户、AB 测试、Prompt 热更新 |
| 多轮对话 | ChatMemory + @MemoryId | 需要上下文记忆的连续对话 |
在 Java 生态中,LangChain4j 是目前集成度最高、对 Spring Boot 支持最完善的选择之一,它让 Java 开发者可以用纯接口定义 + 注解驱动的方式快速构建 AI 应用,无需深究底层协议。但与之并肩的还有官方出品的 Spring AI,以及多智能体领域事实标准 LangGraph,三者在技术定位和能力边界上有显著差异。
8.2 三者核心定位对比:为什么多智能体流程编排选 LangGraph?
| 维度 | LangChain4j | Spring AI | LangGraph |
|---|---|---|---|
| 开发语言 | Java / Spring Boot ⭐ | Java / Spring Boot ⭐ | Python / TypeScript |
| 上手门槛 | 低(注解驱动,社区成熟) | 低(Spring 原生风格,自动配置) | 中(需理解图和状态概念) |
| 多 Agent 能力 | 中等(Supervisor 模式,隐式路由) | 弱(无内置的多 Agent 编排,需手动组合) | 强(显式有向图、条件边、循环、Map-Reduce) ⭐ |
| 流程编排灵活性 | 一般(依赖 LLM 自主决策) | 一般(需自行封装) | 极强(图级控制、状态持久化、断点续跑) ⭐ |
| 工具/函数调用 | 丰富(@Tool 注解,自动生成 Schema) | 丰富(@Tool 注解,与函数式 Bean 结合紧密) | 灵活(自定义 Node 函数,可集成任意库) |
| 状态管理与可观测性 | 较弱(调用链隐式,依赖 LLM 日志) | 较弱(无内置图状态,需借助 Micrometer) | 极强(State 对象流转、图可视化、回放与回溯) ⭐ |
| 高级 Multi-Agent 模式 | 有限(Supervisor 为主) | 需手动编码实现 | 丰富(分层、辩论、Reflexion、ReAct 循环) ⭐ |
| Spring 整合深度 | 较好(Starter、自动配置) | 极佳(官方出品,极致原生体验) ⭐ | 无(需自行封装 HTTP/gRPC 服务) |
| 多模型支持 | 广泛(OpenAI、MiniMax、Ollama 等) | 广泛(与 Spring 生态无缝对接) | 广泛(LangChain 生态模型全部可用) |
| 生产就绪程度 | 简单场景可快速落地 | 企业级 Java 环境首选,生态协同强 | 复杂场景长期迭代首选,学术与工业验证充分 |
8.3 如何做出技术选型?
① 什么时候选 Spring AI?
- 当你的团队以 Spring Boot 为核心技术栈,追求极致的一体化开发体验时,Spring AI 是最自然的选择。
- 它提供了与 Spring 生态的无缝整合(如
@Tool直接对标 Service Bean、与WebClient、Retry、Observability深度结合),尤其适合在已有 Spring 微服务体系上扩展 AI 能力。 - 但它的多智能体能力目前较弱,如果你需要复杂的 Agent 协作,不建议用 Spring AI 从零造轮子,而应将其作为调用下游 Agent 服务的“门面”。
② 什么时候选 LangChain4j?
- 当你的业务场景需要显式的多 Agent 协调(如 Supervisor 模式),但又不想脱离 Java 技术栈时,LangChain4j 是最佳折中。
- 它比 Spring AI 提供了更成熟的 Agent 抽象(
@SupervisorAgent、AgenticServices),又比 LangGraph 更容易落地到 Spring Boot 中。 - 适合:智能客服、报告生成、数据汇总与分析等流程相对线性的多 Agent 场景。
③ 什么时候必须选 LangGraph?
- 当你面对高度复杂的多 Agent 协同:如多步规划、条件分支、持续循环(ReAct)、人工审批介入、多代理辩论等。
- 当你需要图级别的可观测性:步骤追踪、状态可视化、断点续跑、回放。
- 当你需要前沿的 Multi-Agent 模式:如 Reflexion、Self-Refine、Multi-Agent Collaboration 等,这些论文实现的第一语言通常是 Python。
- 推荐架构:用 Spring AI / LangChain4j 作为 Java 侧的 AI 服务入口,将核心 Agent 流程编排交给 LangGraph(Python 微服务) ,通过 HTTP 或 gRPC 通信。这种“混合架构”兼顾了 Java 生态的稳定性和 Python 生态的灵活性,是目前企业级应用的最佳实践。
8.4 最后的话
LangChain4j 与 Spring AI 让 Java 开发者迈入了 AI 应用开发的大门,而 LangGraph 则打开了多智能体协同的复杂世界。在真实业务中,技术选型从来不是“非此即彼”,而是根据场景灵活组合。当前阶段,如果你想构建真正复杂的、可进化的多智能体系统,LangGraph 依然是流程编排的底座,而 Java 侧的工具(LangChain4j/Spring AI)负责将其包装为业务可用的服务。随着 Spring AI 的 Roadmap 中已开始出现 StateGraph 等概念,未来 Java 生态的 Multi-Agent 能力也值得期待。
完整的pom文件如下:
xml
<properties>
<docker.repostory>registry.cn-hangzhou.aliyuncs.com/coccounter/coc</docker.repostory>
<java.version>17</java.version>
<spring-cloud.version>2025.0.1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.21.2</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.39</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 阿里云 OSS 依赖 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<!-- Knife4j Swagger UI 依赖 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.12.2-beta22</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.12.2-beta22</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-agentic</artifactId>
<version>1.12.2-beta22</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.1.0</version>
</plugin>
</plugins>
</build>