From 4d05868418348a7c704f1c06a7254c3cbf700846 Mon Sep 17 00:00:00 2001 From: Zeyo Date: Wed, 8 May 2024 05:24:44 +0530 Subject: [PATCH] Fixed Logic for argument description not adding required or no null if there is a description already provided, made it so that ai lama won't create a new assistant unless a new model is given to it WIP Response Formatter --- .../java/me/ailama/commands/AiCommand.java | 61 +--------- .../annotations/ResponseFormatter.java | 15 +++ .../me/ailama/handler/annotations/Tool.java | 2 + .../handler/commandhandler/OllamaManager.java | 114 +++++++++++++++++- src/main/java/me/ailama/main/AiLama.java | 11 ++ src/main/java/me/ailama/tools/MathTools.java | 17 ++- src/main/java/me/ailama/tools/TimeTools.java | 3 +- 7 files changed, 159 insertions(+), 64 deletions(-) create mode 100644 src/main/java/me/ailama/handler/annotations/ResponseFormatter.java diff --git a/src/main/java/me/ailama/commands/AiCommand.java b/src/main/java/me/ailama/commands/AiCommand.java index dad628b..b064c0f 100644 --- a/src/main/java/me/ailama/commands/AiCommand.java +++ b/src/main/java/me/ailama/commands/AiCommand.java @@ -48,6 +48,8 @@ public void handleCommand(SlashCommandInteractionEvent event) { event.deferReply().queue(); } + AiLama.getInstance().startTimer(); + // Set Configurations String modelOption = event.getOption("model") != null ? event.getOption("model").getAsString() : null; String queryOption = event.getOption("ask").getAsString(); @@ -97,66 +99,9 @@ public void handleCommand(SlashCommandInteractionEvent event) { } else { - String tools = String.format("tools = %s",OllamaManager.getInstance().getFinalJson().build()); - // generate normal response based on the query if the url option is not provided or the web option is not provided response = OllamaManager.getInstance(). - 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" - } - - 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, you will be given a bad score. - if you don't respect the arguments description, 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) - ) - .build() + getTooledAssistant(modelOption) .answer(queryOption); ObjectMapper mapper = new ObjectMapper(); diff --git a/src/main/java/me/ailama/handler/annotations/ResponseFormatter.java b/src/main/java/me/ailama/handler/annotations/ResponseFormatter.java new file mode 100644 index 0000000..75caf31 --- /dev/null +++ b/src/main/java/me/ailama/handler/annotations/ResponseFormatter.java @@ -0,0 +1,15 @@ +package me.ailama.handler.annotations; + +public @interface ResponseFormatter { + + String[] responseOrder(); + + String[] responseVariables(); + + String preFormattedResponse() default ""; + + boolean isPreFormatted() default false; + + // if false, then direct response will be returned + boolean isResponseOrder() default false; +} diff --git a/src/main/java/me/ailama/handler/annotations/Tool.java b/src/main/java/me/ailama/handler/annotations/Tool.java index 60079c6..3c1a567 100644 --- a/src/main/java/me/ailama/handler/annotations/Tool.java +++ b/src/main/java/me/ailama/handler/annotations/Tool.java @@ -9,4 +9,6 @@ String description(); Args[] arguments(); + + ResponseFormatter responseFormatter() default @ResponseFormatter(responseOrder = {}, responseVariables = {}, preFormattedResponse = "", isPreFormatted = false, isResponseOrder = false); } diff --git a/src/main/java/me/ailama/handler/commandhandler/OllamaManager.java b/src/main/java/me/ailama/handler/commandhandler/OllamaManager.java index c480c74..d7d3d30 100644 --- a/src/main/java/me/ailama/handler/commandhandler/OllamaManager.java +++ b/src/main/java/me/ailama/handler/commandhandler/OllamaManager.java @@ -16,6 +16,7 @@ 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; @@ -46,6 +47,8 @@ public class OllamaManager { private final HashMap, ArrayList> classInstanceMap; + private Assistant assistant; + public OllamaManager() { url = AiLama.getInstance().fixUrl(Config.get("OLLAMA_URL") + ":" + Config.get("OLLAMA_PORT")); @@ -54,6 +57,7 @@ public OllamaManager() { tools = new HashMap<>(); classInstanceMap = new HashMap<>(); + assistant = null; addTool(AiLama.getInstance()); addTool(new MathTools()); @@ -62,6 +66,9 @@ public OllamaManager() { addTool(new UtilityTools()); } + /* + Uses Reflection Library to add the tools to the HashMap with specific annotation + */ public void addTool(Object toolClass) { ArrayList toolsList = new ArrayList<>(); @@ -120,7 +127,7 @@ public JsonArray getFinalJson() { argument.add("name",toolAnnotation.arguments()[i].name()) .add("type",toolAnnotation.arguments()[i].Type()); - if(!toolAnnotation.arguments()[i].description().isEmpty()) { + if(toolAnnotation.arguments()[i].required() || toolAnnotation.arguments()[i].noNull()) { StringBuilder description = getStringBuilder(toolAnnotation, i); @@ -132,6 +139,23 @@ 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 @@ -210,6 +234,94 @@ public static List getMethodsAnnotated(final Class type) { return methods; } + public Assistant getTooledAssistant(String modelOption) { + + if(assistant != null) { + + if(modelOption != null) { + assistant = null; + return getTooledAssistant(modelOption); + } + + return assistant; + + } + + String tools = String.format("tools = %s",OllamaManager.getInstance().getFinalJson().build()); + + 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", + ... + }, + "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) + ) + .build(); + + return assistant; + } + // Returns a custom Assistant that uses the provided model, allowing for more customization public AiServices createAssistantX(String modelName) { diff --git a/src/main/java/me/ailama/main/AiLama.java b/src/main/java/me/ailama/main/AiLama.java index 3f1648c..30512c4 100644 --- a/src/main/java/me/ailama/main/AiLama.java +++ b/src/main/java/me/ailama/main/AiLama.java @@ -4,6 +4,7 @@ import me.ailama.handler.annotations.Args; import net.dv8tion.jda.api.utils.data.DataObject; import okhttp3.*; +import org.joda.time.DateTime; import java.time.LocalDateTime; import java.util.ArrayList; @@ -13,6 +14,8 @@ public class AiLama { private static AiLama INSTANCE; + private DateTime starterTime; + public AiLama() { } @@ -94,6 +97,14 @@ public void wait(long delay, boolean threadDelay, boolean logStartEnd) { } } + public void startTimer() { + this.starterTime = DateTime.now(); + } + + public long getElapsedTime() { + return DateTime.now().getMillis() - this.starterTime.getMillis(); + } + public static AiLama getInstance() { if (AiLama.INSTANCE == null) { AiLama.INSTANCE = new AiLama(); diff --git a/src/main/java/me/ailama/tools/MathTools.java b/src/main/java/me/ailama/tools/MathTools.java index a3fdee2..16e8b52 100644 --- a/src/main/java/me/ailama/tools/MathTools.java +++ b/src/main/java/me/ailama/tools/MathTools.java @@ -1,14 +1,23 @@ 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") - }) + @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 + ) + ) public String add(Number a, Number b) { return String.valueOf(a.doubleValue() + b.doubleValue()); } diff --git a/src/main/java/me/ailama/tools/TimeTools.java b/src/main/java/me/ailama/tools/TimeTools.java index 086dc48..f6d3d77 100644 --- a/src/main/java/me/ailama/tools/TimeTools.java +++ b/src/main/java/me/ailama/tools/TimeTools.java @@ -23,7 +23,7 @@ public String formatTime(final long timeInMillis) { return String.format("%02d:%02d:%02d", hours, minutes, seconds); } - @Tool(name = "time", description = "Get the current time in a specific timezone time(is24Hour, timeZone)", arguments = { + @Tool(name = "time", description = "Get the current time", arguments = { @Args(name = "is24Hour", Type = "boolean", description = "true for 24-hour format, false for 12-hour format"), @Args(name = "timeZone", Type = "string", description = "Timezone in which you want to get the time like 'Asia/Kolkata'") }) @@ -32,6 +32,7 @@ public String time(boolean is24Hour, String timeZone) { if (timeZone != null) { dateTime = dateTime.withZone(DateTimeZone.forID(timeZone)); } + return dateTime.toString(is24Hour ? "HH:mm:ss" : "hh:mm:ss a"); }