Skip to content

Commit e7ebc41

Browse files
committed
[BAEL-9419] Introduce the evaluator-optimizer-workflow pattern
1 parent 9ed9d9b commit e7ebc41

File tree

5 files changed

+199
-0
lines changed

5 files changed

+199
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.baeldung.springai.agenticpatterns.aimodels;
2+
3+
import org.springframework.ai.chat.client.ChatClient;
4+
5+
public interface CodeReviewClient extends ChatClient {
6+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.baeldung.springai.agenticpatterns.aimodels;
2+
3+
public final class CodeReviewClientPrompts {
4+
5+
private CodeReviewClientPrompts() {
6+
}
7+
8+
/**
9+
* Prompt for the code review of a given PR
10+
*/
11+
public static final String CODE_REVIEW_PROMPT = """
12+
Given a PR link -> generate a map with proposed code improvements.
13+
The key should be {class-name}:{line-number}:{short-description}.
14+
The value should be the code in one line. For example, 1 proposed improvement could be for the line 'int x = 0, y = 0;':
15+
{"Client:23:'no multiple variables defined in 1 line'", "int x = 0;\\n int y = 0;"}
16+
Rules are, to follow the checkstyle and spotless rules set to the repo. Keep java code clear, readable and extensible.
17+
18+
Finally, if it is not your first attempt, there might feedback provided to you, including your previous suggestion.
19+
You should reflect on it and improve the previous suggestions, or even add more.""";
20+
21+
/**
22+
* Prompt for the evaluation of the result
23+
*/
24+
public static final String EVALUATE_PROPOSED_IMPROVEMENTS_PROMPT = """
25+
Evaluate the suggested code improvements for correctness, time complexity, and best practices.
26+
27+
Return a Map with one entry. The key is the value the evaluation. The value will be your feedback.
28+
29+
The evaluation field must be one of: "PASS", "NEEDS_IMPROVEMENT", "FAIL"
30+
Use "PASS" only if all criteria are met with no improvements needed.
31+
""";
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.baeldung.springai.agenticpatterns.aimodels;
2+
3+
import org.springframework.ai.chat.prompt.Prompt;
4+
import org.springframework.lang.NonNull;
5+
import org.springframework.stereotype.Component;
6+
7+
@Component
8+
public class DummyCodeReviewClient implements CodeReviewClient {
9+
10+
@Override
11+
@NonNull
12+
public ChatClientRequestSpec prompt() {
13+
throw new UnsupportedOperationException("Not supported yet.");
14+
}
15+
16+
@Override
17+
@NonNull
18+
public ChatClientRequestSpec prompt(@NonNull String input) {
19+
throw new UnsupportedOperationException("Not supported yet.");
20+
}
21+
22+
@Override
23+
@NonNull
24+
public ChatClientRequestSpec prompt(@NonNull Prompt prompt) {
25+
throw new UnsupportedOperationException("Not supported yet.");
26+
}
27+
28+
@Override
29+
@NonNull
30+
public Builder mutate() {
31+
throw new UnsupportedOperationException("Not supported yet.");
32+
}
33+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.baeldung.springai.agenticpatterns.workflows.evaluator;
2+
3+
import static com.baeldung.springai.agenticpatterns.aimodels.CodeReviewClientPrompts.CODE_REVIEW_PROMPT;
4+
import static com.baeldung.springai.agenticpatterns.aimodels.CodeReviewClientPrompts.EVALUATE_PROPOSED_IMPROVEMENTS_PROMPT;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
9+
import org.springframework.ai.chat.client.ChatClient;
10+
import org.springframework.stereotype.Component;
11+
12+
import com.baeldung.springai.agenticpatterns.aimodels.CodeReviewClient;
13+
14+
@Component
15+
public class EvaluatorOptimizerWorkflow {
16+
17+
private final CodeReviewClient codeReviewClient;
18+
19+
public EvaluatorOptimizerWorkflow(CodeReviewClient codeReviewClient) {
20+
this.codeReviewClient = codeReviewClient;
21+
}
22+
23+
public Map<String, String> evaluate(String task) {
24+
return loop(task, new HashMap<>(), "");
25+
}
26+
27+
private Map<String, String> loop(String task, Map<String, String> latestSuggestions, String evaluation) {
28+
latestSuggestions = generate(task, latestSuggestions, evaluation);
29+
Map<String, String> evaluationResponse = evaluate(latestSuggestions, task);
30+
String outcome = evaluationResponse.keySet().iterator().next();
31+
evaluation = evaluationResponse.values().iterator().next();
32+
33+
if ("PASS".equals(outcome)) {
34+
System.out.println("Accepted RE Review Suggestions:\n" + latestSuggestions);
35+
return latestSuggestions;
36+
}
37+
38+
return loop(task, latestSuggestions, evaluation);
39+
}
40+
41+
private Map<String, String> generate(String task, Map<String, String> previousSuggestions, String evaluation) {
42+
String request = CODE_REVIEW_PROMPT +
43+
"\n PR: " + task +
44+
"\n previous suggestions: " + previousSuggestions +
45+
"\n evaluation on previous suggestions: " + evaluation;
46+
System.out.println("PR REVIEW PROMPT: " + request);
47+
48+
ChatClient.ChatClientRequestSpec requestSpec = codeReviewClient.prompt(request);
49+
ChatClient.CallResponseSpec responseSpec = requestSpec.call();
50+
Map<String, String> response = responseSpec.entity(Map.class);
51+
52+
System.out.println("PR REVIEW OUTCOME: " + response);
53+
54+
return response;
55+
}
56+
57+
private Map<String, String> evaluate(Map<String, String> latestSuggestions, String task) {
58+
String request = EVALUATE_PROPOSED_IMPROVEMENTS_PROMPT +
59+
"\n PR: " + task +
60+
"\n proposed suggestions: " + latestSuggestions;
61+
System.out.println("EVALUATION PROMPT: " + request);
62+
63+
ChatClient.ChatClientRequestSpec requestSpec = codeReviewClient.prompt(request);
64+
ChatClient.CallResponseSpec responseSpec = requestSpec.call();
65+
Map<String, String> response = responseSpec.entity(Map.class);
66+
System.out.println("EVALUATION OUTCOME: " + response);
67+
68+
return response;
69+
}
70+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.baeldung.springai.agenticpatterns.workflows.evaluator;
2+
3+
import static com.baeldung.springai.agenticpatterns.aimodels.CodeReviewClientPrompts.CODE_REVIEW_PROMPT;
4+
import static com.baeldung.springai.agenticpatterns.aimodels.CodeReviewClientPrompts.EVALUATE_PROPOSED_IMPROVEMENTS_PROMPT;
5+
import static org.assertj.core.api.Assertions.assertThat;
6+
import static org.mockito.Mockito.mock;
7+
import static org.mockito.Mockito.when;
8+
9+
import java.util.Map;
10+
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.extension.ExtendWith;
13+
import org.mockito.InjectMocks;
14+
import org.mockito.Mock;
15+
import org.mockito.junit.jupiter.MockitoExtension;
16+
import org.springframework.ai.chat.client.DefaultChatClient;
17+
18+
import com.baeldung.springai.agenticpatterns.aimodels.CodeReviewClient;
19+
20+
@ExtendWith(MockitoExtension.class)
21+
class EvaluatorOptimizerWorkflowTest {
22+
23+
@Mock
24+
private CodeReviewClient codeReviewClient;
25+
@InjectMocks
26+
private EvaluatorOptimizerWorkflow evaluatorOptimizerWorkflow;
27+
28+
@Test
29+
void opsPipeline_whenAllStepsAreSuccessful_thenSuccess() {
30+
String prLink = "https://github.com/org/repo/pull/70";
31+
String firstGenerationRequest = CODE_REVIEW_PROMPT + "\n PR: " + prLink + "\n previous suggestions: {}" + "\n evaluation on previous suggestions: ";
32+
Map<String, String> firstSuggestion = Map.of("Client:23:'no multiple variables in 1 line'", "int x = 0;\\n int y = 0;");
33+
mockCodeReviewClient(firstGenerationRequest, firstSuggestion);
34+
String firstEvaluationRequest = EVALUATE_PROPOSED_IMPROVEMENTS_PROMPT + "\n PR: " + prLink + "\n proposed suggestions: " + firstSuggestion;
35+
Map<String, String> firstEvaluation = Map.of("FAIL", "method names should be more descriptive");
36+
mockCodeReviewClient(firstEvaluationRequest, firstEvaluation);
37+
String secondGenerationRequest =
38+
CODE_REVIEW_PROMPT + "\n PR: " + prLink + "\n previous suggestions: " + firstSuggestion + "\n evaluation on previous suggestions: " + firstEvaluation.values().iterator().next();
39+
Map<String, String> secondSuggestion = Map.of("Client:23:'no multiple variables in 1 line & improved names'", "int readTimeout = 0;\\n int connectTimeout = 0;");
40+
mockCodeReviewClient(secondGenerationRequest, secondSuggestion);
41+
String secondEvaluationRequest = EVALUATE_PROPOSED_IMPROVEMENTS_PROMPT + "\n PR: " + prLink + "\n proposed suggestions: " + secondSuggestion;
42+
Map<String, String> secondEvaluation = Map.of("PASS", "");
43+
mockCodeReviewClient(secondEvaluationRequest, secondEvaluation);
44+
45+
Map<String, String> response = evaluatorOptimizerWorkflow.evaluate(prLink);
46+
47+
assertThat(response).isEqualTo(secondSuggestion);
48+
}
49+
50+
private void mockCodeReviewClient(String prompt, Map<String, String> response) {
51+
DefaultChatClient.DefaultChatClientRequestSpec requestSpec = mock(DefaultChatClient.DefaultChatClientRequestSpec.class);
52+
DefaultChatClient.DefaultCallResponseSpec responseSpec = mock(DefaultChatClient.DefaultCallResponseSpec.class);
53+
54+
when(codeReviewClient.prompt(prompt)).thenReturn(requestSpec);
55+
when(requestSpec.call()).thenReturn(responseSpec);
56+
when(responseSpec.entity(Map.class)).thenReturn(response);
57+
}
58+
}

0 commit comments

Comments
 (0)