From cbf65fef4819c8a6bf41b99248e09387fd387445 Mon Sep 17 00:00:00 2001 From: amithkoujalgi Date: Mon, 13 Oct 2025 14:34:03 +0530 Subject: [PATCH] Add YAML support for Agent configuration and enhance Agent class to load tools from YAML. Introduce custom prompt functionality and refactor constructor to accept additional parameters. Update SampleAgent to demonstrate YAML loading. --- pom.xml | 6 + .../java/io/github/ollama4j/agent/Agent.java | 118 +++++- .../io/github/ollama4j/agent/SampleAgent.java | 355 ++++++++++-------- .../java/io/github/ollama4j/tools/Tools.java | 53 +++ src/main/resources/agent.yaml | 45 +++ 5 files changed, 409 insertions(+), 168 deletions(-) create mode 100644 src/main/resources/agent.yaml 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