Quarkus 和 A2A Java SDK 入门

几周前,我们宣布我们的 A2A Java SDK 已贡献给官方 A2A 项目! 这是 Red Hat 和 Google 的 WildFly 和 Quarkus 团队之间的合作。 今天,我们发布了 A2A Java SDK 0.2.3.Beta1,它与 A2A 规范的 v0.2.3 版本一致。 在这篇博文中,我们将介绍如何使用 A2A Java SDK 轻松开始使用 Quarkus 和 A2A。

您还可以查看我们的简短视频,其中介绍了 A2A Java SDK。

什么是 A2A?

在深入了解细节之前,让我们先了解一下 A2A 是什么。 _Agent2Agent_ (A2A) 协议是由 Google 开发的开放标准。 它使 AI 代理能够相互通信和协作,而无需考虑每个代理的底层框架、语言或供应商。 这很重要,因为它为多语言多代理系统铺平了道路。

主要概念

A2A 协议涉及几个重要的概念

  • 用户 - 这是最终用户,他们的请求需要一个或多个代理的帮助。

  • A2A 客户端 - 这是将代表用户向 A2A 服务器代理发送请求的客户端。

  • A2A 服务器 - 这是将接收和响应来自 A2A 客户端代理的请求的服务器代理。 A2A 服务器代理公开一个 HTTP 端点,该端点实现 A2A 协议。

可以使用不同的语言和框架来实现 A2A 客户端和 A2A 服务器代理。 他们只需要能够使用 A2A 协议相互通信。 通信使用基于 HTTP(S) 的 JSON-RPC 2.0 作为传输方式。 为各种编程语言编写的 A2A SDK 实现了这种互操作性。

A2A 项目旨在为各种语言提供 SDK。 例如,使用 A2A Python SDK 和我们的 A2A Java SDK,Python 中编写的 A2A 客户端代理可以与 Java 中编写的 A2A 服务器代理通信,反之亦然。

从 Quarkus LangChain4j AI 服务到 A2A 服务器代理

假设我们有一个 Quarkus LangChain4j AI 服务,可以通过使用天气 MCP 服务器来响应用户关于天气的查询

@RegisterAiService
@ApplicationScoped
public interface WeatherAgent {

    @SystemMessage("""
            You are a specialized weather forecast assistant. Your primary function is to
            utilize the provided tools to retrieve and relay weather information in response
            to user queries. You must rely exclusively on these tools for data and refrain
            from inventing information. Ensure that all responses include the detailed output
            from the tools used and are formatted in Markdown.
            """
    )
    @McpToolBox("weather") // <-- The weather MCP server that will be used
    String chat(@UserMessage String question);
}

要将这个天气代理变成 A2A 服务器代理,我们需要遵循几个简单的步骤

添加 A2A Java SDK 服务器依赖项

io.github.a2asdk groupId 是临时的,可能会在未来的版本中更改。 请留意 a2a-java README 以获取最新的文档。

<dependency>
    <groupId>io.github.a2asdk</groupId>
    <artifactId>a2a-java-sdk-server-quarkus</artifactId> (1)
</dependency>
1 a2a-java-sdk-server-quarkus 提供了访问构成 A2A 规范的核心类的权限,并提供了实现 A2A 协议的 HTTP 端点。 此依赖项使用 Quarkus Reactive Routes。 如果不使用 Quarkus,则可以使用 a2a-java-sdk-server-jakarta 依赖项在支持 CDI 和 Jakarta RESTful Web Services 的 Jakarta 服务器中公开 A2A 服务器代理。

添加一个创建 A2A AgentCard 的类

AgentCard 是一个类,它描述了 A2A 服务器代理的功能。 其他代理或客户端将使用它来了解我们的天气代理可以做什么。 A2A Java SDK 将在服务器代理的 .well-known/agent.json URI 自动公开此代理卡。 例如,如果我们的 A2A 服务器代理在 https://:10001 上运行,则代理卡将在 https://:10001/.well-known/agent.json 上可用。

