使用 Java、Spring Boot 和 Spring AI 开发符合 A2A 标准的 AI 智能体

AI 智能体指的是一种软件实体,它能够利用自然语言处理、机器学习或推理系统等人工智能技术,自主感知、推理和行动,以实现特定目标。

我为 Telex 开发了一个 AI 智能体,该智能体接收一个正则表达式模式,并就该模式所匹配的字符串类型提供易于理解的解释。开发此智能体的灵感源于我在此之前开发的一个 API(您可以在此处查看该项目),在该 API 中我必须使用正则表达式进行一些自然语言处理。尽管我之前学习过正则表达式,但感觉像是第一次见到它。正则表达式就是这样。因此,当 Telex 为其平台寻求更多 AI 智能体时,我决定开发这个智能体。

以下是我使用 Java、Spring AI 和 Spring Boot 实现它的过程。

初始设置

1. Spring Boot 项目初始化

我使用 Spring 提供的初始化工具来初始化项目。请注意,我在依赖项中包含了 Spring Web 和 Open AI。

2. 设置 API 凭证

在我的 application.properties 文件中,我设置了 Spring AI 以使用我的 API 凭证(我的 API 密钥)。我通过 Google AI Studio 获得了一个免费的 Google Gemini API 密钥。我的 application.properties 文件设置如下:

properties 复制代码
spring.config.import=classpath:AI.properties

  


spring.application.name=regexplain

spring.ai.openai.api-key = ${GEMINI_API_KEY}

spring.ai.openai.base-url https://generativelanguage.googleapis.com/v1beta/openai

spring.ai.openai.chat.completions-path = /chat/completions

spring.ai.openai.chat.options.model = gemini-2.5-pro

第一行导入了包含我 API 密钥的文件。重要的是不要将您的 API 密钥暴露给公众。该文件与 application.properties 位于同一文件夹中。

3. 首次项目运行

使用我的包管理器(Maven),我安装了所需的依赖项。然后我运行了我的主类,以确保一切正常。如果您到目前为止一切都做对了,您的项目应该可以无错误运行。如果遇到任何错误,请在 Google 上查找解决方法。

A2A 请求和响应模型

在深入实现之前,让我们先谈谈符合 A2A 标准的请求和响应的结构。A2A 协议遵循标准的 JSON-RPC 2.0 结构来处理请求和响应。

所有方法调用都封装在一个请求对象中,其结构如下:

json 复制代码
{

"jsonrpc": "2.0",

"method": "String",

"id": "String | Integer",

"params": "Message"

}

响应对象有些类似:

json 复制代码
{

"jsonrpc": "2.0",

"id": "String | Integer | null",

"result?": "Task | Message | null",

"error?": "JSONRPCError"

}

响应中的 ID 必须与请求中的 ID 相同。

有关 A2A 协议的更多信息,请查阅 A2A 协议文档

以上就是请求和响应的通用结构。我开发这个智能体是为了在 Telex 平台上使用,因此我的部分实现可能特定于 Telex。

现在进入实现部分。我创建了一个名为 model 的文件夹,用于存储我的模型。请求模型类 A2ARequest 如下所示:

java 复制代码
public class A2ARequest {

private String id;

private RequestParamsProperty params;

  


public A2ARequest(String id, RequestParamsProperty params) {

this.id = id;

this.params = params;

}

  


// getters and setters

}

RequestParamsProperty 类代表了 params 中包含信息的结构。它如下所示:

java 复制代码
public class RequestParamsProperty {

private HistoryMessage message;

private String messageId;

  


public RequestParamsProperty(HistoryMessage message, String messageId) {

this.message = message;

this.messageId = messageId;

}

  


// getters and setter

}

HistoryMessage 类如下所示:

java 复制代码
@JsonIgnoreProperties(ignoreUnknown = true)

@JsonInclude(JsonInclude.Include.NON_NULL)

public class HistoryMessage {

private String kind;

private String role;

private List<MessagePart> parts;

private String messageId;

private String taskId;

  


public HistoryMessage() {}

  


public HistoryMessage(String role, List<MessagePart> parts, String messageId, String taskId) {

this.kind = "message";

this.role = role;

this.parts = parts;

this.messageId = messageId;

this.taskId = taskId;

}

  


// getters and setters

}

注解的作用是让 Spring 知道在请求和响应的 JSON 表示中包含什么。如果请求中不存在某个属性,它应该忽略它并在类中将其设置为 null。如果某个属性设置为 null,则不应将其包含在响应中。

MessagePart 类如下所示:

java 复制代码
@JsonIgnoreProperties(ignoreUnknown = true)

@JsonInclude(JsonInclude.Include.NON_NULL)

public class MessagePart {

private String kind;

private String text;

private List<MessagePart> data;

  


public MessagePart(String kind, String text, List<MessagePart> data) {

this.kind = kind;

this.text = text;

this.data = data;

}

  


// getters and setters

}

