Reflection Agent Example

This commit is contained in:
Mark Pollack
2024-11-01 15:45:50 -04:00
parent 9ea4523878
commit 6880ad100f
14 changed files with 1596 additions and 0 deletions

35
agents/reflection/.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
shell.log

Binary file not shown.

View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

View File

@@ -0,0 +1,263 @@
Let's chat!
USER: Generate a Java implementation of the Merge Sort algorithm
## Generation
Certainly! Here's a Java implementation of the Merge Sort algorithm. This implementation includes a `mergeSort` method
that recursively divides the array and a `merge` method that combines the sorted subarrays:
```java
public class MergeSort {
public static void mergeSort(int[] array) {
if (array == null || array.length < 2) {
return;
}
mergeSort(array, 0, array.length - 1);
}
private static void mergeSort(int[] array, int left, int right) {
if (left < right) {
int middle = (left + right) / 2;
// Recursively sort the two halves
mergeSort(array, left, middle);
mergeSort(array, middle + 1, right);
// Merge the sorted halves
merge(array, left, middle, right);
}
}
private static void merge(int[] array, int left, int middle, int right) {
int n1 = middle - left + 1;
int n2 = right - middle;
// Temporary arrays
int[] leftArray = new int[n1];
int[] rightArray = new int[n2];
// Copy data to temp arrays
System.arraycopy(array, left, leftArray, 0, n1);
System.arraycopy(array, middle + 1, rightArray, 0, n2);
int i = 0, j = 0;
int k = left;
// Merge the temp arrays back into the original array
while (i < n1 && j < n2) {
if (leftArray[i] <= rightArray[j]) {
array[k] = leftArray[i];
i++;
} else {
array[k] = rightArray[j];
j++;
}
k++;
}
// Copy remaining elements of leftArray, if any
while (i < n1) {
array[k] = leftArray[i];
i++;
k++;
}
// Copy remaining elements of rightArray, if any
while (j < n2) {
array[k] = rightArray[j];
j++;
k++;
}
}
public static void main(String[] args) {
int[] array = {12, 11, 13, 5, 6, 7};
System.out.println("Given Array");
printArray(array);
mergeSort(array);
System.out.println("\nSorted Array");
printArray(array);
}
private static void printArray(int[] array) {
for (int value : array) {
System.out.print(value + " ");
}
System.out.println();
}
}
```
### Explanation:
- **`mergeSort` Method**: This method is the entry point for the sorting process. It checks if the array is null or has
less than two elements, in which case it returns immediately. Otherwise, it calls the recursive method that handles
sorting.
- **`mergeSort` Recursive Method**: This method divides the array into two halves and recursively sorts each half. It
then merges the sorted halves using the `merge` method.
- **`merge` Method**: It combines the two sorted subarrays into a single sorted array. Temporary arrays are used to hold
the divided parts, and the merging process ensures elements are placed in the correct order.
- **`printArray` Method**: This utility method is used to print the contents of the array before and after sorting.
This implementation is efficient with a time complexity of O(n log n) and a space complexity of O(n).
## Critique
The Java Merge Sort implementation you provided is well-structured and follows the standard approach for implementing
this algorithm. However, I have a few recommendations to improve code readability and efficiency:
1. **Variable Naming:**
- Consider using clearer variable names for the indices, such as `start`, `end`, and `mid` instead of `left`,
`right`, and `middle`. This can improve readability by making it clearer that these indices refer to the start and
end of the partitions.
2. **Handling Edge Cases:**
- The `mergeSort` method already checks for null or very small arrays, which is good. However, confirm that this
check is needed if you assume the input will always be at least partially valid.
3. **Efficiency Consideration:**
- The current implementation creates new temporary arrays for each merge operation, which can be inefficient in
terms of space. You might consider an approach where a single auxiliary array is used for all merge operations,
reducing the space overhead.
4. **Documentation:**
- Adding brief comments directly above each method or section of code could help others understand the purpose and
functionality of each part more quickly.
5. **Main Method:**
- Consider adding more test cases in the `main` method to ensure that the algorithm handles edge cases correctly,
such as arrays with duplicate values, already sorted arrays, or arrays with negative numbers.
6. **Code Formatting:**
- Ensure consistent use of spaces and indentation, following Java conventions, to improve readability further.
Here is a minor suggestion for the change in variable naming:
```java
private static void mergeSort(int[] array, int start, int end) {
if (start < end) {
int mid = (start + end) / 2;
mergeSort(array, start, mid);
mergeSort(array, mid + 1, end);
merge(array, start, mid, end);
}
}
```
Overall, your implementation is correct and functional. These recommendations are mostly for enhancing code readability
and potential optimization for space efficiency.
AGENT: Thank you for the feedback! To incorporate your suggestions, I've made the following improvements to the original
Java Merge Sort implementation:
1. **Variable Naming**: Changed the variable names for indices to `start`, `end`, and `mid` for clarity.
2. **Single Auxiliary Array**: Introduced a single auxiliary array to reduce the space overhead during the merge
operations.
3. **Improved Documentation**: Added comments to explain the purpose and functionality of methods and key sections of
code.
4. **Expanded Test Cases**: Included additional test cases in the `main` method to verify the algorithm's robustness
against various edge cases.
Here is the revised implementation:
```java
public class MergeSort {
public static void mergeSort(int[] array) {
if (array == null || array.length < 2) {
return;
}
int[] tempArray = new int[array.length];
mergeSort(array, tempArray, 0, array.length - 1);
}
private static void mergeSort(int[] array, int[] tempArray, int start, int end) {
if (start < end) {
int mid = (start + end) / 2;
// Recursively sort the two halves
mergeSort(array, tempArray, start, mid);
mergeSort(array, tempArray, mid + 1, end);
// Merge the sorted halves
merge(array, tempArray, start, mid, end);
}
}
private static void merge(int[] array, int[] tempArray, int start, int mid, int end) {
// Copy data to temporary array for merging
System.arraycopy(array, start, tempArray, start, end - start + 1);
int leftIndex = start;
int rightIndex = mid + 1;
int currentIndex = start;
// Merge the temp arrays back into the original array
while (leftIndex <= mid && rightIndex <= end) {
if (tempArray[leftIndex] <= tempArray[rightIndex]) {
array[currentIndex] = tempArray[leftIndex];
leftIndex++;
} else {
array[currentIndex] = tempArray[rightIndex];
rightIndex++;
}
currentIndex++;
}
// Copy remaining elements of left half, if any
while (leftIndex <= mid) {
array[currentIndex] = tempArray[leftIndex];
leftIndex++;
currentIndex++;
}
// No need to copy the right half because it's already in place
}
public static void main(String[] args) {
int[][] testCases = {
{12, 11, 13, 5, 6, 7},
{5, 5, 5, 5, 5, 5},
{},
{-1, -3, -2, -5, -4},
{1, 2, 3, 4, 5, 6},
{9, 7, 5, 3, 1, 0}
};
for (int i = 0; i < testCases.length; i++) {
System.out.println("Test Case " + (i + 1) + ":");
System.out.println("Original Array:");
printArray(testCases[i]);
mergeSort(testCases[i]);
System.out.println("Sorted Array:");
printArray(testCases[i]);
System.out.println();
}
}
private static void printArray(int[] array) {
for (int value : array) {
System.out.print(value + " ");
}
System.out.println();
}
}
```
### Key Changes:
- **Single Auxiliary Array**: A single temporary array `tempArray` is used throughout the merge process, which reduces
the space complexity from O(n log n) to O(n).
- **Comments**: Added comments to guide through the code's logic, making it easier to understand.
- **Test Cases**: The `main` method now includes various test cases to ensure the algorithm's effectiveness across
different scenarios.
These enhancements should make the code more efficient and understandable.