import io.a2a.server.PublicAgentCard;
import io.a2a.spec.AgentCapabilities;
import io.a2a.spec.AgentCard;
import io.a2a.spec.AgentSkill;
...

@ApplicationScoped
public class WeatherAgentCardProducer {

    @Produces
    @PublicAgentCard
    public AgentCard agentCard() {
        return new AgentCard.Builder()
                .name("Weather Agent")
                .description("Helps with weather")
                .url("https://:10001") (1)
                .version("1.0.0")
                .capabilities(new AgentCapabilities.Builder() (2)
                        .streaming(true)
                        .pushNotifications(false)
                        .stateTransitionHistory(false)
                        .build())
                .defaultInputModes(Collections.singletonList("text"))
                .defaultOutputModes(Collections.singletonList("text"))
                .skills(Collections.singletonList(new AgentSkill.Builder()
                        .id("weather_search")
                        .name("Search weather")
                        .description("Helps with weather in city, or states") (3)
                        .tags(Collections.singletonList("weather"))
                        .examples(List.of("weather in LA, CA")) (4)
                        .build()))
                .build();
    }
}
1 我们的 A2A 服务器代理的 URL。 我们在 application.properties 文件中将 quarkus.http.port 设置为 10001,因此我们的 A2A 服务器代理将在 https://:10001 上可用。
2 指示我们的 A2A 服务器代理的功能,例如它是否支持流式传输、推送通知和状态转换历史记录。
3 描述我们的代理可以做什么。
4 我们的代理可以处理的示例查询。

添加一个创建 A2A AgentExecutor 的类

AgentExecutor 是一个类,它将用于处理发送到我们的 A2A 服务器代理的请求。 它会将从 A2A 客户端收到的请求传递给我们的 Quarkus LangChain4j AI 服务,并负责将响应返回给 A2A 客户端。 当请求发送到我们的 A2A 服务器代理时,A2A Java SDK 将调用此执行程序。

请注意,AgentExecutor 接口指定了两个我们需要实现的方法 executecancel

import io.a2a.server.agentexecution.AgentExecutor;
import io.a2a.server.agentexecution.RequestContext;
import io.a2a.server.events.EventQueue;
import io.a2a.server.tasks.TaskUpdater;
import io.a2a.spec.JSONRPCError;
import io.a2a.spec.Message;
import io.a2a.spec.Part;
import io.a2a.spec.Task;
import io.a2a.spec.TaskNotCancelableError;
import io.a2a.spec.TaskState;
import io.a2a.spec.TextPart;
...

@ApplicationScoped
public class WeatherAgentExecutorProducer {

    @Inject
    WeatherAgent weatherAgent; (1)

    @Produces
    public AgentExecutor agentExecutor() {
        return new WeatherAgentExecutor(weatherAgent);
    }

    private static class WeatherAgentExecutor implements AgentExecutor {

        private final WeatherAgent weatherAgent;

        public WeatherAgentExecutor(WeatherAgent weatherAgent) {
            this.weatherAgent = weatherAgent;
        }

        @Override
        public void execute(RequestContext context, EventQueue eventQueue) throws JSONRPCError { (2)
            TaskUpdater updater = new TaskUpdater(context, eventQueue);

            // mark the task as submitted and start working on it
            if (context.getTask() == null) {
                updater.submit();
            }
            updater.startWork();

            // extract the text from the message
            String userMessage = extractTextFromMessage(context.getMessage());

            // call the weather agent with the user's message
            String response = weatherAgent.chat(userMessage); (3)

            // create the response part
            TextPart responsePart = new TextPart(response, null);
            List<Part<?>> parts = List.of(responsePart);

            // add the response as an artifact and complete the task
            updater.addArtifact(parts, null, null, null);
            updater.complete();
        }

