上一个系列讲了Spring AI得到反馈效果不错,有人私信我说这个和Langchain4j有什么区别。如果站在使用方面,都是基于Java的大模型应用研发的工具,本质上没太大区别。但是从细节层面来说还是有很多不同之处,所以索性借此机会,给大家分享一下Langchain4j框架。在本系列中会按照Spring AI系列的顺序来写Langchain4j,这样的好处是可以对比两者不同的细节。
注意:由于框架不同版本改造会有些使用的不同,因此本次系列中使用基本框架是langchain4j-1.9.1,JDK版本使用的是19。另外本系列尽量使用Java原生态,尽量不依赖于Spring和Spring Boot。虽然langchain4j也支持Spring Boot集成,但是如果是使用Spring Boot框架,那为何不索性使用Spring AI。
本系列的所有代码地址: https://github.com/forever1986/langchain4j-study
目录
- 1 什么是A2A
- 2 A2A协议内容
-
- 2.1 整体架构
- 2.2 数据格式
- 2.3 交互机制
- 2.4 A2A 与 MCP
- 3 示例演示
-
- 3.1 agent server
- 3.2 agent client
- 3.3 演示结果
上一章讲解了Agent之HumanIn,这一章来说一个最近很火的名词A2A。
1 什么是A2A
A2A的全称叫Agent2Agent Protocol,它是一项开放标准,它能让人工智能Agent在不同的平台和框架之间进行通信和协作,而无需考虑其底层技术。该协议旨在通过实现真正的多Agent场景来充分发挥Agent人工智能的优势。可能这么说比较官方,不容易理解,回顾一下之前在《Langchain4j 系列之十四 – MCP入门》中学习的MCP,它的作用其实就规范一个远程工具的交互协议,原先工具可能只是本地运行,而且每个工具的定义规范有些不太一样,为了解决这些问题,我认为MCP做了三个重要的事情:
- 1)统一了整体的架构,就是以client-server的架构,加上统一协议交互
- 2)统一了数据的格式(比如以json),里面包括tool、prompt、resources等
- 3)统一了交互的方式,如Stdio模式、SSE模式、treamable HTTP模式等
同理为了解决不同Agent之间的交互,包括本地、远程以及格式和交互等问题,由google发起的A2A协议,这是其官方网站。也是为了解决这些问题。
2 A2A协议内容
2.1 整体架构
首先看看整体架构,这个和之前MCP没有什么区别,A2A就是Agent之间的交互。包括几个关键的概念:User、A2A Client、A2A协议、A2A Server:

