Task Execution Agent
To use this agent, add the Maven dependency to your project, replacing ${agentic-patterns.version} with the latest version.
Latest version:
<dependency>
    <groupId>com.javaaidev.agenticpatterns</groupId>
    <artifactId>task-execution</artifactId>
    <version>${agentic-patterns.version}</version>
</dependency>
Usage
TaskExecutionAgent
To create a Task Execution agent, you can create a subclass of TaskExecutionAgent. When constructing a TaskExecutionAgent, parameters in the table below can be provided.
| Parameter | Required? | Type | Description | 
|---|---|---|---|
| chatClient | yes | ChatClient | Spring AI ChatClient | 
| promptTemplate | yes | String | Prompt template | 
| responseType | no | Type | Type of response, only required when auto-detection fails | 
| promptTemplateContextProvider | no | Function<Request, Map<String, Object>> | Function to provide context for the prompt template | 
| chatClientRequestSpecUpdater | no | Consumer<ChatClientRequestSpec> | Customize the request sent to an LLM | 
| name | no | String | Name of this agent | 
| observationRegistry | no | ObservationRegistry | Micrometer ObservationRegistryfor tracing | 
| mcpClientConfiguration | no | McpClientConfiguration | MCP servers to connect to use tools | 
| toolFilter | no | Predicate<String> | Filter of tool names retrieved from MCP servers | 
TaskExecutionAgent is a parameterized type with request type and response type.
If the prompt template contains variables, then the promptTemplateContextProvider function can be used to provide values for these variables. The request object is provided to build the Map<String, Object> context. The default implementation uses Jackson ObjectMapper to convert the input object to JSON string first, then convert the JSON string to a Map<String, Object>.
If you want to customize the request sent to an LLM, you can provide a  chatClientRequestSpecUpdater to modify the ChatClientRequestSpec object. For example, you can add a system text.
spec -> {
    spec.system(
        "You are a customer support agent for general questions, be polite and helper");
}
The call method is used to send a request to an LLM and receive the response.
MCP Configuration
An TaskExecutionAgent can connect to MCP servers to use tools. This is done by provided a McpClientConfiguration to specify connection details of MCP servers.
A McpClientConfiguration contains a StdioClientProperties and a SseClientProperties for stdio and HTTP SSE connections, respectively. Multiple MCP servers can be defined in the configuration.
Builder
TaskExecutionAgent provides a Builder to build TaskExecutionAgent. Use the TaskExecutionAgent.defaultBuilder method to create a builder.
The code below shows how to use a builder to create a TaskExecutionAgent.
var agent = TaskExecutionAgent.<JokeInput, JokeOutput>defaultBuilder()
    .name("test")
    .responseType(JokeOutput.class)
    .chatClient(chatClient)
    .promptTemplate("""
        tell me {count} jokes
        """)
    .build();
var output = agent.call(new JokeInput(2));
Example
Let's see an sample agent to generate test users. This agent is create by using TaskExecutionAgent.defaultBuilder.
This agent is configured to connect to a file system MCP server (@modelcontextprotocol/server-filesystem) which writes to /tmp.
package com.javaaidev.agenticpatterns.examples.taskexecution;
import com.javaaidev.agenticpatterns.core.AgentUtils;
import com.javaaidev.agenticpatterns.core.McpClientConfiguration;
import com.javaaidev.agenticpatterns.core.McpClientConfiguration.StdioClientProperties;
import com.javaaidev.agenticpatterns.taskexecution.TaskExecutionAgent;
import io.micrometer.observation.ObservationRegistry;
import java.util.List;
import java.util.Map;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpStdioClientProperties.Parameters;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;
@Configuration
public class UserGenerationConfiguration {
  @Bean
  @Description("Generate test users")
  @Qualifier("userGenerationAgent")
  public TaskExecutionAgent<UserGenerationRequest, UserGenerationResponse> userGenerationAgent(
      ChatClient.Builder chatClientBuilder,
      ObservationRegistry observationRegistry
  ) {
    var chatClient = chatClientBuilder.build();
    return TaskExecutionAgent.<UserGenerationRequest, UserGenerationResponse>defaultBuilder()
        .chatClient(chatClient)
        .responseType(UserGenerationResponse.class)
        .promptTemplate(AgentUtils.loadPromptTemplateFromClasspath(
            "prompt_template/generate-user.st"))
        .name("UserGeneration")
        .observationRegistry(observationRegistry)
        .mcpClientConfiguration(new McpClientConfiguration(
            new StdioClientProperties(
                Map.of(
                    "file-system", new Parameters(
                        "npx",
                        List.of(
                            "-y",
                            "@modelcontextprotocol/server-filesystem",
                            "/tmp"
                        ),
                        Map.of()
                    )
                )
            ),
            null
        ))
        .build();
  }
}
The request type is UserGenerationRequest, which contains the number of users to generate. The response type is UserGenerationResponse, which contains a list of Users.
The prompt template is loaded from a classpath resource. Below is the content of this prompt template.
The LLM is instructed to write the generated JSON file into local file system, which will use the file system MCP server configured to the agent.
Goal: Generate {count} users. Generated users are also saved as a JSON file to `/tmp`.
Requirements:
- Id should be a version 4 random UUID.
- Name should be using the format "$firstName $lastName".
- Email address should be using the format "$firstName.$lastName@$domain".
- For an address,
  - Country or region must use ISO 3166 alpha-2 code.
  - For province/state/city, they should be generated based on the country or region.
  - Address line can be fake.
  - Zip code should use the format based on the country or region.
- When generating multiple users, choose different countries or regions for those users.
- For a user, generate 1 to 3 addresses. At least one address has the type HOME.
Now we can expose a REST API to call this agent.
package com.javaaidev.agenticpatterns.examples.taskexecution;
import com.javaaidev.agenticpatterns.taskexecution.TaskExecutionAgent;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users_generation")
public class UserGenerationAgentController {
  private final TaskExecutionAgent<UserGenerationRequest, UserGenerationResponse> userGenerationAgent;
  public UserGenerationAgentController(
      @Qualifier("userGenerationAgent") TaskExecutionAgent<UserGenerationRequest, UserGenerationResponse> userGenerationAgent) {
    this.userGenerationAgent = userGenerationAgent;
  }
  @PostMapping
  public UserGenerationResponse generateUsers(@RequestBody UserGenerationRequest request) {
    return userGenerationAgent.call(request);
  }
}
When calling the REST API, we can get the test users in JSON. Below is the returned JSON when count is set to 1.
{
  "users": [
    {
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "name": "John Doe",
      "email": "john.doe@example.com",
      "mobilePhone": "+1234567890",
      "addresses": [
        {
          "id": "c9bf9e57-1685-4c89-bafb-ff5af830be8a",
          "addressType": "HOME",
          "countryOrRegion": "US",
          "provinceOrState": "NY",
          "city": "New York",
          "addressLine": "123 Fake Street",
          "zipCode": "10001"
        },
        {
          "id": "d3b07384-d9a1-4f3b-8f3d-2c7e0e3f5b2f",
          "addressType": "OFFICE",
          "countryOrRegion": "US",
          "provinceOrState": "NY",
          "city": "New York",
          "addressLine": "456 Another Ave",
          "zipCode": "10002"
        }
      ]
    }
  ]
}
When checking the content of /tmp, we can see the created JSON file.