diff --git a/pom.xml b/pom.xml index f19ef4d..d05cf48 100644 --- a/pom.xml +++ b/pom.xml @@ -259,6 +259,12 @@ jackson-databind 2.20.0 + + tools.jackson.dataformat + jackson-dataformat-yaml + 3.0.0 + + com.fasterxml.jackson.datatype jackson-datatype-jsr310 diff --git a/src/main/java/io/github/ollama4j/agent/Agent.java b/src/main/java/io/github/ollama4j/agent/Agent.java index 942968c..7b3f665 100644 --- a/src/main/java/io/github/ollama4j/agent/Agent.java +++ b/src/main/java/io/github/ollama4j/agent/Agent.java @@ -12,10 +12,14 @@ import io.github.ollama4j.Ollama; import io.github.ollama4j.exceptions.OllamaException; import io.github.ollama4j.impl.ConsoleOutputGenerateTokenHandler; import io.github.ollama4j.models.chat.*; +import io.github.ollama4j.tools.ToolFunction; import io.github.ollama4j.tools.Tools; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Scanner; +import lombok.*; +import tools.jackson.dataformat.yaml.YAMLMapper; public class Agent { private final String name; @@ -23,13 +27,75 @@ public class Agent { private final Ollama ollamaClient; private final String model; private final List chatHistory; + private final String customPrompt; - public Agent(String name, Ollama ollamaClient, String model, List tools) { + public Agent( + String name, + Ollama ollamaClient, + String model, + String customPrompt, + List tools) { this.name = name; this.ollamaClient = ollamaClient; this.chatHistory = new ArrayList<>(); this.tools = tools; this.model = model; + this.customPrompt = customPrompt; + } + + public static Agent fromYaml(String agentYaml) { + try { + YAMLMapper mapper = new YAMLMapper(); + InputStream input = Agent.class.getClassLoader().getResourceAsStream(agentYaml); + if (input == null) { + java.nio.file.Path filePath = java.nio.file.Paths.get(agentYaml); + if (java.nio.file.Files.exists(filePath)) { + input = java.nio.file.Files.newInputStream(filePath); + } else { + throw new RuntimeException( + agentYaml + " not found in classpath or file system"); + } + } + AgentSpec agentSpec = mapper.readValue(input, AgentSpec.class); + List tools = agentSpec.getTools(); + for (AgentToolSpec tool : tools) { + String fqcn = tool.getToolFunctionFQCN(); + if (fqcn != null && !fqcn.isEmpty()) { + try { + Class clazz = Class.forName(fqcn); + Object instance = clazz.getDeclaredConstructor().newInstance(); + if (instance instanceof ToolFunction) { + tool.setToolFunctionInstance((ToolFunction) instance); + } else { + throw new RuntimeException( + "Class does not implement ToolFunction: " + fqcn); + } + } catch (Exception e) { + throw new RuntimeException( + "Failed to instantiate tool function: " + fqcn, e); + } + } + } + List agentTools = new ArrayList<>(); + for (AgentToolSpec a : tools) { + Tools.Tool t = new Tools.Tool(); + t.setToolFunction(a.getToolFunctionInstance()); + Tools.ToolSpec ts = new Tools.ToolSpec(); + ts.setName(a.getName()); + ts.setDescription(a.getDescription()); + ts.setParameters(a.getParameters()); + t.setToolSpec(ts); + agentTools.add(t); + } + return new Agent( + agentSpec.getName(), + new Ollama(agentSpec.getHost()), + agentSpec.getModel(), + agentSpec.getCustomPrompt(), + agentTools); + } catch (Exception e) { + throw new RuntimeException("Failed to load agent from YAML", e); + } } public String think(String userInput) throws OllamaException { @@ -48,17 +114,34 @@ public class Agent { } if (chatHistory.isEmpty()) { chatHistory.add( + // new OllamaChatMessage( + // OllamaChatMessageRole.SYSTEM, + // "You are a helpful assistant named " + // + name + // + ". You only perform tasks using tools + // available for you. You" + // + " respond very precisely and you don't + // overthink or be too" + // + " creative. Do not ever reveal the tool + // specification in" + // + " terms of code or JSON or in a way that + // a software engineer" + // + " sees it. Just be careful with your + // responses and respond" + // + " like a human. Note that you only + // execute tools provided to" + // + " you. Following are the tools that you + // have access to and" + // + " you can perform right actions using + // right tools." + // + availableToolsDescription)); new OllamaChatMessage( OllamaChatMessageRole.SYSTEM, "You are a helpful assistant named " + name - + ". You only perform tasks using tools available for you. You" - + " respond very precisely and you don't overthink or be too" - + " creative. Do not ever reveal the tool specification in" - + " terms of code or JSON or in a way that a software engineer" - + " sees it. Just be careful with your responses and respond" - + " like a human. Note that you only execute tools provided to" - + " you. Following are the tools that you have access to and" + + ". You only perform tasks using tools available for you. " + + customPrompt + + ". Following are the tools that you have access to and" + " you can perform right actions using right tools." + availableToolsDescription)); } @@ -90,4 +173,23 @@ public class Agent { String response = this.think(input); } } + + @Data + public static class AgentSpec { + private String name; + private String description; + private List tools; + private Tools.Parameters parameters; + private String host; + private String model; + private String customPrompt; + } + + @Data + @Setter + @Getter + private static class AgentToolSpec extends Tools.ToolSpec { + private String toolFunctionFQCN = null; + private ToolFunction toolFunctionInstance = null; + } } diff --git a/src/main/java/io/github/ollama4j/agent/SampleAgent.java b/src/main/java/io/github/ollama4j/agent/SampleAgent.java index 3f55273..43652e4 100644 --- a/src/main/java/io/github/ollama4j/agent/SampleAgent.java +++ b/src/main/java/io/github/ollama4j/agent/SampleAgent.java @@ -8,181 +8,218 @@ */ package io.github.ollama4j.agent; -import io.github.ollama4j.Ollama; import io.github.ollama4j.exceptions.OllamaException; import io.github.ollama4j.tools.ToolFunction; -import io.github.ollama4j.tools.Tools; -import java.util.ArrayList; -import java.util.List; import java.util.Map; -/** - * Example usage of the Agent API with some dummy tool functions. - */ +/** Example usage of the Agent API with some dummy tool functions. */ public class SampleAgent { public static void main(String[] args) throws OllamaException { - Ollama ollama = new Ollama("http://192.168.29.224:11434"); - ollama.setRequestTimeoutSeconds(120); - String model = "mistral:7b"; - ollama.pullModel(model); - - List tools = new ArrayList<>(); - // Weather tool - tools.add( - Tools.Tool.builder() - .toolSpec( - Tools.ToolSpec.builder() - .name("weather-tool") - .description( - "Gets the current weather for a given location and" - + " day.") - .parameters( - Tools.Parameters.of( - Map.of( - "location", - Tools.Property.builder() - .type("string") - .description( - "The location for" - + " which to" - + " get the" - + " weather.") - .required(true) - .build(), - "day", - Tools.Property.builder() - .type("string") - .description( - "The day of the" - + " week for" - + " which to" - + " get the" - + " weather.") - .required(true) - .build()))) - .build()) - .toolFunction(new WeatherToolFunction()) - .build()); - - // Calculator tool - tools.add( - Tools.Tool.builder() - .toolSpec( - Tools.ToolSpec.builder() - .name("calculator-tool") - .description( - "Performs a simple arithmetic operation between two" - + " numbers.") - .parameters( - Tools.Parameters.of( - Map.of( - "operation", - Tools.Property.builder() - .type("string") - .description( - "The arithmetic" - + " operation" - + " to perform." - + " One of:" - + " add," - + " subtract," - + " multiply," - + " divide.") - .required(true) - .build(), - "a", - Tools.Property.builder() - .type("number") - .description( - "The first" - + " operand.") - .required(true) - .build(), - "b", - Tools.Property.builder() - .type("number") - .description( - "The second" - + " operand.") - .required(true) - .build()))) - .build()) - .toolFunction(new CalculatorToolFunction()) - .build()); - - // Hotel Booking tool (dummy) - tools.add( - Tools.Tool.builder() - .toolSpec( - Tools.ToolSpec.builder() - .name("hotel-booking-tool") - .description( - "Books a hotel room in a specified city for given" - + " dates and number of guests.") - .parameters( - Tools.Parameters.of( - Map.of( - "city", - Tools.Property.builder() - .type("string") - .description( - "The city where the" - + " hotel will" - + " be booked.") - .required(true) - .build(), - "checkin_date", - Tools.Property.builder() - .type("string") - .description( - "Check-in date" - + " (e.g." - + " 2025-08-10).") - .required(true) - .build(), - "checkout_date", - Tools.Property.builder() - .type("string") - .description( - "Check-out date" - + " (e.g." - + " 2025-08-12).") - .required(true) - .build(), - "guests", - Tools.Property.builder() - .type("number") - .description( - "Number of guests" - + " for the" - + " booking.") - .required(true) - .build()))) - .build()) - .toolFunction(new HotelBookingToolFunction()) - .build()); - - Agent agent = new Agent("Nimma Mirta", ollama, model, tools); + // Ollama ollama = new Ollama("http://192.168.29.224:11434"); + // ollama.setRequestTimeoutSeconds(120); + // String model = "mistral:7b"; + // ollama.pullModel(model); + // List tools = new ArrayList<>(); + // // Weather tool + // tools.add( + // Tools.Tool.builder() + // .toolSpec( + // Tools.ToolSpec.builder() + // .name("weather-tool") + // .description( + // "Gets the current weather for a given + // location and" + // + " day.") + // .parameters( + // Tools.Parameters.of( + // Map.of( + // "location", + // Tools.Property.builder() + // .type("string") + // .description( + // "The + // location for" + // + " + // which to" + // + " + // get the" + // + " + // weather.") + // .required(true) + // .build(), + // "day", + // Tools.Property.builder() + // .type("string") + // .description( + // "The day + // of the" + // + " + // week for" + // + " + // which to" + // + " + // get the" + // + " + // weather.") + // .required(true) + // .build()))) + // .build()) + // .toolFunction(new WeatherToolFunction()) + // .build()); + // + // // Calculator tool + // tools.add( + // Tools.Tool.builder() + // .toolSpec( + // Tools.ToolSpec.builder() + // .name("calculator-tool") + // .description( + // "Performs a simple arithmetic operation + // between two" + // + " numbers.") + // .parameters( + // Tools.Parameters.of( + // Map.of( + // "operation", + // Tools.Property.builder() + // .type("string") + // .description( + // "The + // arithmetic" + // + " + // operation" + // + " to + // perform." + // + " + // One of:" + // + " + // add," + // + " + // subtract," + // + " + // multiply," + // + " + // divide.") + // .required(true) + // .build(), + // "a", + // Tools.Property.builder() + // .type("number") + // .description( + // "The + // first" + // + " + // operand.") + // .required(true) + // .build(), + // "b", + // Tools.Property.builder() + // .type("number") + // .description( + // "The + // second" + // + " + // operand.") + // .required(true) + // .build()))) + // .build()) + // .toolFunction(new CalculatorToolFunction()) + // .build()); + // + // // Hotel Booking tool (dummy) + // tools.add( + // Tools.Tool.builder() + // .toolSpec( + // Tools.ToolSpec.builder() + // .name("hotel-booking-tool") + // .description( + // "Books a hotel room in a specified city + // for given" + // + " dates and number of guests.") + // .parameters( + // Tools.Parameters.of( + // Map.of( + // "city", + // Tools.Property.builder() + // .type("string") + // .description( + // "The city + // where the" + // + " + // hotel will" + // + " be + // booked.") + // .required(true) + // .build(), + // "checkin_date", + // Tools.Property.builder() + // .type("string") + // .description( + // "Hotel + // check-in date" + // + " + // (e.g." + // + " + // 2025-08-10).") + // .required(true) + // .build(), + // "checkout_date", + // Tools.Property.builder() + // .type("string") + // .description( + // + // "HotelCheck-out date" + // + " + // (e.g." + // + " + // 2025-08-12).") + // .required(true) + // .build(), + // "guests", + // Tools.Property.builder() + // .type("number") + // .description( + // "Number of + // guests" + // + " + // for the" + // + " + // booking.") + // .required(true) + // .build()))) + // .build()) + // .toolFunction(new HotelBookingToolFunction()) + // .build()); + // + // Map functionMap = Map.of( + // "weather-tool", new WeatherToolFunction(), + // "calculator-tool", new CalculatorToolFunction() + // ); + // List tools = + // Tools.fromYAMLFile("/Users/amithkoujalgi/Downloads/tools.yaml", functionMap); + // Agent agent = new Agent("Nimma Mirta", ollama, model, tools); + Agent agent = Agent.fromYaml("agent.yaml"); agent.runInteractive(); } } -/** - * ToolFunction implementation that returns a dummy weekly weather forecast. - */ +/** ToolFunction implementation that returns a dummy weekly weather forecast. */ class WeatherToolFunction implements ToolFunction { @Override public Object apply(Map arguments) { String response = - "Monday: Pleasant, Tuesday: Sunny, Wednesday: Windy, Thursday: Cloudy, Friday:" - + " Rainy, Saturday: Heavy rains, Sunday: Clear"; + "Monday: Pleasant." + + "Tuesday: Sunny." + + "Wednesday: Windy." + + "Thursday: Cloudy." + + "Friday: Rainy." + + "Saturday: Heavy rains." + + "Sunday: Clear."; return response; } } -/** - * ToolFunction implementation for basic arithmetic calculations. - */ +/** ToolFunction implementation for basic arithmetic calculations. */ class CalculatorToolFunction implements ToolFunction { @Override public Object apply(Map arguments) { @@ -213,9 +250,7 @@ class CalculatorToolFunction implements ToolFunction { } } -/** - * ToolFunction implementation simulating a hotel booking. - */ +/** ToolFunction implementation simulating a hotel booking. */ class HotelBookingToolFunction implements ToolFunction { @Override public Object apply(Map arguments) { diff --git a/src/main/java/io/github/ollama4j/tools/Tools.java b/src/main/java/io/github/ollama4j/tools/Tools.java index 79fa8e6..b1b4795 100644 --- a/src/main/java/io/github/ollama4j/tools/Tools.java +++ b/src/main/java/io/github/ollama4j/tools/Tools.java @@ -11,7 +11,9 @@ package io.github.ollama4j.tools; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -19,6 +21,8 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import tools.jackson.core.type.TypeReference; +import tools.jackson.dataformat.yaml.YAMLMapper; public class Tools { private Tools() {} @@ -116,4 +120,53 @@ public class Tools { @JsonIgnore private boolean required; } + + public static List fromJSONFile(String filePath, Map functionMap) { + try { + ObjectMapper mapper = new ObjectMapper(); + List> rawTools = + mapper.readValue( + new File(filePath), + new com.fasterxml.jackson.core.type.TypeReference<>() {}); + + List tools = new ArrayList<>(); + + for (Map rawTool : rawTools) { + String json = mapper.writeValueAsString(rawTool); + Tool tool = mapper.readValue(json, Tool.class); + String toolName = tool.getToolSpec().getName(); + for (Map.Entry toolFunctionEntry : functionMap.entrySet()) { + if (toolFunctionEntry.getKey().equals(toolName)) { + tool.setToolFunction(toolFunctionEntry.getValue()); + } + } + tools.add(tool); + } + return tools; + } catch (Exception e) { + throw new RuntimeException("Failed to load tools from file: " + filePath, e); + } + } + + public static List fromYAMLFile(String filePath, Map functionMap) { + try { + YAMLMapper mapper = new YAMLMapper(); + List> rawTools = + mapper.readValue(new File(filePath), new TypeReference<>() {}); + List tools = new ArrayList<>(); + for (Map rawTool : rawTools) { + String yaml = mapper.writeValueAsString(rawTool); + Tool tool = mapper.readValue(yaml, Tool.class); + String toolName = tool.getToolSpec().getName(); + ToolFunction function = functionMap.get(toolName); + if (function != null) { + tool.setToolFunction(function); + } + tools.add(tool); + } + return tools; + } catch (Exception e) { + throw new RuntimeException("Failed to load tools from YAML file: " + filePath, e); + } + } } diff --git a/src/main/resources/agent.yaml b/src/main/resources/agent.yaml new file mode 100644 index 0000000..380e8b2 --- /dev/null +++ b/src/main/resources/agent.yaml @@ -0,0 +1,45 @@ +name: Nimma Mitra +host: http://192.168.29.224:11434 +model: mistral:7b +customPrompt: > + Only use tools and do not use your creativity. + Do not ever tell me to call the tool or how to use the tool or command myself. + You do that for me. That is why you exist. + You call tool on my behalf and give me a response from the tool. +tools: + - name: weather-tool + toolFunctionFQCN: io.github.ollama4j.agent.WeatherToolFunction + description: Gets the current weather for a given location and day. + parameters: + properties: + location: + type: string + description: The location for which to get the weather. + required: true + day: + type: string + description: The day of the week for which to get the weather. + required: true + + - name: calculator-tool + toolFunctionFQCN: io.github.ollama4j.agent.CalculatorToolFunction + description: Performs a simple arithmetic operation between two numbers. + parameters: + properties: + operation: + type: string + description: The arithmetic operation to perform. + enum: + - add + - subtract + - multiply + - divide + required: true + a: + type: number + description: The first operand. + required: true + b: + type: number + description: The second operand. + required: true