View File

@@ -0,0 +1,309 @@
Let's chat!
USER: generate a junit5 test for a Person class that tests for appropriate serialization and deserialization
##generation
To generate a JUnit 5 test for a `Person` class that tests appropriate serialization and deserialization, we need to assume that the `Person` class implements the `Serializable` interface. Additionally, we'll use Java's built-in serialization mechanisms.
Below is a sample `Person` class and a corresponding JUnit 5 test class that tests the serialization and deserialization process:
### Person.java
```java
import java.io.Serializable;
import java.util.Objects;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// Getters and setters
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// Override equals and hashCode for comparison in tests
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(firstName, person.firstName) && Objects.equals(lastName, person.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName, age);
}
}
```
### PersonTest.java
```java
import org.junit.jupiter.api.Test;
import java.io.*;
import static org.junit.jupiter.api.Assertions.*;
class PersonTest {
@Test
void testSerializationAndDeserialization() {
Person originalPerson = new Person("John", "Doe", 30);
Person deserializedPerson = null;
// Serialize the object
try (ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
ObjectOutputStream outStream = new ObjectOutputStream(byteOutStream)) {
outStream.writeObject(originalPerson);
outStream.flush();
// Deserialize the object
try (ByteArrayInputStream byteInStream = new ByteArrayInputStream(byteOutStream.toByteArray());
ObjectInputStream inStream = new ObjectInputStream(byteInStream)) {
deserializedPerson = (Person) inStream.readObject();
}
} catch (IOException | ClassNotFoundException e) {
fail("Exception during serialization/deserialization: " + e.getMessage());
}
// Assert that the original and deserialized objects are equal
assertEquals(originalPerson, deserializedPerson, "The deserialized person should be equal to the original person");
}
}
```
### Explanation
- **Person Class**: This class is serializable and includes basic attributes like first name, last name, and age. It overrides `equals` and `hashCode` for comparison purposes.
- **Test Class**: The `testSerializationAndDeserialization` method in `PersonTest`:
- Creates a `Person` object.
- Serializes it using `ObjectOutputStream`.
- Deserializes it back using `ObjectInputStream`.
- Asserts that the original and deserialized `Person` objects are equal, using `assertEquals`.
This test ensures that the `Person` class can be serialized and deserialized correctly, maintaining data integrity.
##Critique
Here are some recommendations and critiques for the provided content:
1. **Error Handling Improvement**:
- Instead of catching and handling exceptions with a `fail` method, consider using `assertThrows` for expected exceptions or add more detailed error handling logic, especially in production code where it's crucial to handle exceptions gracefully.
2. **Code Readability**:
- Consider adding comments within the test method to separate the serialization and deserialization logic more clearly. This can help in understanding each step easily.
3. **Testing Edge Cases**:
- Add tests for edge cases, such as null values for `firstName` and `lastName`, and negative values or maximum integer value for `age`, to ensure that serialization handles these cases without any issues.
4. **Test Structure**:
- While the test correctly checks for equality, consider separating serialization and deserialization into distinct test methods for better modularity and easier debugging if something goes wrong.
5. **Test Coverage**:
- Introduce tests for other scenarios like modifying object attributes after deserialization to ensure object integrity and correct behavior of getters and setters.
6. **Java Class Structure**:
- Ensure fields in the `Person` class are initialized properly, possibly with default values, to avoid null-related issues during serialization and deserialization.
7. **Use of Modern Java Features**:
- Consider using Java's `try-with-resources` more explicitly with braces for better clarity and consistency.
By addressing these points, you can improve the robustness, readability, and coverage of your test code.
##Critique
<OK>
Stop sequence found
AGENT: Thank you for the detailed feedback. Let's incorporate your recommendations into a revised version of the code:
### Revised Person.java
```java
import java.io.Serializable;
import java.util.Objects;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// Getters and setters
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// Override equals and hashCode for comparison in tests
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(firstName, person.firstName) && Objects.equals(lastName, person.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName, age);
}
}
```
### Revised PersonTest.java
```java
import org.junit.jupiter.api.Test;
import java.io.*;
import static org.junit.jupiter.api.Assertions.*;
class PersonTest {
@Test
void testSerialization() {
Person originalPerson = new Person("John", "Doe", 30);
// Serialize the object
byte[] serializedPerson;
try (ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
ObjectOutputStream outStream = new ObjectOutputStream(byteOutStream)) {
outStream.writeObject(originalPerson);
serializedPerson = byteOutStream.toByteArray();
} catch (IOException e) {
fail("Serialization failed: " + e.getMessage());
return;
}
assertNotNull(serializedPerson, "Serialized person should not be null");
}
@Test
void testDeserialization() {
Person originalPerson = new Person("John", "Doe", 30);
byte[] serializedPerson;
// Serialize first to get byte array
try (ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
ObjectOutputStream outStream = new ObjectOutputStream(byteOutStream)) {
outStream.writeObject(originalPerson);
serializedPerson = byteOutStream.toByteArray();
} catch (IOException e) {
fail("Serialization failed: " + e.getMessage());
return;
}
// Deserialize the object
Person deserializedPerson;
try (ByteArrayInputStream byteInStream = new ByteArrayInputStream(serializedPerson);
ObjectInputStream inStream = new ObjectInputStream(byteInStream)) {
deserializedPerson = (Person) inStream.readObject();
} catch (IOException | ClassNotFoundException e) {
fail("Deserialization failed: " + e.getMessage());
return;
}
// Assert that the original and deserialized objects are equal
assertEquals(originalPerson, deserializedPerson, "Deserialized person should be equal to the original person");
}
@Test
void testSerializationWithNullValues() {
Person originalPerson = new Person(null, null, 30);
// Serialize and deserialize
byte[] serializedPerson;
try (ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
ObjectOutputStream outStream = new ObjectOutputStream(byteOutStream)) {
outStream.writeObject(originalPerson);
serializedPerson = byteOutStream.toByteArray();
} catch (IOException e) {
fail("Serialization failed: " + e.getMessage());
return;
}
Person deserializedPerson;
try (ByteArrayInputStream byteInStream = new ByteArrayInputStream(serializedPerson);
ObjectInputStream inStream = new ObjectInputStream(byteInStream)) {
deserializedPerson = (Person) inStream.readObject();
} catch (IOException | ClassNotFoundException e) {
fail("Deserialization failed: " + e.getMessage());
return;
}
// Assert that the original and deserialized objects are equal
assertEquals(originalPerson, deserializedPerson, "Deserialized person with null values should be equal to the original person");
}
}
```
### Key Changes:
1. **Separate Test Methods**: The tests for serialization and deserialization have been separated for better modularity.
2. **Error Handling**: We continue to use `fail` for handling exceptions but ensure detailed messages. `assertThrows` is more suitable for expected exceptions, which isn't the primary focus here.
3. **Code Readability**: Added comments and structured the code for clarity, particularly separating serialization and deserialization steps.
4. **Edge Case Testing**: Added a test for null values in `firstName` and `lastName`.
By addressing your critiques, the revised code offers improved readability, modularity, and coverage.

