Chain of Responsibility Pattern & Strategy Pattern
Output from application

Design Pattern For AI Backend: Chain of Responsibility Pattern & Strategy Pattern

Dr. Manoj Kumar Yadav
redbus India Blog

--

In this article, we will walk through the process of developing a dynamic prompt handling system in Java. This system is designed to manage a series of prompts and responses, modify variables based on responses, and return parsed responses in a structured manner. This article will cover the design philosophies, implementation details, and provide code snippets for a comprehensive understanding. Prelude to this article is The AI Revolution: ChatGPT Leads the Charge into the Summer of 2023 and The Holy Trinity of ChatGPT, SBERT, and MapDB

Git repo: https://github.com/manojkumarredbus/CoRSP

Design Philosophy

1. Modularity: The system is designed with modularity in mind, where different components (prompt handling, response parsing, variable modification) are separated into distinct classes and interfaces.
2. Scalability: The system can easily be extended to handle new types of prompts and responses without significant changes to the existing codebase.
3. Thread Safety: By using concurrent data structures, the system ensures thread safety, allowing it to be used in multi-threaded environments.
4. Flexibility: The system allows dynamic modification of variables and handling of responses based on different prompt IDs, making it adaptable to various use cases.

Components and Implementation

  1. AbstractPromptHandler

The `AbstractPromptHandler` class is an abstract class that defines the method `handleResponse` which must be implemented by any concrete prompt handler.


import java.util.Map;
public abstract class AbstractPromptHandler {
public abstract Object handleResponse(String promptId, Map<String, String> updatedVariables, String ipAddress);
}

2. PromptResponseHandler

The `PromptResponseHandler` class is responsible for managing prompt handlers, modifying variables based on prompt IDs, and handling responses.

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PromptResponseHandler {
private Map<String, AbstractPromptHandler> promptHandlers;
private String ipAddress;
public PromptResponseHandler(String ipAddress) {
this.promptHandlers = new ConcurrentHashMap<>();
this.ipAddress = ipAddress;
}
// Method to register a handler for a specific prompt ID
public void registerHandler(String promptId, AbstractPromptHandler handler) {
promptHandlers.put(promptId, handler);
}
// Method to modify variables based on prompt ID
public Map<String, String> modifyVariables(String promptId, Map<String, String> variables) {
Map<String, String> newVariables = new ConcurrentHashMap<>();
if (promptId.equals("Q1")) {
newVariables.put("newVariable1", "newValue1");
} else if (promptId.equals("Q2")) {
newVariables.put("newVariable2", "newValue2");
}
return newVariables;
}
// Method to handle the response for the current prompt ID and return the parsed response
public Object handleResponse(String promptId, Map<String, String> updatedVariables) {
AbstractPromptHandler handler = promptHandlers.get(promptId);
if (handler != null) {
return handler.handleResponse(promptId, updatedVariables, ipAddress);
} else {
System.out.println("No handler registered for prompt ID: " + promptId);
return null;
}
}
}

3. ExampleHandler

The `ExampleHandler` class extends `AbstractPromptHandler` and provides specific implementations for handling different prompt IDs.