以上就是表示从 Telex 接收的请求结构所需的所有类。现在需要为我的响应创建一个模型,以及表示响应所需的所有支持类。

A2AResponse 类:

java 复制代码
@JsonInclude(JsonInclude.Include.NON_NULL)

public class A2AResponse {

private final String jsonrpc;

@JsonInclude(JsonInclude.Include.ALWAYS)

private String id;

private Result result;

private CustomError error;

  


public A2AResponse() {

this.jsonrpc = "2.0";

}

  


public A2AResponse(String id, Result result, CustomError error) {

this.jsonrpc = "2.0";

this.id = id;

this.result = result;

this.error = error;

}

  


//getters and setters

}

Result 类:

java 复制代码
public class Result {

private String id;

private String contextId;

private TaskStatus status;

private List<Artifact> artifacts;

private List<HistoryMessage> history;

private String kind;

  


public Result() {}

  


public Result(String id, String contextId, TaskStatus status, List<Artifact> artifacts, List<HistoryMessage> history, String task) {

this.id = id;

this.contextId = contextId;

this.status = status;

this.artifacts = artifacts;

this.history = history;

this.kind = task;

}

  


// getters and setters

}

CustomError 类:

java 复制代码
public class CustomError {

private int code;

private String message;

private Map<String, String> data;

  


public CustomError(int code, String message, Map<String, String> data) {

this.code = code;

this.message = message;

this.data = data;

}

  


// getters and setters

}

TaskStatus 类:

java 复制代码
@JsonInclude(JsonInclude.Include.NON_NULL)

public class TaskStatus {

private String state;

private Instant timestamp;

private HistoryMessage message;

  


public TaskStatus() {}

  


public TaskStatus(String state, Instant timestamp, HistoryMessage message) {

this.state = state;

this.timestamp = timestamp;

this.message = message;

}

  


// getters and setters

}

Artifact 类:

java 复制代码
public class Artifact {

private String artifactId;

private String name;

private List<MessagePart> parts; // 稍后复查此类型

  


public Artifact() {}

  


public Artifact(String artifactId, String name, List<MessagePart> parts) {

this.artifactId = artifactId;

this.name = name;

this.parts = parts;

}

  


// getters and setters

}

A2A 协议还包含一个称为"智能体卡片"的东西。我也为它创建了一个模型。

java 复制代码
public class AgentCard {

private String name;

private String description;

private String url;

private Map<String, String> provider;

private String version;

private Map<String, Boolean> capabilities;

private List<String> defaultInputModes;

private List<String> defaultOutputModes;

private List<Map<String, Object>> skills;

  


public AgentCard() {

this.provider = new HashMap<>();

this.capabilities = new HashMap<>();

this.skills = new ArrayList<>();

}

  


// getters and setters

}

模型部分就这些了。继续...

服务类

我的智能体的作用是获取一个正则表达式字符串,然后使用预定义的提示词将其发送到 OpenAI 的 API。服务类负责与 OpenAI 通信,发送提示词并接收响应。我创建了另一个名为 service 的文件夹,我的服务类就放在这里。我是这样编写我的服务类的:

java 复制代码
@Service

public class RegExPlainService {

private ChatClient chatClient;

  


RegExPlainService(ChatClient.Builder chatClientBuilder) {

this.chatClient = chatClientBuilder.build();

}

  


@Tool(name = "regexplain", description = "An agent that explains what type of string a regex pattern matches")

public String generateResponse(String regex) {

return chatClient

.prompt("Give me a simple explanation of the type of string matched by this regex pattern: %s. No validating statements from you. Just straight to the point".formatted(regex))

.call()

.content();

}

}

@Service 注解允许 Spring Boot 将服务注入到您的控制器中。@Tool 注解将该方法标记为一个智能体工具,如果将来要扩展该智能体以包含该功能,它可以被自主调用。不过目前并不需要它。

控制器

控制器通过 REST API 暴露该智能体。在这个案例中,我有两个端点,一个 GET 端点和一个 POST 端点。我在一个名为 controller 的文件夹中创建了我的控制器。实现如下:

java 复制代码
@RestController