114
agents/reflection/README.md Normal file
View File

@@ -0,0 +1,114 @@
# Spring AI Hello World Chat Application
A simple command-line chat application demonstrating Spring AI's ChatClient capabilities with AI models.
It is based on the code in the repository https://github.com/neural-maze/agentic_patterns
## Prerequisites
- Java 17 or higher
- Maven
This examples uses OpenAI as the model provider.
Before using the AI commands, make sure you have a developer token from OpenAI.
Create an account at [OpenAI Signup](https://platform.openai.com/signup) and generate the token at [API Keys](https://platform.openai.com/account/api-keys).
The Spring AI project defines a configuration property named `spring.ai.openai.api-key` that you should set to the value of the API key obtained from OpenAI.
Exporting an environment variable is one way to set that configuration property:
```shell
export SPRING_AI_OPENAI_API_KEY=<INSERT KEY HERE>
```
## Running the Application
1. Clone the repository
2. Navigate to the project directory
3. Run the application using Maven wrapper:
`./mvnw spring-boot:run`
## Reflection Agent Demo
This project demonstrates the use of Spring AI to create a self-improving code generation system. The Reflection Agent uses two ChatClient instances in an iterative loop - one for generation and one for critique - to produce high-quality Java code.
## Overview
The application implements a reflection-based system where the Reflection Agent:
1. Uses a generation `ChatClient` instance to create code based on user prompts
2. Uses a critique `ChatClient` instance to review the generated code
3. Iteratively improves the code by feeding critique back to the generation ChatClient
4. Continues this loop until the critique ChatClient is satisfied with the quality
## Project Structure
### Main Components
* `Application.java`: The main Spring Boot application that provides the command-line interface
* `ReflectionAgent.java`: The core component that manages the iteration between generation and critique
## How It Works
### Initial Setup
The Reflection Agent creates two `ChatClient` instances:
- **generateChatClient**: For generating Java code based on user requests
- **critiqueChatClient**: For reviewing and critiquing the generated code
## Generation Process
- **User inputs a request**
- The **generation ChatClient** creates initial code
- The **critique ChatClient** reviews the code
- If improvements are needed, the **generation ChatClient** creates a revised version
- This continues for up to `maxIterations` or until the **critique ChatClient** approves (`<OK>`)
-
## ChatClient Configurations
- **Generation ChatClient**:
```text
You are a Java programmer tasked with generating high quality Java code. Your task is to generate the best content possible for the user's request.
```
- **Critique ChatClient**:
```text
You are tasked with generating critique and recommendations for the user's generated content. If the user content has something wrong or something to be improved, output a list of recommendations and critiques."
```
## Example Run
In this sample run, the user requested a JUnit 5 test for a `Person` class. See the file `JacksonTestAgent.md` for the actual output.
### Initial Generation
- The generation `ChatClient` created a basic `Person` class and test class
- Included serialization/deserialization functionality
- Implemented basic test cases
### Critique Phase
The critique `ChatClient identified several improvements:
- Better error handling
- Improved code readability
- Need for edge case testing
- Better test structure
- Expanded test coverage
- Enhanced Java class structure
- Modern Java feature usage
### Final Result
The generation `ChatClient` created improved code with:
- Separated test methods for better modularity
- Enhanced error handling with detailed messages
- Added edge case testing for null values
- Improved code structure and readability
- Better test coverage
## MergeSort
The file `AgentMergeSort.md` shows a similar run to create a merge sort algorithm.

305
agents/reflection/mvnw vendored Executable file
View File

@@ -0,0 +1,305 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ "$MVNW_REPOURL" = true]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

172
agents/reflection/mvnw.cmd vendored Executable file
View File

@@ -0,0 +1,172 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
echo Found %WRAPPER_JAR%
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
)
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
echo Finished downloading %WRAPPER_JAR%
)
@REM End of extension
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

84
agents/reflection/pom.xml Normal file
View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>ai-openai-helloworld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ai-openai-helloworld</name>
<description>Simple AI Application using OpenAPI Service</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1,32 @@
package org.springframework.ai.openai.samples.helloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.Scanner;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
CommandLineRunner cli(ReflectionAgent reflectionAgent) {
return args -> {
var scanner = new Scanner(System.in);
System.out.println("\nLet's chat!");
// Generate a Java implementation of the Merge Sort algorithm
while (true) {
System.out.print("\nUSER: ");
System.out.println("AGENT: " +
reflectionAgent.run(scanner.nextLine(), 2));
}
};
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2024 - 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.openai.samples.helloworld;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.stereotype.Component;
@Component
public class ReflectionAgent {
private final ChatClient generateChatClient;
private final ChatClient critiqueChatClient;
public ReflectionAgent(ChatModel chatModel) {
this.generateChatClient = ChatClient.builder(chatModel)
.defaultSystem("""
You are a Java programmer tasked with generating high quality Java code.
Your task is to Generate the best content possible for the user's request. If the user provides critique,
respond with a revised version of your previous attempt.
""")
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
this.critiqueChatClient = ChatClient.builder(chatModel)
.defaultSystem("""
You are tasked with generating critique and recommendations to the user's generated content.
If the user content has something wrong or something to be improved, output a list of recommendations
and critiques. If the user content is ok and there's nothing to change, output this: <OK>
""")
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
}
public String run(String userQuestion, int maxIterations) {
String generation = generateChatClient.prompt(userQuestion).call().content();
System.out.println("##generation\n\n" + generation);
String critique;
for (int i = 0; i < maxIterations; i++) {
critique = critiqueChatClient.prompt(generation).call().content();
System.out.println("##Critique\n\n" + critique);
if (critique.contains("<OK>")) {
System.out.println("\n\nStop sequence found\n\n");
break;
}
generation = generateChatClient.prompt(critique).call().content();
}
return generation;
}
}

View File

@@ -0,0 +1,29 @@
spring:
ai:
azure:
openai:
chat:
options:
function-callbacks:
- name: "functionName1"
description: "Description of what function1 does"
input-type-schema: |
{
"type": "object",
"properties": {
"param1": {
"type": "string"
}
}
}
- name: "functionName2"
description: "Description of what function2 does"
input-type-schema: |
{
"type": "object",
"properties": {
"param2": {
"type": "integer"
}
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2024 - 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.openai.samples.helloworld;
public class MergeSort {
public static void mergeSort(int[] array) {
if (array == null || array.length < 2) {
return;
}
int[] tempArray = new int[array.length];
mergeSort(array, tempArray, 0, array.length - 1);
}
private static void mergeSort(int[] array, int[] tempArray, int start, int end) {
if (start < end) {
int mid = (start + end) / 2;
// Recursively sort the two halves
mergeSort(array, tempArray, start, mid);
mergeSort(array, tempArray, mid + 1, end);
// Merge the sorted halves
merge(array, tempArray, start, mid, end);
}
}
private static void merge(int[] array, int[] tempArray, int start, int mid, int end) {
// Copy data to temporary array for merging
System.arraycopy(array, start, tempArray, start, end - start + 1);
int leftIndex = start;
int rightIndex = mid + 1;
int currentIndex = start;
// Merge the temp arrays back into the original array
while (leftIndex <= mid && rightIndex <= end) {
if (tempArray[leftIndex] <= tempArray[rightIndex]) {
array[currentIndex] = tempArray[leftIndex];
leftIndex++;
} else {
array[currentIndex] = tempArray[rightIndex];
rightIndex++;
}
currentIndex++;
}
// Copy remaining elements of left half, if any
while (leftIndex <= mid) {
array[currentIndex] = tempArray[leftIndex];
leftIndex++;
currentIndex++;
}
// No need to copy the right half because it's already in place
}
public static void main(String[] args) {
int[][] testCases = {
{12, 11, 13, 5, 6, 7},
{5, 5, 5, 5, 5, 5},
{},
{-1, -3, -2, -5, -4},
{1, 2, 3, 4, 5, 6},
{9, 7, 5, 3, 1, 0}
};
for (int i = 0; i < testCases.length; i++) {
System.out.println("Test Case " + (i + 1) + ":");
System.out.println("Original Array:");
printArray(testCases[i]);
mergeSort(testCases[i]);
System.out.println("Sorted Array:");
printArray(testCases[i]);
System.out.println();
}
}
private static void printArray(int[] array) {
for (int value : array) {
System.out.print(value + " ");
}
System.out.println();
}
}

View File

@@ -0,0 +1,79 @@
package org.springframework.ai.openai.samples.helloworld;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ResultFromAgentTest {
@Test
void testMergeSortWithRandomArray() {
int[] array = {12, 11, 13, 5, 6, 7};
int[] expected = {5, 6, 7, 11, 12, 13};
MergeSort.mergeSort(array);
assertArrayEquals(expected, array);
}
@Test
void testMergeSortWithAllSameElements() {
int[] array = {5, 5, 5, 5, 5};
int[] expected = {5, 5, 5, 5, 5};
MergeSort.mergeSort(array);
assertArrayEquals(expected, array);
}
@Test
void testMergeSortWithEmptyArray() {
int[] array = {};
int[] expected = {};
MergeSort.mergeSort(array);
assertArrayEquals(expected, array);
}
@Test
void testMergeSortWithNegativeNumbers() {
int[] array = {-1, -3, -2, -5, -4};
int[] expected = {-5, -4, -3, -2, -1};
MergeSort.mergeSort(array);
assertArrayEquals(expected, array);
}
@Test
void testMergeSortWithAlreadySortedArray() {
int[] array = {1, 2, 3, 4, 5, 6};
int[] expected = {1, 2, 3, 4, 5, 6};
MergeSort.mergeSort(array);
assertArrayEquals(expected, array);
}
@Test
void testMergeSortWithReverseSortedArray() {
int[] array = {9, 7, 5, 3, 1, 0};
int[] expected = {0, 1, 3, 5, 7, 9};
MergeSort.mergeSort(array);
assertArrayEquals(expected, array);
}
@Test
void testMergeSortWithNullArray() {
int[] array = null;
// Should not throw an exception
assertDoesNotThrow(() -> MergeSort.mergeSort(array));
}
@Test
void testMergeSortWithSingleElement() {
int[] array = {1};
int[] expected = {1};
MergeSort.mergeSort(array);
assertArrayEquals(expected, array);
}
}