import java.util.Map;
public class ExampleHandler extends AbstractPromptHandler {
@Override
public Object handleResponse(String promptId, Map<String, String> updatedVariables, String ipAddress) {
switch (promptId) {
case "Q1":
return handleQ1(updatedVariables, ipAddress);
case "Q2":
return handleQ2(updatedVariables, ipAddress);
case "Q3":
return handleQ3(updatedVariables, ipAddress);
default:
System.out.println("No specific handler for prompt ID: " + promptId);
return null;
}
}
private Object handleQ1(Map<String, String> updatedVariables, String ipAddress) {
System.out.println("Handling Q1 response from IP: " + ipAddress);
for (Map.Entry<String, String> entry : updatedVariables.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
return "Response for Q1";
}
private Object handleQ2(Map<String, String> updatedVariables, String ipAddress) {
System.out.println("Handling Q2 response from IP: " + ipAddress);
for (Map.Entry<String, String> entry : updatedVariables.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
return "Response for Q2";
}
private Object handleQ3(Map<String, String> updatedVariables, String ipAddress) {
System.out.println("Handling Q3 response from IP: " + ipAddress);
for (Map.Entry<String, String> entry : updatedVariables.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
return "Response for Q3";
}
}

4. PROMPTS

The `PROMPTS` class manages the prompt text, variables, and the chain of prompts. It also provides methods to iterate through the chain of prompts and handle responses.

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PROMPTS {
private Map<String, Prompt> prompts = new ConcurrentHashMap<>();
// Method to add prompts
public void addPrompt(String promptId, String text, String nextPromptId, String version, String modelId) {
Prompt prompt = new Prompt(promptId, text, nextPromptId, version, modelId);
prompts.put(promptId, prompt);
}
// Method to get a prompt by its ID
public String getPrompt(String promptId, Map<String, String> variables) {
Prompt prompt = prompts.get(promptId);
if (prompt != null) {
return replaceVariables(prompt.getText(), variables);
}
return null;
}
// Method to get the next prompt ID
public String getNextPromptId(String promptId) {
Prompt prompt = prompts.get(promptId);
return (prompt != null) ? prompt.getNextPromptId() : null;
}
// Method to replace variables in a text
private String replaceVariables(String text, Map<String, String> variables) {
for (Map.Entry<String, String> entry : variables.entrySet()) {
text = text.replace("{" + entry.getKey() + "}", entry.getValue());
}
return text;
}
// Method to update variables in a thread-safe manner
private Map<String, String> updateVariables(Map<String, String> currentVariables, Map<String, String> newVariables) {
currentVariables.putAll(newVariables);
return new ConcurrentHashMap<>(currentVariables);
}
// Method to iterate through the chain and handle responses
public Map<String, Object> iterateThroughChain(String startPromptId, Map<String, String> variables, PromptResponseHandler responseHandler) {
String currentPromptId = startPromptId;
Map<String, String> currentVariables = new ConcurrentHashMap<>(variables);
Map<String, Object> promptResponses = new ConcurrentHashMap<>();
while (currentPromptId != null) {
String promptText = getPrompt(currentPromptId, currentVariables);
if (promptText != null) {
System.out.println("Prompt: " + promptText);
}
// Call the abstract method to modify variables with the current prompt ID
Map<String, String> newVariables = responseHandler.modifyVariables(currentPromptId, currentVariables);
// Update current variables in a thread-safe manner
currentVariables = updateVariables(currentVariables, newVariables);
// Handle the response for the current prompt ID and store the parsed response
Object parsedResp = responseHandler.handleResponse(currentPromptId, currentVariables);
promptResponses.put(currentPromptId, parsedResp);
currentPromptId = getNextPromptId(currentPromptId);
}
return promptResponses;
}
}

5. Main Class/ParseMilitaryScienceQuestions

The main class parses JSON input to create prompts and demonstrates the prompt handling system.

import org.json.JSONArray;
import org.json.JSONException;
import java.util.HashMap;
import java.util.Map;
public class ParseMilitaryScienceQuestions {
public static void main(String[] args) {
String jsonString = "Here are the 10 questions on \"Military Science Calculation\":\n\n[\n" +
"\"Calculate the trajectory of a mortar shell with an initial velocity of {velocity} m/s and a muzzle elevation of {elevation} degrees.\",\n" +
"\"What is the maximum range of a rifle bullet fired at an angle of {angle} degrees with an initial velocity of {velocity} m/s?\",\n" +
"\"Determine the time of flight for an artillery shell traveling {distance} km at an altitude of {altitude} km.\",\n" +
"\"Calculate the distance to a target that is 500 meters away and has a height of 10 meters above the surrounding terrain.\",\n" +
"\"What is the effective range of a machine gun firing 7.62mm rounds at a rate of 600 rounds per minute?\",\n" +
"\"Determine the velocity of a tank moving at an angle of 45 degrees with an initial speed of 30 km/h.\",\n" +
"\"Calculate the trajectory of a rocket-propelled grenade launcher with an initial velocity of 150 m/s and a muzzle elevation of 60 degrees.\",\n" +
"\"What is the maximum effective range of a sniper rifle firing .308 Winchester rounds?\",\n" +
"\"Determine the time it takes for a helicopter to travel 50 km at an altitude of 1000 meters.\",\n
" +
"\"Calculate the distance to a target that is 800 meters away and has a height of 20 meters above the surrounding terrain.\"\n" +
"]";
// Extract the JSON array string from the input string
String jsonArrayString = jsonString.substring(jsonString.indexOf("["));
try {
// Parse the JSON array
JSONArray questionsArray = new JSONArray(jsonArrayString);
// Initialize the PROMPTS repository
PROMPTS promptRepository = new PROMPTS();
// Add prompts with placeholders and next prompt IDs
for (int i = 0; i < questionsArray.length(); i++) {
String question = questionsArray.getString(i);
String promptId = "Q" + (i + 1); // Generate a unique identifier for each question
String nextPromptId = (i < questionsArray.length() - 1) ? "Q" + (i + 2) : null; // Chain the next prompt
promptRepository.addPrompt(promptId, question, nextPromptId, "1.0", "model1");
}
// Initialize the response handler with an example IP address
PromptResponseHandler responseHandler = new PromptResponseHandler("192.168.0.1");
ExampleHandler exampleHandler = new ExampleHandler();
// Register the example handler for all prompts
for (int i = 1; i <= questionsArray.length(); i++) {
responseHandler.registerHandler("Q" + i, exampleHandler);
}
// Initial variables
Map<String, String> variables = new HashMap<>();
variables.put("velocity", "100");
variables.put("elevation", "45");
variables.put("angle", "30");
variables.put("distance", "10");
variables.put("altitude", "1");
// Iterate through the chain and handle responses
Map<String, Object> promptResponses = promptRepository.iterateThroughChain("Q1", variables, responseHandler);
// Print the responses
for (Map.Entry<String, Object> entry : promptResponses.entrySet()) {
System.out.println("Prompt ID: " + entry.getKey() + ", Response: " + entry.getValue());
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}

The design pattern created above can be primarily identified as the Chain of Responsibility pattern combined with aspects of the Strategy pattern. Here’s a breakdown of how these patterns are applied:

Chain of Responsibility:

  • Chain of Prompts: The system iterates through a series of prompts in a predefined sequence. Each prompt can have a reference to the next prompt, forming a chain.
  • Handling Responses: Each prompt can modify variables and handle its own specific response, which is passed along the chain.

Strategy:

  • Prompt Handlers: The AbstractPromptHandler defines a strategy for handling responses, and different concrete handlers (like ExampleHandler) provide specific implementations for different prompts. This allows the response handling strategy to vary independently from the prompt iteration mechanism.

By combining these two patterns, the system achieves flexibility in both iterating through a chain of prompts and handling responses based on the specific needs of each prompt.

Chain of Responsibility Pattern

In the Chain of Responsibility pattern, objects are arranged in a chain, where each object can pass the request along the chain until it is handled. In our implementation:

  • The PROMPTS class manages a chain of prompts, where each prompt has a reference to the next prompt (nextPromptId).
  • The iterateThroughChain method processes each prompt in the sequence, modifying variables and handling responses as it progresses through the chain.

Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. In our implementation:

  • The AbstractPromptHandler class defines the interface for handling responses.
  • Concrete implementations like ExampleHandler provide specific handling logic for different prompt IDs.
  • The PromptResponseHandler class uses these strategies to handle responses dynamically based on the prompt ID.

Summary

In this article, we’ve designed and implemented a flexible, modular, and scalable prompt handling system in Java. The key components include `AbstractPromptHandler` for handling specific prompts, `PromptResponseHandler` for managing handlers and modifying variables, and `PROMPTS` for managing the chain of prompts and responses. By following these design principles, you can easily extend this system to handle various types of prompts and responses in different applications.

Output of code

Prompt: Calculate the trajectory of a mortar shell with an initial velocity of 100 m/s and a muzzle elevation of 45 degrees.
Prompt: What is the maximum range of a rifle bullet fired at an angle of 30 degrees with an initial velocity of 100 m/s?
Prompt: Determine the time of flight for an artillery shell traveling 20 km at an altitude of 5 km.
Prompt: Calculate the distance to a target that is 500 meters away and has a height of 10 meters above the surrounding terrain.
Prompt: What is the effective range of a machine gun firing 7.62mm rounds at a rate of 600 rounds per minute?
Prompt: Determine the velocity of a tank moving at an angle of 45 degrees with an initial speed of 30 km/h.
Prompt: Calculate the trajectory of a rocket-propelled grenade launcher with an initial velocity of 150 m/s and a muzzle elevation of 60 degrees.
Prompt: What is the maximum effective range of a sniper rifle firing .308 Winchester rounds?
Prompt: Determine the time it takes for a helicopter to travel 50 km at an altitude of 1000 meters.
Prompt: Calculate the distance to a target that is 800 meters away and has a height of 20 meters above the surrounding terrain.
Handling response for prompt ID: Q1
Handling response for prompt ID: Q2
No handler registered for prompt ID: Q10
Handling response for prompt ID: Q3
No handler registered for prompt ID: Q4
No handler registered for prompt ID: Q5
No handler registered for prompt ID: Q6
No handler registered for prompt ID: Q7
No handler registered for prompt ID: Q8
No handler registered for prompt ID: Q9
Prompt ID: Q1
Text: Calculate the trajectory of a mortar shell with an initial velocity of {velocity} m/s and a muzzle elevation of {elevation} degrees.
Extracted Variables: [elevation, velocity]
Version: 1.0
Model ID: modelA
Path: [Q1]

Prompt ID: Q2
Text: What is the maximum range of a rifle bullet fired at an angle of {angle} degrees with an initial velocity of {velocity} m/s?
Extracted Variables: [angle, velocity]
Version: 1.0
Model ID: modelA
Path: [Q1, Q2]

Prompt ID: Q3
Text: Determine the time of flight for an artillery shell traveling {distance} km at an altitude of {altitude} km.
Extracted Variables: [altitude, distance]
Version: 1.0
Model ID: modelA
Path: [Q1, Q2, Q3]

Prompt ID: Q4
Text: Calculate the distance to a target that is 500 meters away and has a height of 10 meters above the surrounding terrain.
Extracted Variables: []
Version: 1.0
Model ID: modelA
Path: [Q1, Q2, Q3, Q4]

Prompt ID: Q5
Text: What is the effective range of a machine gun firing 7.62mm rounds at a rate of 600 rounds per minute?
Extracted Variables: []
Version: 1.0
Model ID: modelA
Path: [Q1, Q2, Q3, Q4, Q5]

Prompt ID: Q6
Text: Determine the velocity of a tank moving at an angle of 45 degrees with an initial speed of 30 km/h.
Extracted Variables: []
Version: 1.0
Model ID: modelA
Path: [Q1, Q2, Q3, Q4, Q5, Q6]

Prompt ID: Q7
Text: Calculate the trajectory of a rocket-propelled grenade launcher with an initial velocity of 150 m/s and a muzzle elevation of 60 degrees.
Extracted Variables: []
Version: 1.0
Model ID: modelA
Path: [Q1, Q2, Q3, Q4, Q5, Q6, Q7]

Prompt ID: Q8
Text: What is the maximum effective range of a sniper rifle firing .308 Winchester rounds?
Extracted Variables: []
Version: 1.0
Model ID: modelA
Path: [Q1, Q2, Q3, Q4, Q5, Q6, Q7, Q8]

Prompt ID: Q9
Text: Determine the time it takes for a helicopter to travel 50 km at an altitude of 1000 meters.
Extracted Variables: []
Version: 1.0
Model ID: modelA
Path: [Q1, Q2, Q3, Q4, Q5, Q6, Q7, Q8, Q9]

Prompt ID: Q10
Text: Calculate the distance to a target that is 800 meters away and has a height of 20 meters above the surrounding terrain.
Extracted Variables: []
Version: 1.0
Model ID: modelA
Path: [Q1, Q2, Q3, Q4, Q5, Q6, Q7, Q8, Q9, Q10]

Prompt: Calculate the trajectory of a mortar shell with an initial velocity of 100 m/s and a muzzle elevation of 45 degrees.

Read Next: Objective Driven Prompt System

--

--

Dr. Manoj Kumar Yadav
redbus India Blog

Doctor of Business Administration | VP - Engineering at redBus | Data Engineering | ML | Servers | Serverless | Java | Python | Dart | 3D/2D