Skip to content

Conversation

@geoand
Copy link
Collaborator

@geoand geoand commented Dec 4, 2025

For now, the extension does two things:

  • Simplifies the AgentCard creation
  • Creates an implementation of AgentExecutor

Compared to how this improves the developer experience for writing an A2A server, consider the Weather Agent from the A2A samples.
Using this PR, the following diff shows how much easier the task now becomes (as writing the AgentCard and the AgentExecutor` manually, is no longer necessary):

Index: samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgentCardProducer.java
===================================================================
diff --git a/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgentCardProducer.java b/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgentCardProducer.java
deleted file mode 100644
--- a/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgentCardProducer.java	(revision d4fa006438e521b63a8c8145676f6df0c6b0aafa)
+++ /dev/null	(revision d4fa006438e521b63a8c8145676f6df0c6b0aafa)
@@ -1,68 +0,0 @@
-package com.samples.a2a;
-
-import io.a2a.server.PublicAgentCard;
-import io.a2a.spec.AgentCapabilities;
-import io.a2a.spec.AgentCard;
-import io.a2a.spec.AgentSkill;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.enterprise.inject.Produces;
-import jakarta.inject.Inject;
-import java.util.Collections;
-import java.util.List;
-import org.eclipse.microprofile.config.inject.ConfigProperty;
-
-/**
- * Producer for weather agent card configuration.
- * This class is final and not designed for extension.
- */
-@ApplicationScoped
-public final class WeatherAgentCardProducer {
-
-  /** The HTTP port for the agent service. */
-  @Inject
-  @ConfigProperty(name = "quarkus.http.port")
-  private int httpPort;
-
-  /**
-   * Gets the HTTP port.
-   *
-   * @return the HTTP port
-   */
-  public int getHttpPort() {
-    return httpPort;
-  }
-
-  /**
-   * Produces the agent card for the weather agent.
-   *
-   * @return the configured agent card
-   */
-  @Produces
-  @PublicAgentCard
-  public AgentCard agentCard() {
-    return new AgentCard.Builder()
-        .name("Weather Agent")
-        .description("Helps with weather")
-        .url("http://localhost:" + getHttpPort())
-        .version("1.0.0")
-        .capabilities(
-            new AgentCapabilities.Builder()
-                .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")
-                    .tags(Collections.singletonList("weather"))
-                    .examples(List.of("weather in LA, CA"))
-                    .build()))
-        .protocolVersion("0.3.0")
-        .build();
-  }
-}
Index: samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgentExecutorProducer.java
===================================================================
diff --git a/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgentExecutorProducer.java b/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgentExecutorProducer.java
deleted file mode 100644
--- a/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgentExecutorProducer.java	(revision d4fa006438e521b63a8c8145676f6df0c6b0aafa)
+++ /dev/null	(revision d4fa006438e521b63a8c8145676f6df0c6b0aafa)
@@ -1,130 +0,0 @@
-package com.samples.a2a;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.enterprise.inject.Produces;
-import jakarta.inject.Inject;
-import java.util.List;
-
-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;
-
-/**
- * Producer for weather agent executor.
- * This class is final and not designed for extension.
- */
-@ApplicationScoped
-public final class WeatherAgentExecutorProducer {
-
-    /**
-     * The weather agent instance.
-     */
-    @Inject
-    private WeatherAgent weatherAgent;
-
-    /**
-     * Gets the weather agent.
-     *
-     * @return the weather agent
-     */
-    public WeatherAgent getWeatherAgent() {
-        return weatherAgent;
-    }
-
-    /**
-     * Produces the agent executor for the weather agent.
-     *
-     * @return the configured agent executor
-     */
-    @Produces
-    public AgentExecutor agentExecutor() {
-        return new WeatherAgentExecutor(getWeatherAgent());
-    }
-
-    /**
-     * Weather agent executor implementation.
-     */
-    private static class WeatherAgentExecutor implements AgentExecutor {
-
-        /**
-         * The weather agent instance.
-         */
-        private final WeatherAgent agent;
-
-        /**
-         * Constructor for WeatherAgentExecutor.
-         *
-         * @param weatherAgentInstance the weather agent instance
-         */
-        WeatherAgentExecutor(final WeatherAgent weatherAgentInstance) {
-            this.agent = weatherAgentInstance;
-        }
-
-        @Override
-        public void execute(final RequestContext context,
-                            final EventQueue eventQueue) throws JSONRPCError {
-            final 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
-            final String userMessage = extractTextFromMessage(
-                    context.getMessage());
-
-            // call the weather agent with the user's message
-            final String response = agent.chat(userMessage);
-
-            // create the response part
-            final TextPart responsePart = new TextPart(response, null);
-            final List<Part<?>> parts = List.of(responsePart);
-
-            // add the response as an artifact and complete the task
-            updater.addArtifact(parts, null, null, null);
-            updater.complete();
-        }
-
-        private String extractTextFromMessage(final Message message) {
-            final StringBuilder textBuilder = new StringBuilder();
-            if (message.getParts() != null) {
-                for (final Part part : message.getParts()) {
-                    if (part instanceof TextPart textPart) {
-                        textBuilder.append(textPart.getText());
-                    }
-                }
-            }
-            return textBuilder.toString();
-        }
-
-        @Override
-        public void cancel(final RequestContext context,
-                           final EventQueue eventQueue) throws JSONRPCError {
-            final 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
-            final TaskUpdater updater = new TaskUpdater(context, eventQueue);
-            updater.cancel();
-        }
-    }
-}
Index: samples/java/agents/weather_mcp/pom.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/samples/java/agents/weather_mcp/pom.xml b/samples/java/agents/weather_mcp/pom.xml
--- a/samples/java/agents/weather_mcp/pom.xml	(revision d4fa006438e521b63a8c8145676f6df0c6b0aafa)
+++ b/samples/java/agents/weather_mcp/pom.xml	(date 1764852558587)
@@ -14,9 +14,9 @@
 
     <dependencies>
         <dependency>
-            <groupId>io.github.a2asdk</groupId>
-            <artifactId>a2a-java-sdk-reference-jsonrpc</artifactId>
-            <version>${io.a2a.sdk.version}</version>
+            <groupId>io.quarkiverse.langchain4j</groupId>
+            <artifactId>quarkus-langchain4j-a2a-server</artifactId>
+            <version>${quarkus.langchain4j.version}</version>
         </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
Index: samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgent.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgent.java b/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgent.java
--- a/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgent.java	(revision d4fa006438e521b63a8c8145676f6df0c6b0aafa)
+++ b/samples/java/agents/weather_mcp/src/main/java/com/samples/a2a/WeatherAgent.java	(date 1764852741628)
@@ -1,5 +1,6 @@
 package com.samples.a2a;
 
+import io.quarkiverse.langchain4j.a2a.server.ExposeA2AAgent;
 import jakarta.enterprise.context.ApplicationScoped;
 
 import dev.langchain4j.service.SystemMessage;
@@ -11,6 +12,7 @@
  * Weather agent interface that provides weather forecast assistance.
  */
 @RegisterAiService
+@ExposeA2AAgent(name = "Weather Agent", description = "Helps with weather", skills = @ExposeA2AAgent.Skill(id = "weather_search", name = "Search weather", description = "Helps with weather in city, or states", tags = "weather", examples = "weather in LA, CA"))
 @ApplicationScoped
 public interface WeatherAgent {
 
Index: samples/java/agents/pom.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/samples/java/agents/pom.xml b/samples/java/agents/pom.xml
--- a/samples/java/agents/pom.xml	(revision d4fa006438e521b63a8c8145676f6df0c6b0aafa)
+++ b/samples/java/agents/pom.xml	(date 1764852558592)
@@ -28,7 +28,7 @@
         <!-- Common dependency versions -->
         <jakarta.enterprise.cdi-api.version>4.1.0</jakarta.enterprise.cdi-api.version>
         <quarkus.platform.version>3.26.1</quarkus.platform.version>
-        <quarkus.langchain4j.version>1.3.1</quarkus.langchain4j.version>
+        <quarkus.langchain4j.version>999-SNAPSHOT</quarkus.langchain4j.version>
         <protobuf.version>4.31.1</protobuf.version>
     </properties>
 

The implementation still has significant omissions, as can be seen in the various TODO comments, but it does show that the basic approach works.

Relates to: #1895

For now, the extension does two things:

- Simplifies the AgentCard creation
- Creates an implementation of AgentExecutor

The implementation still has significant omissions,
as can be seen in the various `TODO` comments, but it
does show that the basic approach works

Relates to: #1895
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants