diff --git a/_data/authors.yaml b/_data/authors.yaml index 5660156d08..03a9fbd299 100644 --- a/_data/authors.yaml +++ b/_data/authors.yaml @@ -619,3 +619,10 @@ maeste: job_title: "Senior Engineering Manager" twitter: "maeste" bio: "Senior Engineering Manager at Red Hat / IBM with an old passion for Open Source and more recent one for AI Engineering." +fjuma: + name: "Farah Juma" + email: "fjuma@redhat.com" + emailhash: "e07a676199f6ad0af96a61a691b114b3" + job_title: "Principal Software Engineer" + twitter: "farahjuma" + bio: "Principal Software Engineer at Red Hat / IBM working on AI projects" diff --git a/_posts/2025-07-01-quarkus-and-a2a-java-sdk.adoc b/_posts/2025-07-01-quarkus-and-a2a-java-sdk.adoc new file mode 100644 index 0000000000..8bf62589f6 --- /dev/null +++ b/_posts/2025-07-01-quarkus-and-a2a-java-sdk.adoc @@ -0,0 +1,281 @@ +--- +layout: post +title: 'Getting Started with Quarkus and the A2A Java SDK' +date: 2025-07-01 +tags: release +synopsis: 'Today, we released A2A Java SDK 0.2.3. This makes it possible to quickly get started with Quarkus and A2A.' +author: fjuma +--- +:imagesdir: /assets/images/posts/quarkus-and-a2a-java-sdk + +Last week, we https://quarkus.io/blog/a2a-project-launches-java-sdk/[announced] that our https://github.com/a2aproject/a2a-java[A2A Java SDK] has been contributed to the official A2A project! This was a collaboration between our WildFly and Quarkus teams at Red Hat and Google. Today, we have released A2A Java SDK 0.2.3, which aligns with the v0.2.3 version of the https://github.com/a2aproject/A2A/tree/v0.2.3[A2A specification]. In this blog post, we'll cover how to easily get started with Quarkus and A2A using the A2A Java SDK. + +== What's A2A? + +Before jumping into the details, let's go through what https://a2aproject.github.io/A2A/specification/[A2A] is. The Agent2Agent or A2A protocol for short, is an open standard that was created by Google. It allows AI agents to communicate and collaborate with each other, regardless of each agent's underlying framework, language, or vendor. This is really important because it's paving the way for polyglot multi-agent systems. + +=== Important Concepts + +The A2A protocol involves a few important concepts: + +* *User* - This is the end user who has a request that will require the help of one or more agents. +* *A2A Client* - This is the client that will send requests on the user's behalf to an A2A server agent. +* *A2A Server* - This is the server agent that will receive and respond to requests from an A2A client agent. An A2A server agent exposes an HTTP endpoint that implements the A2A protocol. + +A2A client agents and A2A server agents can be implemented using different languages and frameworks. They just need to be able to speak with each other using the A2A protocol. Communication happens using JSON-RPC 2.0 over HTTP(S). A2A SDKs written for different programming languages make this interoperability possible. + +The https://github.com/orgs/a2aproject/repositories[A2A project] aims to provide SDKs for various languages. Using the https://github.com/a2aproject/a2a-python[A2A Python SDK] and our https://github.com/a2aproject/a2a-java[A2A Java SDK], for example, it's possible for an A2A client agent written in Python to communicate with an A2A server agent written in Java and vice versa. + +== From a Quarkus LangChain4j AI Service to an A2A Server Agent + +Let's say we have a Quarkus LangChain4j AI service that can respond to user queries about the weather by making use of a weather MCP server: + +[source,java] +---- +@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); +} +---- + +To turn this weather agent into an A2A server agent, there are a few simple steps we need to follow: + +=== Add A2A Java SDK Core and Server Dependencies + +[source,xml] +---- + + io.a2a.sdk + a2a-java-sdk-core <1> + + + io.a2a.sdk + a2a-java-sdk-server-quarkus <2> + +---- +<1> `a2a-java-sdk-core` provides the core classes that make up the A2A specification, it's needed to create `AgentCards` and `AgentExecutors`. It can also be used to create A2A client agents. +<2> `a2a-java-sdk-server-quarkus` provides the HTTP endpoint that implements the A2A protocol. This dependency makes use of Quarkus Reactive Routes. To make use of Jakarta REST, the `a2a-java-sdk-server-jakarta` dependency can be used instead. + +=== Add a Class that Creates an A2A `AgentCard` + +The `AgentCard` is a class that describes an A2A server agent's capabilities. This will be used by other agents or clients to understand what our weather agent can do. The A2A Java SDK will automatically expose this agent card at the server agent's `.well-known/agent.json` URI. For example, if our A2A server agent is running on http://localhost:10001, the agent card will be available at http://localhost:10001/.well-known/agent.json. + +[source,java] +---- +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("http://localhost: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> The URL of our A2A server agent. We set `quarkus.http.port` to `10001` in our `application.properties` file so our A2A server agent will be available at http://localhost:10001. +<2> Indicates the capabilities of our A2A server agent like whether it supports streaming, push notifications, and state transition history. +<3> Describes what our agent can do. +<4> An example query that our agent can handle. + +=== Add a class that creates an A2A `AgentExecutor` + +The `AgentExecutor` is a class that will be used to process requests sent to our A2A server agent. It will pass the requests received from the A2A client to our Quarkus LangChain4j AI service and is responsible for returning the responses back to the A2A client. The A2A Java SDK will call this executor when a request is sent to our A2A server agent. + +Notice that the `AgentExecutor` interface specifies two methods, `execute` and `cancel`, that we need to implement. + +[source,java] +---- +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> 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> This is our Quarkus LangChain4j AI service. +<2> The `execute` method will be used to process requests from an A2A client. +<3> Here we are invoking our Quarkus LangChain4j AI service. +<4> The `cancel` method be used to cancel an ongoing request. + + +That's it, we can now start our Quarkus application as shown below and our A2A server agent will be available at http://localhost:10001. A2A client agents can now send weather-related queries to our A2A server agent and our agent will respond with the weather information. + +[source,bash] +---- +$ mvn quarkus:dev +---- + +We've gone from a Quarkus LangChain4j AI service to an A2A server agent in just a few steps! + +The source code for this example is available https://github.com/a2aproject/a2a-samples/tree/main/samples/multi_language/python_and_java_multiagent/weather_agent[here]. + +== Validating our A2A Server Agent Using the A2A Inspector + +The https://github.com/a2aproject/a2a-inspector[A2A Inspector] is a web application that's very easy to run and can be used to inspect any A2A server agent. + +We can use the A2A Inspector to validate our A2A server agent by specifying our server agent's URL in the `Connect` text box. + +The A2A Inspector will obtain and show our server agent's agent card: + +image::a2a-inspector-agent-card.png[scaledwidth=100%] + +Notice that this matches the information we provided in our `WeatherAgentCardProducer` class. + +You can also use the inspector to send requests to the A2A server agent and to view the raw HTTP requests and responses. + +== Multi-Agent Orchestration with Python and Java Server Agents + +Let's take a look at a more complex example that makes use of our weather A2A server agent. + +image::multiagent-java-python.png[scaledwidth=100%] + +This is a multi-agent example where a host agent delegates requests to two different A2A server agents, an Airbnb agent and our Weather agent, based on the user's question. Under the hood, the host agent makes use of each agent's agent card to determine the capabilities of each agent and uses an LLM to determine which agent to delegate the request to based on their capabilities. + +The https://github.com/a2aproject/a2a-samples/tree/main/samples/multi_language/python_and_java_multiagent/airbnb_agent[Airbnb agent] is a Python agent that's implemented using LangGraph and makes use of the A2A Python SDK. + +The https://github.com/a2aproject/a2a-samples/tree/main/samples/multi_language/python_and_java_multiagent/weather_agent[Weather agent] is our Java agent that's implemented using Quarkus LangChain4j and makes use of the A2A Java SDK. + +Notice that the host agent uses A2A clients written in Python to communicate with the server agents. It's also possible to use an https://github.com/a2aproject/a2a-java?tab=readme-ov-file#a2a-client[A2A client] written in Java using our A2A Java SDK. + +The complete source code for this example is available https://github.com/a2aproject/a2a-samples/tree/main/samples/multi_language/python_and_java_multiagent[here]. To experiment with this multi-agent example, try sending different types of questions to the host agent, for example: + +* What's the weather in New York, NY? +* Find me a room in LA, CA, July 7-9, 2 adults + +Notice that the host agent will delegate the first question to the Weather agent and the second question to the Airbnb agent. + +image::new_york_weather.png[scaledwidth=100%] + +And the second question will be delegated to the Airbnb agent: + +image::la_airbnb.png[scaledwidth=100%] + +== Conclusion + +We've seen how easy it is to get started with Quarkus and A2A using the A2A Java SDK. With just a few steps, we can turn a Quarkus LangChain4j AI service into an A2A server agent that can communicate with other A2A agents, regardless of the language or framework they are implemented in. + +=== Further Reading + +* https://a2aproject.github.io/A2A/specification/[A2A Specification] +* https://github.com/a2aproject/a2a-java/blob/main/README.md[A2A Java SDK Documentation] + + diff --git a/assets/images/posts/quarkus-and-a2a-java-sdk/a2a-inspector-agent-card.png b/assets/images/posts/quarkus-and-a2a-java-sdk/a2a-inspector-agent-card.png new file mode 100644 index 0000000000..e8837b503c Binary files /dev/null and b/assets/images/posts/quarkus-and-a2a-java-sdk/a2a-inspector-agent-card.png differ diff --git a/assets/images/posts/quarkus-and-a2a-java-sdk/la_airbnb.png b/assets/images/posts/quarkus-and-a2a-java-sdk/la_airbnb.png new file mode 100644 index 0000000000..645d72635a Binary files /dev/null and b/assets/images/posts/quarkus-and-a2a-java-sdk/la_airbnb.png differ diff --git a/assets/images/posts/quarkus-and-a2a-java-sdk/multiagent-java-python.png b/assets/images/posts/quarkus-and-a2a-java-sdk/multiagent-java-python.png new file mode 100644 index 0000000000..406e9a84f5 Binary files /dev/null and b/assets/images/posts/quarkus-and-a2a-java-sdk/multiagent-java-python.png differ diff --git a/assets/images/posts/quarkus-and-a2a-java-sdk/new_york_weather.png b/assets/images/posts/quarkus-and-a2a-java-sdk/new_york_weather.png new file mode 100644 index 0000000000..747247531e Binary files /dev/null and b/assets/images/posts/quarkus-and-a2a-java-sdk/new_york_weather.png differ