Skip to main content

Parallelization Workflow 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>parallelization-workflow</artifactId>
<version>${agentic-patterns-version}</version>
</dependency>

ParallelizationWorkflowAgent

ParallelizationWorkflowAgent executes a list of subtasks in parallel. Subtasks are just other TaskExecutionAgents. Each subtask must have a unique id. This id is used to get its execution result.

There are two approaches to add subtasks.

The first approach is using the addSubtask method. When adding a subtask, you should provide parameters shown below.

ParameterTypeDescription
taskIdStringTask id
subtaskTaskExecutionAgentTask agent
requestTransformerFunctionTransform the request into task input

The second approach is using the createTasks method to create a list of subtasks from the request. The return value of createTasks method is a List<SubtaskCreationRequest<Request>> object. SubtaskCreationRequest encapsulates taskId, task, and requestTransformer used in the addSubtask method.

Subtasks added by these two approaches are merged.

DirectAssembling

DirectAssembling doesn't use an LLM to generate the result. It's useful for scenarios like voting.

Subclasses of DirectAssembling must implement the Response assemble(TaskExecutionResults results) method. This method assembles execution results of subtasks to produce the response.

PromptBasedAssembling

PromptBasedAssembling uses a LLM to generate the final result. A subclass of PromptBasedAssembling must provide the prompt template and values of template variables.

Values of template variables may come from two places.

  • From task input, overrides the getRequestPromptContext method.
  • From subtask execution results, overrides the getSubtasksPromptContext method.

The final Map<String, Object> values of template variables are merged from results of these two methods.

Example

The example is an agent to write articles about algorithms. Each article has code examples written in different programming languages. A parallelization workflow agent runs parallel subtasks to generate code examples, then generates an article using these code examples.

SampleCodeGenerationAgent shown below is a TaskExecutionAgent to generate code samples. It generates code samples based on a language and a description. For example, it can generate a code sample for quick sort using Java.

Agent to generate sample code
package com.javaaidev.agenticpatterns.examples.parallelizationworkflow;

import com.javaaidev.agenticpatterns.core.AgentUtils;
import com.javaaidev.agenticpatterns.examples.parallelizationworkflow.SampleCodeGenerationAgent.SampleCodeGenerationRequest;
import com.javaaidev.agenticpatterns.examples.parallelizationworkflow.SampleCodeGenerationAgent.SampleCodeGenerationResponse;
import com.javaaidev.agenticpatterns.taskexecution.TaskExecutionAgent;
import io.micrometer.observation.ObservationRegistry;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.ai.chat.client.ChatClient;

public class SampleCodeGenerationAgent extends
TaskExecutionAgent<SampleCodeGenerationRequest, SampleCodeGenerationResponse> {

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

@Override
protected String getPromptTemplate() {
return """
Write {language} code to meet the requirement.
{description}
""";
}

@Override
protected @Nullable Map<String, Object> getPromptContext(
@Nullable SampleCodeGenerationRequest request) {
return Map.of(
"language",
AgentUtils.safeGet(request, SampleCodeGenerationRequest::language, "java"),
"description",
AgentUtils.safeGet(request, SampleCodeGenerationRequest::description, "")
);
}

public record SampleCodeGenerationRequest(String language, String description) {

}

public record SampleCodeGenerationResponse(String code) {

}


}

AlgorithmArticleGenerationAgent shown below creates a list of subtasks from the request. A SampleCodeGenerationAgent is created for each algorithm in the AlgorithmArticleGenerationRequest. The result article is generated using an LLM based on the execution results of SampleCodeGenerationAgents.

In the createTasks method, each language is mapped to a SubtaskCreationRequest.

  • Task id is the language.
  • Task is a new SampleCodeGenerationAgent.
  • Request transformer converts the AlgorithmArticleGenerationRequest to a SampleCodeGenerationRequest used by the task.

AlgorithmArticleGenerationAgent uses an LLM to generate the final result. In the prompt template, there are two variables, algorithm and code_sample.

  • Value of algorithm comes from the request, populated in the getRequestPromptContext method.
  • Value of code_sample comes from subtask execution results, populated in the getSubtasksPromptContext method.
Agent to generate algorithm article
package com.javaaidev.agenticpatterns.examples.parallelizationworkflow;

