diff --git a/agents/reflection/.gitignore b/agents/reflection/.gitignore new file mode 100644 index 0000000..e506055 --- /dev/null +++ b/agents/reflection/.gitignore @@ -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 \ No newline at end of file diff --git a/agents/reflection/.mvn/wrapper/maven-wrapper.jar b/agents/reflection/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..c1dd12f Binary files /dev/null and b/agents/reflection/.mvn/wrapper/maven-wrapper.jar differ diff --git a/agents/reflection/.mvn/wrapper/maven-wrapper.properties b/agents/reflection/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..b7cb93e --- /dev/null +++ b/agents/reflection/.mvn/wrapper/maven-wrapper.properties @@ -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 diff --git a/agents/reflection/AgentMergeSort.md b/agents/reflection/AgentMergeSort.md new file mode 100644 index 0000000..282b546 --- /dev/null +++ b/agents/reflection/AgentMergeSort.md @@ -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. diff --git a/agents/reflection/JacksonTestAgent.md b/agents/reflection/JacksonTestAgent.md new file mode 100644 index 0000000..8b0dd5b --- /dev/null +++ b/agents/reflection/JacksonTestAgent.md @@ -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 + + + + +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. + diff --git a/agents/reflection/README.md b/agents/reflection/README.md new file mode 100644 index 0000000..a52e950 --- /dev/null +++ b/agents/reflection/README.md @@ -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= +``` + + + +## 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 (``) +- +## 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. \ No newline at end of file diff --git a/agents/reflection/mvnw b/agents/reflection/mvnw new file mode 100755 index 0000000..eb65ff2 --- /dev/null +++ b/agents/reflection/mvnw @@ -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 "$@" diff --git a/agents/reflection/mvnw.cmd b/agents/reflection/mvnw.cmd new file mode 100755 index 0000000..4f5150a --- /dev/null +++ b/agents/reflection/mvnw.cmd @@ -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% diff --git a/agents/reflection/pom.xml b/agents/reflection/pom.xml new file mode 100644 index 0000000..4a44d10 --- /dev/null +++ b/agents/reflection/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.0 + + + com.example + ai-openai-helloworld + 0.0.1-SNAPSHOT + ai-openai-helloworld + Simple AI Application using OpenAPI Service + + 17 + + + + + + org.springframework.ai + spring-ai-bom + 1.0.0-SNAPSHOT + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.ai + spring-ai-openai-spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + + diff --git a/agents/reflection/src/main/java/org/springframework/ai/openai/samples/helloworld/Application.java b/agents/reflection/src/main/java/org/springframework/ai/openai/samples/helloworld/Application.java new file mode 100644 index 0000000..df63a86 --- /dev/null +++ b/agents/reflection/src/main/java/org/springframework/ai/openai/samples/helloworld/Application.java @@ -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)); + } + }; + } +} diff --git a/agents/reflection/src/main/java/org/springframework/ai/openai/samples/helloworld/ReflectionAgent.java b/agents/reflection/src/main/java/org/springframework/ai/openai/samples/helloworld/ReflectionAgent.java new file mode 100644 index 0000000..a28526d --- /dev/null +++ b/agents/reflection/src/main/java/org/springframework/ai/openai/samples/helloworld/ReflectionAgent.java @@ -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: + """) + .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("")) { + System.out.println("\n\nStop sequence found\n\n"); + break; + } + generation = generateChatClient.prompt(critique).call().content(); + } + return generation; + + } + +} diff --git a/agents/reflection/src/main/resources/application.yaml b/agents/reflection/src/main/resources/application.yaml new file mode 100644 index 0000000..0679b3f --- /dev/null +++ b/agents/reflection/src/main/resources/application.yaml @@ -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" + } + } + } \ No newline at end of file diff --git a/agents/reflection/src/test/java/org/springframework/ai/openai/samples/helloworld/MergeSort.java b/agents/reflection/src/test/java/org/springframework/ai/openai/samples/helloworld/MergeSort.java new file mode 100644 index 0000000..e0daf57 --- /dev/null +++ b/agents/reflection/src/test/java/org/springframework/ai/openai/samples/helloworld/MergeSort.java @@ -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(); + } +} diff --git a/agents/reflection/src/test/java/org/springframework/ai/openai/samples/helloworld/ResultFromAgentTest.java b/agents/reflection/src/test/java/org/springframework/ai/openai/samples/helloworld/ResultFromAgentTest.java new file mode 100644 index 0000000..da84b2f --- /dev/null +++ b/agents/reflection/src/test/java/org/springframework/ai/openai/samples/helloworld/ResultFromAgentTest.java @@ -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); + } +} \ No newline at end of file