- User:终端用户可以是人类操作员,也可以是自动化服务。用户会发起请求或设定目标,需要借助一个或多个人工智能代理来获得帮助。
- A2A Client(Client Agent):代表用户运行的应用程序、服务或其他人工智能Agent。客户端使用 A2A 协议发起通信。 A2A Server(Remote Agent):一个人工智能Agent或一个具有Agent功能的系统,它公开一个实现 A2A 协议的 HTTP 端点。它接收来自客户端的请求,处理任务,并返回结果或状态更新。从客户端的角度来看,远程代理就像一个不透明(黑箱)的系统,意味着其内部运作、内存或工具都不对外暴露。
- A2A 协议:开放标准的协议,包括数据格式、交互方式的规范
2.2 数据格式
下面是A2A协议规定的一些关键的数据格式:
| Agent Card | 一个描述Agent身份、能力、端点、技能以及认证要求的 JSON 元数据文档。 | 使客户能够找到Agent,并了解如何以安全且有效的方式与他们进行互动。 |
| Task | 由Agent发起的一个具有状态的作业单元,具有唯一的标识符,并具有明确的生命周期。. | 有助于对长时间运行的操作进行追踪,并支持多轮交互和协作。 |
| Message | 客户与Agent之间的一次信息交流,包含内容及角色(“用户”或“Agent”)。 | 传达指令、背景信息、问题、答案或状态更新等内容,这些内容不一定属于正式的书面形式。 |
| Part | 在消息和文件中所使用的基本内容容器(例如,文本部分、文件部分、数据部分)。 | 为Agent提供了灵活性,使其能够在消息和工件中交换各种内容类型。 |
| Artifact | 在一项任务中,执行者所产生的可实际观察到的成果(例如,一份文档、一幅图像或结构化数据) | 提供Agent工作的具体成果,确保输出内容具有条理且易于检索。 |
上面的描述可能比较抽象,下面关于不同element做一下更细节说明:
- Agent Card:这个是一个 JSON 格式的文档,它提供了有关Agent的重要元数据。客户会解析这些信息来确定Agent是否适合完成特定任务、如何构建请求以及如何进行安全通信。关键信息包括身份、服务端点(URL)、代理到代理(A2A)功能、认证要求以及技能列表。简单理解就是一个Agent的说明书。
- Messages and Parts:一条消息代表了客户端与Agent之间的一次通信过程。它包含一个Role(“User”或“Agent”)以及一个唯一的消息标识符。它包含一个或多个“Part”对象,这些对象是实际内容的细粒度容器。简单理解就是Client发给Server的内容结构,主要的种类有:
- TextPart:包含纯文本内容。
- FilePart:代表一个文件。它可以以内联形式(使用 Base64 编码)传输,也可以通过 URI 传输。它包含诸如“名称”和“文件类型”等元数据。
- DataPart:携带结构化的 JSON 数据。这适用于表单、参数或任何机器可读的信息。
- Artifacts:一个Artifact代表着由remote Agent在任务处理过程中产生的有形输出或具体结果。与一般的消息不同,Artifact是实际的交付成果。Artifact具有唯一的Artifact标识符、一个便于人类理解的名称,并由一个或多个部分对象组成。Artifact与任务的生命周期紧密相关,并且可以逐步以流的形式传输给客户端。简单理解就是Server对Client的回复。
其它详细的结构可以参考官方说明。
2.3 交互机制
A2A 协议支持多种交互模式,以满足对响应性和持久性的不同需求。这些机制确保代理能够高效且可靠地交换信息,无论任务的复杂程度或持续时间如何:
- Request/Response (Polling):客户端发送请求,服务器进行响应。对于长时间运行的任务,客户端会定期向服务器发起请求以获取更新信息。
- Streaming with Server-Sent Events (SSE):客户端启动流式传输以从服务器接收实时的、增量的结果或状态更新,通过一个开放的 HTTP 连接进行传输。
- Push Notifications:对于非常长时间运行的任务或断开连接的情况,服务器可以在任务有重要更新时主动向客户端提供的自定义回调函数发送异步通知。
以上三种类型适合不同的交互场景,不同的远程Agent各自实现支持,因此可以在Agent Cards中说明。
2.4 A2A 与 MCP
从A2A这个协议出来之后,就有很多人拿它与MCP协议做比较,以下是我觉得总结比较到位的区别。首先是各个层面的区别
| 核心关注点 | Model到tool的连接 | Agent到agent的连接 |
| 交互模式 | Function calls, structured I/O | Conversational, long-running tasks |
| 应用场景 | 工具集成、API 调用、资源访问 | 多智能体协作、复杂任务分解、服务发现 |
| 抽象级别 | 低级别(特定功能) | 高级别(意图和能力) |
| 标准化 | 比较成熟规范 | 处于早期 |
其次两者之间,有自己本身的优势和挑战
| 优势 | 结构清晰,执行可预测与现有 API 框架集成简单降低将 AI 与工具连接的复杂性性能开销相对较低 | 支持动态发现和即兴协作适合开放式、复杂任务更接近自然的人类团队协作模式高度可扩展,易于添加新代理 |
| 挑战 | 灵活性有限,需要明确定义每个工具不适合高度动态或未知的任务难以表达复杂的协作需求 | 复杂的状态一致性管理安全和访问控制挑战显著的推理开销不成熟的部分故障处理机制 |
3 示例演示
代码参考lesson18子模块
本次示例通过简单演示一个agent server和一个agent client。通过简单的**Request/Response (Polling)**方式进行交互
1)新建lesson18子模块
3.1 agent server
代码参考lesson18子模块下a2aServer子模块
本示例a2a server是基于io.github.a2asdk实现的,这是由于目前实现a2a协议的有几个不同的插件,包括google自己。而langchain中目前引入的是io.github.a2asdk,因此本示例就采用这个
1)在lesson18子模块下,新建a2aServer子模块,其pom引入如下:
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-agentic</artifactId>
</dependency>
<!– 引入a2asdk的server插件,用于搭建a2a server –>
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-server-common</artifactId>
<version>0.3.2.Final</version>
</dependency>
<!– 引入spring-boot,用于搭建web server –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
2)在a2aServer子模块下,新建CreativeWriter接口,作为agent服务
package com.langchain.lesson18.server.agent;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
public interface CreativeWriter {
@UserMessage("""
你是一位富有创造力的作家。
围绕给定的主题生成一段不超过 3 句话的故事草稿。
只返回故事内容,不要其他任何信息。
主题为 {{topic}} 。
""")
@Agent(value = "根据给定的主题生成一个故事", name = "name" , description = "根据给定的主题生成一个故事", outputKey = "story", async = false)
String generateStory(@V("topic") String topic);
}
3)在a2aServer子模块下,新建AgentConfigure配置类,初始化chatmodel和agent
package com.langchain.lesson18.server.agent;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AgentConfigure {
// 创建ChatModel
@Bean
public ChatModel chatModel(){
//1.获取API KEY
String apiKey = System.getenv("ZHIPU_API_KEY");
//2.加载大模型
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(apiKey)
.baseUrl("https://open.bigmodel.cn/api/paas/v4")
.modelName("glm-4-flash-250414")
.build();
return chatModel;
}
// 创建Agent
@Bean
public CreativeWriter creativeWriter(ChatModel chatModel){
CreativeWriter creativeWriter = AgenticServices
.agentBuilder(CreativeWriter.class) // 代理的类
.chatModel(chatModel) // 大语言模型
.build();
return creativeWriter;
}
}
4)在a2aServer子模块下,新建ServerController类,提供对外服务
package com.langchain.lesson18.server.controller;
import com.langchain.lesson18.server.agent.CreativeWriter;
import io.a2a.spec.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
@RestController
public class ServerController {
@Autowired
private CreativeWriter creativeWriter;
/**
* 该方法就是对你的Agent Server提供的能力的描述
*/
@GetMapping("/.well-known/agent-card.json")
public AgentCard agentCard() {
return new AgentCard.Builder()
// agent的名称
.name("富有创造力作家的Agent")
// Agent的描述
.description("""
你是一位富有创造力的作家。
围绕给定的主题生成一段不超过 3 句话的故事草稿。
只返回故事内容,不要其他任何信息。
主题为 {{topic}} 。
""")
// URL是与Agent进行交互的基 URL 端点,这里设置a2a是因为我们下面的url地址是这个
.url("http://localhost:8080/a2a")
// Agent服务的提供方的相关信息
.provider(new AgentProvider(
"langchain lesson",
"http://localhost:8080/"
))
// 指该Agent或其 API 的版本标识符。
.version("1.0.0")
// 该Agent所支持的各种能力
.capabilities(new AgentCapabilities.Builder()
.streaming(false) // 是否支持Streaming with Server-Sent Events (SSE)
.pushNotifications(false) // 是否支持Push Notifications
.stateTransitionHistory(false) //是否支持“state转换历史记录”,表明该agent是否支持提供state转换历史信息
.build())
// 默认输入格式
.defaultInputModes(Collections.singletonList("text"))
// 默认输出格式
.defaultOutputModes(Collections.singletonList("text"))
// 该Agent所提供的具体skill列表。(skill是Anthropic的Claude模型提供一种使用大模型能力的范式,有兴趣的朋友可以去了解一下)
.skills(Collections.emptyList())
.build();
}
/**
* 这里只提供一个非streaming方式的访问接口,也就是Request/Response (Polling)方式
*/
@PostMapping(
path = "/a2a",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public JSONRPCResponse handleJsonRpcRequest(@RequestBody NonStreamingJSONRPCRequest request) {
// 1. 获取client传过来的信息
Message message = ((MessageSendParams)request.getParams()).message();
TextPart textPart = (TextPart)message.getParts().get(message.getParts().size()–1);
// 2. 调用agent回复内容
String result = creativeWriter.generateStory(textPart.getText());
System.out.println(result);
// 3.返回结果信息
return new SendMessageResponse(
"2.0",
request.getId(),
new Task.Builder()
.id(request.getId().toString())
.contextId("contextId")
.status(new TaskStatus(TaskState.COMPLETED))
// .history(resultMessage) // 历史结果,这里就不做演示,可以将历次聊天加入历史中,这样实现多轮聊天
.artifacts(Collections.singletonList(
new Artifact.Builder()
.artifactId("artifactId")
.name("name")
.description("description")
.parts(new TextPart(result)) // 将结果放入artifacts返回
.build()
))
.build(),
null
);
}
}
5)在a2aServer子模块下,新建A2AServerApplication 启动类
package com.langchain.lesson18.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class A2AServerApplication {
public static void main(String[] args) {
SpringApplication.run(A2AServerApplication.class, args);
}
}
6)启动A2AServerApplication 类,结果如下:

3.2 agent client
代码参考lesson18子模块下a2aClient子模块
1)在lesson18子模块下,新建a2aClient子模块,其pom引入:
<dependencies>
<!– 引入langchain4j的a2a的插件 –>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-agentic-a2a</artifactId>
</dependency>
</dependencies>
2)在a2aClient子模块下,新建CreativeWriter接口,用于作为Agent接口
package com.langchain.lesson18.client;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
public interface CreativeWriter {
@UserMessage("""
你是一位富有创造力的作家。
围绕给定的主题生成一段不超过 3 句话的故事草稿。
只返回故事内容,不要其他任何信息。
主题为 {{topic}} 。
""")
@Agent(value = "根据给定的主题生成一个故事", name = "name" , description = "根据给定的主题生成一个故事", outputKey = "story", async = false)
String generateStory(@V("topic") String topic);
}
3)在a2aClient子模块下,新建A2AClientTest类:
package com.langchain.lesson18.client;
import dev.langchain4j.agentic.AgenticServices;
public class A2AClientTest {
public static void main(String[] args) throws InterruptedException {
CreativeWriter creativeWriter = AgenticServices
.a2aBuilder("http://localhost:8080", CreativeWriter.class)
.inputKeys("topic")
.outputKey("story")
.build();
String result = creativeWriter.generateStory("神话故事");
System.out.println(result);
}
}
3.3 演示结果
1)使用浏览器访问:http://localhost:8080/.well-known/agent-card.json ,会得到关于我们发布agent的信息

2)启动A2AClientTest ,可以得到结果如下:

3)回去看A2AServerApplication的控制台输出,可以看到其访问的过程输出

结语:本章给大家演示了Langchain4j中如何对接A2A协议,虽然A2A协议目前处于草稿阶段,可能后续变动会比较大,但是做给更为开放式和复杂任务场景,这个也是为了Agent的发展方向,因此先了解这些基础是非常有必要的。 到这里,关于Langchain4j 的内容也就告一段落,至于与Spring AI的区别,相信学习到这里已经有一个比较明显的结论。在细节实现上面,两者各有优缺点,在Spring框架上结合,Spring AI更优优势。但是在生态上,Langchain4j 更优优势。但是无论是哪一种,都可以实现大部分场景应用场景。
Langchain4j 系列上一章:《Langchain4j 系列之四十 – Agent之HumanIn》

