Skip to content

Commit

Permalink
Added tool, callApi
Browse files Browse the repository at this point in the history
Added tool, getTools
minor fixes
  • Loading branch information
ZeyoYT committed May 27, 2024
1 parent 592d98a commit 5e66cd1
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
}

if(CommandRegister.getInstance().getCommandMap().containsKey(event.getInteraction().getName())) {
Thread.ofVirtual().start(() -> CommandRegister.getInstance().getCommand(event.getInteraction().getName()).handleCommand(event));
Thread.ofVirtual().start(() -> {
CommandRegister.getInstance().getCommand(event.getInteraction().getName()).handleCommand(event);
});
}
}
}
47 changes: 28 additions & 19 deletions src/main/java/me/ailama/commands/AiCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,26 +91,27 @@ public void handleCommand(SlashCommandInteractionEvent event) {

}

boolean isTooledQuery = queryOption.startsWith(".");

if(urlOption != null || urlForContent != null) {

// if the url option is provided or the web option is provided, ask the assistant to answer the query based on the url
Assistant assistant = OllamaManager.getInstance().urlAssistant( List.of(urlForContent != null ? urlForContent : urlOption) , modelOption, userId);
Assistant assistant = OllamaManager.getInstance().urlAssistant( List.of(urlForContent != null ? urlForContent : urlOption) , modelOption, userId, isTooledQuery);

if(assistant != null) {
response = assistant.chat(userId,queryOption);

// add the source of the content to the response
if(response != null) {
if(response != null && !isTooledQuery) {
response += "\n\nSource: <" + (urlForContent != null ? urlForContent : urlOption) + ">";
}
}
}
else
{
// generate normal response based on the query if the url option is not provided or the web option is not provided
boolean isTooledQuery = queryOption.startsWith(".");

if(isTooledQuery) {
response = OllamaManager.getInstance().getTooledAssistant(modelOption, userId).answer(queryOption.replaceFirst(".", ""));
response = OllamaManager.getInstance().getTooledAssistant(modelOption, userId, null).answer(queryOption.replaceFirst(".", ""));
}
else
{
Expand All @@ -119,31 +120,39 @@ public void handleCommand(SlashCommandInteractionEvent event) {
.chat(userId,queryOption);
}

if(isTooledQuery) {
ObjectMapper mapper = new ObjectMapper();

String temp = Pattern.compile("(?<=\":\").*(?=\")").matcher(response).replaceAll(x -> x.group().replace("\"", "_QUOTE_") );
if(!isTooledQuery) {
response = response.replace("_QUOTE_", "\"");
}

}

if(isTooledQuery) {
ObjectMapper mapper = new ObjectMapper();

try {
Tool tooled = mapper.readValue(temp, Tool.class);
String temp = Pattern.compile("(?<=\":\").*(?=\")").matcher(response).replaceAll(x -> x.group().replace("\"", "_QUOTE_") );

if(!tooled.tooled) {
response = String.join("\n\n", tooled.response);
try {
Tool tooled = mapper.readValue(temp, Tool.class);

if(!tooled.tooled) {
response = String.join("\n\n", tooled.response);
}
else
{
if(OllamaManager.getInstance().isToolRawResponse(tooled.name)) {
response = OllamaManager.getInstance().executeTool(tooled.name, response).toString();
}
else
{
response = OllamaManager.getInstance().executeTool(tooled.name, tooled.arguments.values().toArray()).toString();
}
}
catch (Exception e) {
response = temp;
Main.LOGGER.warn("Error while executing tool: {}", e.getMessage());
}
}
else {
response = response.replace("_QUOTE_", "\"");
catch (Exception e) {
response = temp;
Main.LOGGER.warn("Error while executing tool: {}", e.getMessage());
}

}


Expand Down
2 changes: 1 addition & 1 deletion src/main/java/me/ailama/commands/WebCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public void handleCommand(SlashCommandInteractionEvent event) {
}

// Create a URL Assistant
Assistant assistant = OllamaManager.getInstance().urlAssistant(urlForContent, modelOption, userId);
Assistant assistant = OllamaManager.getInstance().urlAssistant(urlForContent, modelOption, userId, false);

// if there was an error while creating the assistant
if(assistant == null) {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/me/ailama/handler/annotations/Tool.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
String name();
String description();

Args[] arguments();
Args[] arguments() default {};

boolean rawResponse() default false;

ResponseFormatter responseFormatter() default @ResponseFormatter(responseOrder = {}, responseVariables = {}, preFormattedResponse = "", isPreFormatted = false, isResponseOrder = false);
}
161 changes: 96 additions & 65 deletions src/main/java/me/ailama/handler/commandhandler/OllamaManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;
import me.ailama.config.Config;
import me.ailama.handler.JsonBuilder.JsonArray;
import me.ailama.handler.JsonBuilder.JsonObject;
Expand All @@ -31,7 +30,6 @@

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

Expand All @@ -53,9 +51,6 @@ public class OllamaManager {

private boolean isTooledAssistant = false;

// TODO Implement Separate Memory for each User
// https://github.com/langchain4j/langchain4j-examples/blob/main/other-examples/src/main/java/ServiceWithMemoryForEachUserExample.java

public OllamaManager() {

url = AiLama.getInstance().fixUrl(Config.get("OLLAMA_URL") + ":" + Config.get("OLLAMA_PORT"));
Expand Down Expand Up @@ -184,6 +179,10 @@ public Class<?> getToolClass(String toolName) {
return null;
}

public boolean isToolRawResponse(String toolName) {
return getTool(toolName) != null && getTool(toolName).getAnnotation(Tool.class).rawResponse();
}

// Execute the Tool
public Object executeTool(String toolName, Object... args) {

Expand Down Expand Up @@ -229,84 +228,51 @@ public Assistant getAssistantFromID(String id) {
return null;
}

public Assistant getTooledAssistant(String modelOption, String userID) {
public Assistant getTooledAssistant(String modelOption, String userID, List<Document> documents) {

Assistant assistantFromID = getAssistantFromID(userID);

if(assistantFromID != null && isTooledAssistant) {

if(modelOption != null) {
return getTooledAssistant(modelOption, userID);
return getTooledAssistant(modelOption, userID, documents);
}

return assistantFromID;

}

DocumentSplitter splitter = DocumentSplitters.recursive(500, 0);
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
OllamaEmbeddingModel embeddingModel = OllamaEmbeddingModel.builder().baseUrl(url).modelName(this.embeddingModel).build();


if(documents != null) {
for (Document doc : documents) {
List<TextSegment> segments = splitter.split(doc);
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();

embeddingStore.addAll(embeddings, segments);
}
}

ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(4)
.minScore(0.6)
.build();

String tools = String.format("tools = %s",OllamaManager.getInstance().getFinalJson().build());

assistantFromID = 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",
...
],
"rule": "which_rule_used_to_not_use_tool"
}
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)
getFormat(tools)
)
.contentRetriever(contentRetriever)
.build();

// todo: possible bug here, as its global variable for all users
isTooledAssistant = true;

// Update or Add the Assistant to the HashMap
Expand All @@ -315,6 +281,67 @@ public Assistant getTooledAssistant(String modelOption, String userID) {
return assistantFromID;
}

private static @NotNull String getFormat(String tools) {
return 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.
don't break the order of arguments, 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",
...
],
"rule": "which_rule_used_to_not_use_tool"
}
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);
}

public ChatMemory getChatMemory(String userId) {

if(chatMemories == null) {
Expand Down Expand Up @@ -426,7 +453,7 @@ public Assistant createAssistant(List<Document> documents, String modelName, Str
}

// Response Based on Provided URL
public Assistant urlAssistant(List<String> url, String model, String userId) {
public Assistant urlAssistant(List<String> url, String model, String userId, boolean isTooled) {

List<Document> documents = new ArrayList<>();

Expand All @@ -452,6 +479,10 @@ public Assistant urlAssistant(List<String> url, String model, String userId) {
return null;
}

if(isTooled) {
return getTooledAssistant(model, userId, documents);
}

return OllamaManager.getInstance().createAssistant(documents, model, null, userId);

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,13 @@ public List<String> getTopSearchResults(String query, int amount, boolean imageO
}

public void addForbiddenUrl(String url, String reason) {
forbiddenUrls.add(url);
Main.LOGGER.info("Added " + url + " to the forbidden list" + (reason != null ? " because " + reason : ""));
if(!forbiddenUrls.contains(url)) {
forbiddenUrls.add(url);
Main.LOGGER.warn("Added {} to the forbidden list: {}", url, reason);
}
else {
Main.LOGGER.warn("{} was found in the forbidden list", url);
}
}

public boolean isSearXNGEnabled() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/me/ailama/handler/models/Tool.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
public class Tool {

public boolean tooled;
public boolean rawResponse;
public String name;

public HashMap<String, Object> arguments;


public String[] response;
}
Loading

0 comments on commit 5e66cd1

Please sign in to comment.