Skip to main content

Task Execution Agent

To use this agent, add the Maven dependency to your project, replacing ${agentic-patterns-version} with the latest version.

Latest version: Maven Central 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.

ParameterRequired?TypeDescription
chatClientyesChatClientSpring AI ChatClient
responseTypenoTypeType of response, only required when auto-detection fails
observationRegistrynoObservationRegistryMicrometer ObservationRegistry for tracing

TaskExecutionAgent is a parameterized type with request type and response type.

When subclassing TaskExecutionAgent, the getPromptTemplate method must be implemented. This method returns the prompt template sent to an LLM.

If the prompt template contains variables, then the getPromptContext method may be overridden 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 override updateChatClientRequest method to modify the ChatClientRequestSpec object. For example, you can add a system text.

Use updateChatClientRequest to customize request
@Override
protected void updateChatClientRequest(ChatClientRequestSpec 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.

NoLLMTaskExecutionAgent

If you agent doesn't use LLM to execute a task, you can extends from NoLLMTaskExecutionAgent. Then you must override call method, as the default implementation of call method uses ChatClient to send request to an LLM.

Example

Let's see an sample agent to generate test users.

UserGenerationAgent extends from TaskExecutionAgent. The request type is UserGenerationRequest, which contains the number of users to generate. The response type is List<User>, which is a list of Users.

In the getPromptTemplate method, the prompt template is loaded from a classpath resource. Below is the content of this prompt template.

Prompt template
Goal: Generate {count} users

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.

In the getPromptContext method, the UserGenerationRequest object is used to set the value of variable count in the prompt template.

UserGenerationAgent
package com.javaaidev.agenticpatterns.examples.taskexecution;

import com.javaaidev.agenticpatterns.core.AgentUtils;
import com.javaaidev.agenticpatterns.examples.taskexecution.UserGenerationAgent.UserGenerationRequest;
import com.javaaidev.agenticpatterns.taskexecution.TaskExecutionAgent;
import io.micrometer.observation.ObservationRegistry;
import java.util.List;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.springframework.ai.chat.client.ChatClient;

public class UserGenerationAgent extends
TaskExecutionAgent<UserGenerationRequest, List<User>> implements
Function<UserGenerationRequest, List<User>> {

public UserGenerationAgent(ChatClient chatClient,
@Nullable ObservationRegistry observationRegistry) {
super(chatClient, observationRegistry);
}

@Override
protected String getPromptTemplate() {
return AgentUtils.loadPromptTemplateFromClasspath("prompt_template/generate-user.st");
}

public record UserGenerationRequest(int count) {

}
}

Now we can expose a REST API to call this agent.

REST controller for agent
package com.javaaidev.agenticpatterns.examples.taskexecution;

import com.javaaidev.agenticpatterns.examples.taskexecution.UserGenerationAgent.UserGenerationRequest;
import java.util.List;
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 UserGenerationAgent userGenerationAgent;

public UserGenerationAgentController(UserGenerationAgent userGenerationAgent) {
this.userGenerationAgent = userGenerationAgent;
}

@PostMapping
public List<User> 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.

Generated users
[
{
"id": "f3e3d3c0-6e4d-6e5d-0e3f-3c0e3d3c0e3d",
"name": "John Doe",
"email": "John.Doe@example.com",
"mobilePhone": "+1-416-555-1234",
"addresses": [
{
"id": "b1c1e1f0-4c2b-4c3b-8c1e-1f0b1c1e1f0b",
"addressType": "HOME",
"countryOrRegion": "CA",
"provinceOrState": "Ontario",
"city": "Toronto",
"addressLine": "123 Elm Street",
"zipCode": "M5A 1A1"
},
{
"id": "c2d2e2f0-5d3c-5d4c-9d2e-2f0c2d2e2f0c",
"addressType": "OFFICE",
"countryOrRegion": "CA",
"provinceOrState": "Ontario",
"city": "Toronto",
"addressLine": "456 Maple Avenue",
"zipCode": "M5B 2B2"
}
]
}
]