From d7a8542f471691c8ad6d2db12d4d681bb8abd47c Mon Sep 17 00:00:00 2001 From: Zeyo Date: Sat, 11 May 2024 14:51:41 +0530 Subject: [PATCH] New System Prompt, improved math tool json by combining similar tools into one schema --- .idea/material_theme_project_new.xml | 13 ++ build.gradle | 4 +- .../java/me/ailama/commands/AiCommand.java | 7 +- .../handler/commandhandler/OllamaManager.java | 209 ++++++++++-------- src/main/java/me/ailama/tools/MathTools.java | 66 +++--- 5 files changed, 170 insertions(+), 129 deletions(-) create mode 100644 .idea/material_theme_project_new.xml diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..81900f6 --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 11674e8..5a62de3 100644 --- a/build.gradle +++ b/build.gradle @@ -45,11 +45,11 @@ dependencies { implementation group: 'io.github.cdimascio', name: 'java-dotenv', version: '5.2.2' implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.6' implementation group: 'dev.langchain4j', name: 'langchain4j-ollama', version: '0.30.0' - implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0' + implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '5.0.0-alpha.14' implementation group: 'joda-time', name: 'joda-time', version: '2.12.7' implementation group: 'org.jsoup', name: 'jsoup', version: '1.17.2' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0' implementation 'dev.langchain4j:langchain4j-easy-rag:0.30.0' diff --git a/src/main/java/me/ailama/commands/AiCommand.java b/src/main/java/me/ailama/commands/AiCommand.java index b064c0f..a3f3b4c 100644 --- a/src/main/java/me/ailama/commands/AiCommand.java +++ b/src/main/java/me/ailama/commands/AiCommand.java @@ -104,6 +104,8 @@ public void handleCommand(SlashCommandInteractionEvent event) { getTooledAssistant(modelOption) .answer(queryOption); + System.out.println(response); + ObjectMapper mapper = new ObjectMapper(); String temp = Pattern.compile("(?<=\":\").*(?=\")").matcher(response).replaceAll(x -> x.group().replace("\"", "_QUOTE_") ); @@ -112,7 +114,7 @@ public void handleCommand(SlashCommandInteractionEvent event) { Tool tooled = mapper.readValue(temp, Tool.class); if(!tooled.tooled) { - response = String.join("\n", tooled.response); + response = String.join("\n\n", tooled.response); } else { @@ -152,7 +154,8 @@ public void sendMessage(SlashCommandInteractionEvent event, String response) { { event.getHook().sendMessage(response).queue(); } - } + System.out.println(AiLama.getInstance().getElapsedTime()); + } } diff --git a/src/main/java/me/ailama/handler/commandhandler/OllamaManager.java b/src/main/java/me/ailama/handler/commandhandler/OllamaManager.java index d7d3d30..564547a 100644 --- a/src/main/java/me/ailama/handler/commandhandler/OllamaManager.java +++ b/src/main/java/me/ailama/handler/commandhandler/OllamaManager.java @@ -1,6 +1,5 @@ package me.ailama.handler.commandhandler; -import com.fasterxml.jackson.databind.util.JSONPObject; import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.DocumentSplitter; import dev.langchain4j.data.document.splitter.DocumentSplitters; @@ -16,7 +15,6 @@ import me.ailama.config.Config; import me.ailama.handler.JsonBuilder.JsonArray; import me.ailama.handler.JsonBuilder.JsonObject; -import me.ailama.handler.annotations.ResponseFormatter; import me.ailama.handler.annotations.Tool; import me.ailama.handler.interfaces.Assistant; import me.ailama.main.AiLama; @@ -25,7 +23,6 @@ import me.ailama.tools.MathTools; import me.ailama.tools.TimeTools; import me.ailama.tools.UtilityTools; -import org.apache.commons.lang3.StringEscapeUtils; import org.jetbrains.annotations.NotNull; import org.jsoup.Jsoup; @@ -127,11 +124,24 @@ public JsonArray getFinalJson() { argument.add("name",toolAnnotation.arguments()[i].name()) .add("type",toolAnnotation.arguments()[i].Type()); - if(toolAnnotation.arguments()[i].required() || toolAnnotation.arguments()[i].noNull()) { + if(toolAnnotation.arguments()[i].description() != null && !toolAnnotation.arguments()[i].description().isEmpty()) { + argument.add("description",toolAnnotation.arguments()[i].description()); + } - StringBuilder description = getStringBuilder(toolAnnotation, i); + if(toolAnnotation.arguments()[i].required()) { + argument.add("required",true); + } + else + { + argument.add("required",false); + } - argument.add("description",description.toString().trim()); + if(toolAnnotation.arguments()[i].noNull()) { + argument.add("no_null",true); + } + else + { + argument.add("no_null",false); } argumentJsonObjects.add(argument); @@ -139,23 +149,6 @@ public JsonArray getFinalJson() { arguments.objects(argumentJsonObjects); object.add("arguments",arguments); - - JsonObject responseFormatterObject = new JsonObject(); - - // add response formatter - ResponseFormatter responseFormatter = toolAnnotation.responseFormatter(); - - responseFormatterObject.add("response_order",responseFormatter.responseOrder()); - responseFormatterObject.add("response_variables",responseFormatter.responseVariables()); - - if(responseFormatter.isPreFormatted()) { - responseFormatterObject.add("pre_formatted_response",responseFormatter.preFormattedResponse()); - } - else { - responseFormatterObject.add("pre_formatted_response",""); - } - - object.add("response_formatter",responseFormatterObject); } // Add the tool object to the array @@ -251,77 +244,115 @@ public Assistant getTooledAssistant(String modelOption) { assistant = createAssistantX(modelOption) .systemMessageProvider(x -> +// String.format(""" +// You are a helpful AI assistant, you have a score, which can either be good or bad, +// you need to maintain a good score to be helpful, if you don't maintain a good score then you will be considered unhelpful. +// +// you will try to answer the users need as best as you can and only in JSON format, else you will be given a bad score. +// +// any of the tools description listed below match the specific needs of the query then use the tool to answer the query, +// the tools description is as specific as possible, so don't assume that the tool can be used for anything else. +// +// finally if a tool is matched then give response using following schema: +// +// { +// "tooled": true, +// "name": "tool_name", +// "arguments": { +// "argument_name": "value", +// ... +// }, +// "reason": "detailed_reason_for_using_tool", +// "match_percentage": Number +// } +// +// the tool_name is the name of the tool that you are using, the arguments are the arguments that you are passing to the tool. +// +// following are the rules for tools: +// if the tool description does not specify the user's needs then don't respond using a tool else you will be given a bad score. +// if you don't pass the required arguments to the tool then you will be given a bad score. +// if you pass a null value to a argument that specified NOT_NULL in its description then you will be given a bad score. +// if you don't respect the arguments data type and description, you will be given a bad score. +// if the tool has response_formatter then use it, else you will be given a bad score. +// if the tool's response_formatter has a pre_formatted_response then use it, else you will be given a bad score. +// don't add any other variables not defined in the response_variables, else you will be given a bad score. +// don't use the tool if user asked specifically for not using tools, else you will be given a bad score. +// the reason should not exceed 200 characters, and if it does, you will be given a bad score. +// +// and if you don't follow the schema, you will be given a bad score, but if you follow the schema, you will be given a good score. +// +// if you don't find a tool that match the requirements of the user then respond to the user normally, +// and also make the response to be encoded for the JSON format or you will be given a bad score, +// and use the following schema: +// +// { +// "tooled": false, +// "response": [ +// "paragraph", +// "paragraph", +// ... +// ] +// } +// +// in the above schema, the response is an array of paragraphs that you want to respond to the user, minimum of 1 paragraph. +// each new paragraph should be a new string in the array. +// between each paragraph, there should be '\\n'. +// +// %s +// """,tools) + + //todo improve this prompt + String.format(""" - You are a helpful AI assistant, you have a score, which can either be good or bad, - you need to maintain a good score to be helpful, if you don't maintain a good score then you will be considered unhelpful. - - you will try to answer the users need as best as you can and only in JSON format, else you will be given a bad score. - - any of the tools description listed below match the specific needs of the query then use the tool to answer the query, - the tools description is as specific as possible, so don't assume that the tool can be used for anything else. - - finally if a tool is matched then give response using following schema: - - { - "tooled": true, - "name": "tool_name", - "arguments": { - "argument_name": "value", - ... - }, - "response_formatter": "response_format", - "reason": "detailed_reason_for_using_tool", - "match_percentage": Number - } - - the tool_name is the name of the tool that you are using, the arguments are the arguments that you are passing to the tool. - the response_formatter is the format of the response, here add your response and add '({n})' where the response from the tool will be placed. - the response_formatter can contains both, the before or after response text, like "The answer for 2 + 2 is ({response_variable})". - - following are the rules for tools: - if the tool description does not specify the user's needs then don't respond using a tool else you will be given a bad score. - if you don't pass the required arguments to the tool then you will be given a bad score. - if you pass a null value to a argument that specified NOT_NULL in its description then you will be given a bad score. - if you don't respect the arguments data type, you will be given a bad score. - if you don't respect the arguments description, you will be given a bad score. - if the match_percentage is more than 85 then use the tool, else you will be given a bad score. - if the tool has response_formatter then use it, else you will be given a bad score. - if the tool's response_formatter has a pre_formatted_response then use it, else you will be given a bad score. - the tool's response_formatter has a response_order, this is the order of the response variables, and you must respect it, else you will be given a bad score. - the tool's response_formatter has a response_variables, this is the variables that you will use in the response_formatter, and you must respect it, else you will be given a bad score. - the tool's response_formatter must not exceed 200 characters, and if it does, you will be given a bad score. - the tool's response_variables contains the only variables that you can use in the response_formatter, and you must respect it, else you will be given a bad score. - don't add any other variables not defined in the response_variables, else you will be given a bad score. - don't use the tool if user asked specifically for not using tools, else you will be given a bad score. - the reason should not exceed 200 characters, and if it does, you will be given a bad score. - - and if you don't follow the schema, you will be given a bad score, but if you follow the schema, you will be given a good score. - - if you don't find a tool that match the requirements of the user then respond to the user normally, - and also make the response to be encoded for the JSON format or you will be given a bad score, - and use the following schema: - - { - "tooled": false, - "response": [ - "paragraph", - "paragraph", - ... - ] - } - - in the above schema, the response is an array of paragraphs that you want to respond to the user, minimum of 1 paragraph. - each new paragraph should be a new string in the array. - between each paragraph, there should be '\\n'. - - %s - """,tools) + + You are a helpful AI Assistant, you will try to answer the user's needs as best as you can and only in JSON format. + you have access to a set of tools that may help you give accurate responses to the user. + if you find a tool that matches the user's query then use the tool to answer the query. + + If a tool is matched then give response using the following schema: + + { + "tooled": true, + "name": "tool_name", + "arguments": { + "argument_name": "value", + ... + }, + "reason": "detailed_reason_for_using_tool", + "match_percentage": Number + } + + the tool_name is the name of the tool that you are using, the arguments are the arguments that you are passing to the tool. + the tool's description is as specific as possible, so don't assume that the tool can be used for anything else. + + if you don't find a tool that matches the requirements of the user then respond to the user using the following schema: + + { + "tooled": false, + "response": [ + "paragraph", + "paragraph", + ... + ] + } + + each paragraph must be 200 characters or less + + in the above schema, the response is an array of paragraphs that you want to respond to the user, minimum of 1 paragraph. + each new paragraph should be a new string in the array. + + %s + """, tools) ) .build(); + System.out.println(tools); + return assistant; } + // write a hello world function + // Returns a custom Assistant that uses the provided model, allowing for more customization public AiServices createAssistantX(String modelName) { @@ -338,6 +369,10 @@ public AiServices createAssistantX(String modelName) { .chatLanguageModel(ollama); } + // Hello World + + + // Just a Simple Response public Assistant createAssistant(String modelName) { diff --git a/src/main/java/me/ailama/tools/MathTools.java b/src/main/java/me/ailama/tools/MathTools.java index 16e8b52..babc093 100644 --- a/src/main/java/me/ailama/tools/MathTools.java +++ b/src/main/java/me/ailama/tools/MathTools.java @@ -1,77 +1,67 @@ package me.ailama.tools; import me.ailama.handler.annotations.Args; -import me.ailama.handler.annotations.ResponseFormatter; import me.ailama.handler.annotations.Tool; public class MathTools { - @Tool(name = "add", description = "Addition ('+') of two numbers like N1+N2", - arguments = { - @Args(name = "a", Type = "number"), - @Args(name = "b", Type = "number") - }, - responseFormatter = @ResponseFormatter( - responseOrder = {"result"}, - responseVariables = {"result"}, - isPreFormatted = true, - isResponseOrder = true - ) - ) + @Tool(name = "basicMathOperations", description = "Basic math operations, only does Addition (+), Subtraction (-), Multiplication (*), Division (/), Modulus (%), Power (^)", arguments = { + @Args(name = "a", Type = "number"), + @Args(name = "b", Type = "number"), + @Args(name = "operation_symbol", Type = "string") + }) + public String basicMathOperations(Number a, Number b, String operation) { + return switch (operation) { + case "+" -> add(a, b); + case "-" -> subtract(a, b); + case "*" -> multiply(a, b); + case "/" -> divide(a, b); + case "%" -> modulus(a, b); + case "^" -> power(a, b); + default -> "Invalid operation"; + }; + } + public String add(Number a, Number b) { return String.valueOf(a.doubleValue() + b.doubleValue()); } - @Tool(name = "subtract", description = "Subtraction ('-') of two numbers like N1-N2", arguments = { - @Args(name = "a", Type = "number"), - @Args(name = "b", Type = "number") - }) public String subtract(Number a, Number b) { return String.valueOf(a.doubleValue() - b.doubleValue()); } - @Tool(name = "multiply", description = "Multiplication ('*') of two numbers like N1*N2", arguments = { - @Args(name = "a", Type = "number"), - @Args(name = "b", Type = "number") - }) public String multiply(Number a, Number b) { return String.valueOf(a.doubleValue() * b.doubleValue()); } - @Tool(name = "divide", description = "Division ('/') of two numbers like N1/N2", arguments = { - @Args(name = "a", Type = "number"), - @Args(name = "b", Type = "number") - }) public String divide(Number a, Number b) { return String.valueOf(a.doubleValue() / b.doubleValue()); } - @Tool(name = "modulus", description = "Modulus ('%') of two numbers like N1%N2", arguments = { - @Args(name = "a", Type = "number"), - @Args(name = "b", Type = "number") - }) public String modulus(Number a, Number b) { return String.valueOf(a.doubleValue() % b.doubleValue()); } - @Tool(name = "power", description = "Power ('^') of two numbers like N1^N2", arguments = { - @Args(name = "a", Type = "number"), - @Args(name = "b", Type = "number") - }) public String power(Number a, Number b) { return String.valueOf(Math.pow(a.doubleValue(), b.doubleValue())); } - @Tool(name = "sqrt", description = "Square root of a number like sqrt(N1)", arguments = { - @Args(name = "a", Type = "number") + @Tool(name = "squareOrCube", description = "Square or cube of a number, symbols are square_root and cube_root", arguments = { + @Args(name = "a", Type = "number"), + @Args(name = "operation_symbol", Type = "string") }) + public String squareOrCube(Number a, String operation) { + return switch (operation) { + case "square_root" -> sqrt(a); + case "cube_root" -> cubeRoot(a); + default -> "Invalid operation"; + }; + } + public String sqrt(Number a) { return String.valueOf(Math.sqrt(a.doubleValue())); } - @Tool(name = "cubeRoot", description = "Cube root of a number", arguments = { - @Args(name = "a", Type = "number") - }) public String cubeRoot(Number a) { return String.valueOf(Math.cbrt(a.doubleValue())); }