        @Override
        public void cancel(RequestContext context, EventQueue eventQueue) throws JSONRPCError { (4)
            Task task = context.getTask();

            if (task.getStatus().state() == TaskState.CANCELED) {
                // task already cancelled
                throw new TaskNotCancelableError();
            }

            if (task.getStatus().state() == TaskState.COMPLETED) {
                // task already completed
                throw new TaskNotCancelableError();
            }

            // cancel the task
            TaskUpdater updater = new TaskUpdater(context, eventQueue);
            updater.cancel();
        }

        private String extractTextFromMessage(Message message) {
            StringBuilder textBuilder = new StringBuilder();
            if (message.getParts() != null) {
                for (Part part : message.getParts()) {
                    if (part instanceof TextPart textPart) {
                        textBuilder.append(textPart.getText());
                    }
                }
            }
            return textBuilder.toString();
        }
    }
}
1 这是我们的 Quarkus LangChain4j AI 服务。
2 execute 方法将用于处理来自 A2A 客户端的请求。
3 在这里,我们正在调用我们的 Quarkus LangChain4j AI 服务。
4 cancel 方法将用于取消正在进行的请求。

就是这样,我们现在可以如下所示启动我们的 Quarkus 应用程序,我们的 A2A 服务器代理将在 https://:10001 上可用。 A2A 客户端代理现在可以将与天气相关的查询发送到我们的 A2A 服务器代理,我们的代理将回复天气信息。

$ mvn quarkus:dev

我们只需几个步骤就从 Quarkus LangChain4j AI 服务变成了 A2A 服务器代理!

此示例的源代码可在此处获得。

使用 A2A Inspector 验证我们的 A2A 服务器代理

A2A Inspector 是一个非常易于运行的 Web 应用程序,可用于检查任何 A2A 服务器代理。

我们可以通过在 Connect 文本框中指定我们的服务器代理的 URL 来使用 A2A Inspector 验证我们的 A2A 服务器代理。

A2A Inspector 将获取并显示我们的服务器代理的代理卡

a2a inspector agent card

请注意,这与我们在 WeatherAgentCardProducer 类中提供的信息相匹配。

您还可以使用检查器将请求发送到 A2A 服务器代理并查看原始 HTTP 请求和响应。

使用 Python 和 Java 服务器代理进行多代理协调

让我们看看一个更复杂的例子,它使用了我们的天气 A2A 服务器代理。

multiagent java python

这是一个多代理示例,其中主机代理根据用户的问题将请求委托给两个不同的 A2A 服务器代理,一个 Airbnb 代理和我们的天气代理。 在后台,主机代理使用每个代理的代理卡来确定每个代理的功能,并使用 LLM 来确定基于它们的功能将请求委托给哪个代理。

Airbnb 代理是一个使用 LangGraph 实现并使用 A2A Python SDK 的 Python 代理。

天气代理是我们的 Java 代理,它使用 Quarkus LangChain4j 实现并使用 A2A Java SDK。

请注意,主机代理使用用 Python 编写的 A2A 客户端与服务器代理通信。 也可以使用我们的 A2A Java SDK 中用 Java 编写的 A2A 客户端

此示例的完整源代码可在此处获得。 要尝试此多代理示例,请尝试向主机代理发送不同类型的问题,例如

  • 纽约州纽约的天气如何?

  • 在加利福尼亚州洛杉矶为我找一个房间,7 月 7 日至 9 日,2 位成人

请注意,主机代理会将第一个问题委托给天气代理,并将第二个问题委托给 Airbnb 代理。

new york weather

第二个问题将被委托给 Airbnb 代理

la airbnb

结论

我们已经看到了使用 A2A Java SDK 开始使用 Quarkus 和 A2A 是多么容易。 只需几个步骤,我们就可以将 Quarkus LangChain4j AI 服务变成可以与其他 A2A 代理通信的 A2A 服务器代理,而无需考虑它们是用哪种语言或框架实现的。 LangChain4j 和 Quarkus 团队也在努力删除大多数样板代码,以公开 A2A 服务器并与 A2A 客户端交互。 因此,请继续关注!