import com.javaaidev.agenticpatterns.core.AgentUtils;
import com.javaaidev.agenticpatterns.examples.parallelizationworkflow.AlgorithmArticleGenerationAgent.AlgorithmArticleGenerationRequest;
import com.javaaidev.agenticpatterns.examples.parallelizationworkflow.AlgorithmArticleGenerationAgent.AlgorithmArticleGenerationResponse;
import com.javaaidev.agenticpatterns.examples.parallelizationworkflow.SampleCodeGenerationAgent.SampleCodeGenerationRequest;
import com.javaaidev.agenticpatterns.examples.parallelizationworkflow.SampleCodeGenerationAgent.SampleCodeGenerationResponse;
import com.javaaidev.agenticpatterns.parallelizationworkflow.PromptBasedAssembling;
import com.javaaidev.agenticpatterns.parallelizationworkflow.SubtaskCreationRequest;
import com.javaaidev.agenticpatterns.parallelizationworkflow.TaskExecutionResults;
import io.micrometer.observation.ObservationRegistry;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.util.CollectionUtils;

public class AlgorithmArticleGenerationAgent extends
PromptBasedAssembling<AlgorithmArticleGenerationRequest, AlgorithmArticleGenerationResponse> {

public AlgorithmArticleGenerationAgent(ChatClient chatClient,
@Nullable ObservationRegistry observationRegistry) {
super(chatClient, AlgorithmArticleGenerationResponse.class, observationRegistry);
}

@Override
protected String getPromptTemplate() {
return """
Goal: Write an article about {algorithm}.

Requirements:
- Start with a brief introduction.
- Include only sample code listed below.
- Output the article in markdown.

{sample_code}
""";
}

@Override
protected @Nullable Map<String, Object> getRequestPromptContext(
@Nullable AlgorithmArticleGenerationRequest request) {
return Map.of("algorithm",
AgentUtils.safeGet(request, AlgorithmArticleGenerationRequest::algorithm, ""));
}

@Override
protected @Nullable Map<String, Object> getSubtasksPromptContext(
TaskExecutionResults results) {
var sampleCode = results.allSuccessfulResults().entrySet().stream().map(entry -> """
Language: %s
Code:
%s
""".formatted(entry.getKey(), ((SampleCodeGenerationResponse) entry.getValue()).code()))
.collect(Collectors.joining("==========\n", "\n----------\n", "=========="));
return Map.of("sample_code", sampleCode);
}

@Override
protected List<SubtaskCreationRequest<AlgorithmArticleGenerationRequest>> createTasks(
@Nullable AlgorithmArticleGenerationRequest request) {
var languages = AgentUtils.safeGet(request,
AlgorithmArticleGenerationRequest::languages, List.<String>of());
if (CollectionUtils.isEmpty(languages)) {
return List.of();
}
var codeGenerationAgent = new SampleCodeGenerationAgent(chatClient, observationRegistry);
return languages.stream().map(
language -> new SubtaskCreationRequest<>(language,
codeGenerationAgent,
(AlgorithmArticleGenerationRequest req) -> new SampleCodeGenerationRequest(language,
"Implement algorithm " + req.algorithm()))).toList();

}

public record AlgorithmArticleGenerationRequest(String algorithm, List<String> languages) {

}

public record AlgorithmArticleGenerationResponse(String article) {

}
}

We can expose a REST API to test this agent.

REST controller
package com.javaaidev.agenticpatterns.examples.parallelizationworkflow;

import com.javaaidev.agenticpatterns.examples.parallelizationworkflow.AlgorithmArticleGenerationAgent.AlgorithmArticleGenerationRequest;
import com.javaaidev.agenticpatterns.examples.parallelizationworkflow.AlgorithmArticleGenerationAgent.AlgorithmArticleGenerationResponse;
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("/algorithm_article")
public class AlgorithmArticleGenerationController {

private final AlgorithmArticleGenerationAgent agent;

public AlgorithmArticleGenerationController(AlgorithmArticleGenerationAgent agent) {
this.agent = agent;
}

@PostMapping
public AlgorithmArticleGenerationResponse generateAlgorithmArticle(
@RequestBody AlgorithmArticleGenerationRequest request) {
return agent.call(request);
}
}