public class RegExPlainController {

private final RegExPlainService regexplainService;

  


@Autowired

RegExPlainController (RegExPlainService regexplainService) {

this.regexplainService = regexplainService;

}

  


@GetMapping("/a2a/agent/regexplain/.well-known/agent.json")

public ResponseEntity<AgentCard> getAgentCard () {

AgentCard agentCard = new AgentCard();

agentCard.setName("regexplain");

agentCard.setDescription("An agent that provides a simple explanation of the type of string a regex pattern matches");

agentCard.setUrl("regexplain-production.up.railway.app/api");

agentCard.setProvider("Bituan", null); // 假设 setProvider 处理 Map 的填充

agentCard.setVersion("1.0");

agentCard.setCapabilities(false, false, false); // 假设 setCapabilities 处理 Map 的填充

agentCard.setDefaultInputModes(List.of("text/plain"));

agentCard.setDefaultOutputModes(List.of("application/json", "text/plain"));

agentCard.setSkill("skill-001", "Explain Regex", "Provides a simple explanation of the type of string a regex pattern matches",

List.of("text/plain"), List.of("text/plain"), List.of());

  


return ResponseEntity.ok(agentCard);

}

  


@PostMapping("/a2a/agent/regexplain")

public ResponseEntity<A2AResponse> explainRegex (@RequestBody A2ARequest request) {

String regexRequest;

String responseText;

  


// 如果参数无效,返回 403

try {

regexRequest = request.getParams().getMessage().getParts().get(0).getText();

} catch (Exception e) {

CustomError error = new CustomError(-32603, "Invalid Parameter", Map.of("details", e.getMessage()));

A2AResponse errorResponse = new A2AResponse(null, null, error);

return ResponseEntity.status(HttpStatusCode.valueOf(403)).body(errorResponse);

}

  


// 如果调用服务失败,返回错误 500

try {

responseText = regexplainService.generateResponse(regexRequest);

} catch (Exception e) {

CustomError error = new CustomError(-32603, "Internal Error", Map.of("details", e.getMessage()));

A2AResponse errorResponse = new A2AResponse(null, null, error);

return ResponseEntity.internalServerError().body(errorResponse);

}

  


// 构建响应

A2AResponse response = new A2AResponse();

response.setId(request.getId());

  


// 构建响应 -> 构建结果

Result result = new Result();

result.setId(UUID.randomUUID().toString());

result.setContextId(UUID.randomUUID().toString());

result.setKind("task");

  


// 构建响应 -> 构建结果 -> 构建状态

TaskStatus status = new TaskStatus();

status.setState("completed");

status.setTimestamp(Instant.now());

  


// 构建响应 -> 构建结果 -> 构建状态 -> 构建消息

HistoryMessage message = new HistoryMessage();

message.setRole("agent");

message.setParts(List.of(new MessagePart("text", responseText, null)));

message.setKind("message");

message.setMessageId(UUID.randomUUID().toString());

  


// 构建响应 -> 构建结果 -> 构建状态 (续)

status.setMessage(message);

  


// 构建响应 -> 构建结果 -> 构建工件

List<Artifact> artifacts = new ArrayList<>();

Artifact artifact = new Artifact();

artifact.setArtifactId(UUID.randomUUID().toString());

artifact.setName("regexplainerResponse");

artifact.setParts(List.of(new MessagePart("text", responseText, null)));

artifacts.add(artifact);

  


// 构建响应 -> 构建结果 -> 构建历史记录

List<HistoryMessage> history = new ArrayList<>();

  


// 构建响应 -> 构建结果 (续)

result.setStatus(status);

result.setArtifacts(artifacts);

result.setHistory(history);

  


// 构建响应 (续)

response.setResult(result);

  


return ResponseEntity.ok(response);

}

}
  • GET 端点使用的路由路径是 A2A 协议标准中用于获取智能体卡片的部分。智能体卡片是对智能体及其功能的描述。

  • POST 端点接收一个符合 A2A 标准的请求,执行智能体,然后返回适当的响应。

结论

就是这样。这就是我编写 Regexplain 的过程。

通过这个示例,您可以从头开始构建您的 AI 智能体并使其符合 A2A 标准。或者,至少我希望这能让您对如何使用 Java 开发符合 A2A 标准的 AI 智能体有所了解。


【注】本文译自:Developing an A2A-compliant AI Agent with Java, Spring Boot and Spring AI - DEV Community

相关推荐
牛奶5 小时前
2026年大模型怎么选?前端人实用对比
前端·人工智能·ai编程
牛奶5 小时前
前端人为什么要学AI?
前端·人工智能·ai编程
KEEN的创享空间11 小时前
AI编程从0到1之10X提效(Vibe Coding 氛围式编码 )09篇
openai·ai编程
AlienZHOU12 小时前
为 AI Agent 编写高质量 Skill:Claude 官方指南
agent·ai编程·claude
恋猫de小郭13 小时前
移动端开发稳了?AI 目前还无法取代客户端开发,小红书的论文告诉你数据
前端·flutter·ai编程
KaneLogger14 小时前
【翻译】打造 Agent Skills 的最佳实践
agent·ai编程·claude
王小酱14 小时前
Everything Claude Code 文档
openai·ai编程·aiops
雮尘15 小时前
如何在非 Claude IDE (TARE、 Cursor、Antigravity 等)下使用 Agent Skills
前端·agent·ai编程
刘贺同学15 小时前
Day12-龙虾哥打工日记:OpenClaw 子 Agent 到底看到了什么?
aigc·ai编程
程序员鱼皮17 小时前
离大谱,我竟然在 VS Code 里做了个视频!
github·aigc·ai编程