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 文件设置如下:
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 结构来处理请求和响应。
所有方法调用都封装在一个请求对象中,其结构如下:
{ "jsonrpc": "2.0", "method": "String", "id": "String | Integer", "params": "Message" }
响应对象有些类似:
{ "jsonrpc": "2.0", "id": "String | Integer | null", "result?": "Task | Message | null", "error?": "JSONRPCError" }
响应中的 ID 必须与请求中的 ID 相同。
有关 A2A 协议的更多信息,请查阅 A2A 协议文档。
以上就是请求和响应的通用结构。我开发这个智能体是为了在 Telex 平台上使用,因此我的部分实现可能特定于 Telex。
现在进入实现部分。我创建了一个名为 model 的文件夹,用于存储我的模型。请求模型类 A2ARequest 如下所示:
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 中包含信息的结构。它如下所示:
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 类如下所示:
@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 类如下所示:
@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 类:
@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 类:
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 类:
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 类:
@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 类:
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 协议还包含一个称为"智能体卡片"的东西。我也为它创建了一个模型。
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 的文件夹,我的服务类就放在这里。我是这样编写我的服务类的:
@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 的文件夹中创建了我的控制器。实现如下:
@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