From da6d20d118c79eb07cf60497dc3c683b12177e48 Mon Sep 17 00:00:00 2001 From: amithkoujalgi Date: Fri, 10 Oct 2025 23:56:31 +0530 Subject: [PATCH 1/9] Add default target to Makefile, enhance Ollama class to use tools, and introduce Agent and SampleAgent classes for interactive tool usage. Update Javadoc generation message and improve error handling in endpoint callers. --- Makefile | 6 +- src/main/java/io/github/ollama4j/Ollama.java | 3 +- .../java/io/github/ollama4j/agent/Agent.java | 93 ++++++++ .../io/github/ollama4j/agent/SampleAgent.java | 215 ++++++++++++++++++ .../request/OllamaChatEndpointCaller.java | 1 - .../request/OllamaGenerateEndpointCaller.java | 27 ++- 6 files changed, 330 insertions(+), 15 deletions(-) create mode 100644 src/main/java/io/github/ollama4j/agent/Agent.java create mode 100644 src/main/java/io/github/ollama4j/agent/SampleAgent.java diff --git a/Makefile b/Makefile index b134b29..54cfdb1 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +# Default target +.PHONY: all +all: dev build + dev: @echo "Setting up dev environment..." @command -v pre-commit >/dev/null 2>&1 || { echo "Error: pre-commit is not installed. Please install it first."; exit 1; } @@ -43,7 +47,7 @@ doxygen: @doxygen Doxyfile javadoc: - @echo "\033[0;34mGenerating Javadocs into '$(javadocfolder)'...\033[0m" + @echo "\033[0;34mGenerating Javadocs...\033[0m" @mvn clean javadoc:javadoc @if [ -f "target/reports/apidocs/index.html" ]; then \ echo "\033[0;32mJavadocs generated in target/reports/apidocs/index.html\033[0m"; \ diff --git a/src/main/java/io/github/ollama4j/Ollama.java b/src/main/java/io/github/ollama4j/Ollama.java index 60d7c8f..e79f53d 100644 --- a/src/main/java/io/github/ollama4j/Ollama.java +++ b/src/main/java/io/github/ollama4j/Ollama.java @@ -805,6 +805,7 @@ public class Ollama { chatRequest.setMessages(msgs); msgs.add(ocm); OllamaChatTokenHandler hdlr = null; + chatRequest.setUseTools(true); chatRequest.setTools(request.getTools()); if (streamObserver != null) { chatRequest.setStream(true); @@ -881,7 +882,7 @@ public class Ollama { // only add tools if tools flag is set if (request.isUseTools()) { // add all registered tools to request - request.setTools(toolRegistry.getRegisteredTools()); + request.getTools().addAll(toolRegistry.getRegisteredTools()); } if (tokenHandler != null) { diff --git a/src/main/java/io/github/ollama4j/agent/Agent.java b/src/main/java/io/github/ollama4j/agent/Agent.java new file mode 100644 index 0000000..942968c --- /dev/null +++ b/src/main/java/io/github/ollama4j/agent/Agent.java @@ -0,0 +1,93 @@ +/* + * Ollama4j - Java library for interacting with Ollama server. + * Copyright (c) 2025 Amith Koujalgi and contributors. + * + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * +*/ +package io.github.ollama4j.agent; + +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.Tools; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class Agent { + private final String name; + private final List tools; + private final Ollama ollamaClient; + private final String model; + private final List chatHistory; + + public Agent(String name, Ollama ollamaClient, String model, List tools) { + this.name = name; + this.ollamaClient = ollamaClient; + this.chatHistory = new ArrayList<>(); + this.tools = tools; + this.model = model; + } + + public String think(String userInput) throws OllamaException { + StringBuilder availableToolsDescription = new StringBuilder(); + if (!tools.isEmpty()) { + for (Tools.Tool t : tools) { + String toolName = t.getToolSpec().getName(); + String toolDescription = t.getToolSpec().getDescription(); + availableToolsDescription.append( + "\nTool name: '" + + toolName + + "'. Tool Description: '" + + toolDescription + + "'.\n"); + } + } + 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)); + } + OllamaChatRequest request = + OllamaChatRequest.builder() + .withTools(tools) + .withUseTools(true) + .withModel(model) + .withMessages(chatHistory) + .withMessage(OllamaChatMessageRole.USER, userInput) + .build(); + request.withMessage(OllamaChatMessageRole.USER, userInput); + OllamaChatStreamObserver chatTokenHandler = + new OllamaChatStreamObserver( + new ConsoleOutputGenerateTokenHandler(), + new ConsoleOutputGenerateTokenHandler()); + OllamaChatResult response = ollamaClient.chat(request, chatTokenHandler); + chatHistory.clear(); + chatHistory.addAll(response.getChatHistory()); + return response.getResponseModel().getMessage().getResponse(); + } + + public void runInteractive() throws OllamaException { + Scanner sc = new Scanner(System.in); + while (true) { + System.out.print("\nYou: "); + String input = sc.nextLine(); + if ("exit".equalsIgnoreCase(input)) break; + String response = this.think(input); + } + } +} diff --git a/src/main/java/io/github/ollama4j/agent/SampleAgent.java b/src/main/java/io/github/ollama4j/agent/SampleAgent.java new file mode 100644 index 0000000..7e98ecc --- /dev/null +++ b/src/main/java/io/github/ollama4j/agent/SampleAgent.java @@ -0,0 +1,215 @@ +/* + * Ollama4j - Java library for interacting with Ollama server. + * Copyright (c) 2025 Amith Koujalgi and contributors. + * + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * +*/ +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; + +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 current weather for a given location and a" + + " given day") + .parameters( + Tools.Parameters.of( + Map.of( + "location", + Tools.Property.builder() + .type("string") + .description( + "The location to" + + " get the" + + " weather" + + " for.") + .required(true) + .build(), + "day", + Tools.Property.builder() + .type("string") + .description( + "The day of the" + + " week to get" + + " the weather" + + " for.") + .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( + "Arithmetic" + + " operation" + + " to perform:" + + " 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( + "Helps with booking 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 you" + + " want to" + + " book the" + + " hotel.") + .required(true) + .build(), + "checkin_date", + Tools.Property.builder() + .type("string") + .description( + "Check-in date.") + .required(true) + .build(), + "checkout_date", + Tools.Property.builder() + .type("string") + .description( + "Check-out date.") + .required(true) + .build(), + "guests", + Tools.Property.builder() + .type("number") + .description( + "Number of guests.") + .required(true) + .build()))) + .build()) + .toolFunction(new HotelBookingToolFunction()) + .build()); + + Agent agent = new Agent("Nimma Mirta", ollama, model, tools); + agent.runInteractive(); + } +} + +/** ToolFunction implementation for diff checking. */ +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"; + return response; + } +} + +/** ToolFunction implementation for simple calculations. */ +class CalculatorToolFunction implements ToolFunction { + @Override + public Object apply(Map arguments) { + String operation = (String) arguments.get("operation"); + double a = ((Number) arguments.get("a")).doubleValue(); + double b = ((Number) arguments.get("b")).doubleValue(); + double result; + switch (operation.toLowerCase()) { + case "add": + result = a + b; + break; + case "subtract": + result = a - b; + break; + case "multiply": + result = a * b; + break; + case "divide": + if (b == 0) { + return "Cannot divide by zero."; + } + result = a / b; + break; + default: + return "Unknown operation: " + operation; + } + return "Result: " + result; + } +} + +/** ToolFunction implementation for a dummy hotel booking agent. */ +class HotelBookingToolFunction implements ToolFunction { + @Override + public Object apply(Map arguments) { + String city = (String) arguments.get("city"); + String checkin = (String) arguments.get("checkin_date"); + String checkout = (String) arguments.get("checkout_date"); + int guests = ((Number) arguments.get("guests")).intValue(); + + // Dummy booking confirmation logic + return String.format( + "Booking confirmed! %d guest(s) in %s from %s to %s. (Confirmation #DUMMY1234)", + guests, city, checkin, checkout); + } +} diff --git a/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java b/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java index 6e86b0d..fef35e3 100644 --- a/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java +++ b/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java @@ -141,7 +141,6 @@ public class OllamaChatEndpointCaller extends OllamaEndpointCaller { responseBuffer); if (statusCode != 200) { LOG.error("Status code: {}", statusCode); - System.out.println(responseBuffer); throw new OllamaException(responseBuffer.toString()); } if (wantedToolsForStream != null && ollamaChatResponseModel != null) { diff --git a/src/main/java/io/github/ollama4j/models/request/OllamaGenerateEndpointCaller.java b/src/main/java/io/github/ollama4j/models/request/OllamaGenerateEndpointCaller.java index fcd16fc..387a1be 100644 --- a/src/main/java/io/github/ollama4j/models/request/OllamaGenerateEndpointCaller.java +++ b/src/main/java/io/github/ollama4j/models/request/OllamaGenerateEndpointCaller.java @@ -136,18 +136,21 @@ public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller { thinkingBuffer.toString(), endTime - startTime, statusCode); - ollamaResult.setModel(ollamaGenerateResponseModel.getModel()); - ollamaResult.setCreatedAt(ollamaGenerateResponseModel.getCreatedAt()); - ollamaResult.setDone(ollamaGenerateResponseModel.isDone()); - ollamaResult.setDoneReason(ollamaGenerateResponseModel.getDoneReason()); - ollamaResult.setContext(ollamaGenerateResponseModel.getContext()); - ollamaResult.setTotalDuration(ollamaGenerateResponseModel.getTotalDuration()); - ollamaResult.setLoadDuration(ollamaGenerateResponseModel.getLoadDuration()); - ollamaResult.setPromptEvalCount(ollamaGenerateResponseModel.getPromptEvalCount()); - ollamaResult.setPromptEvalDuration(ollamaGenerateResponseModel.getPromptEvalDuration()); - ollamaResult.setEvalCount(ollamaGenerateResponseModel.getEvalCount()); - ollamaResult.setEvalDuration(ollamaGenerateResponseModel.getEvalDuration()); - + if (ollamaGenerateResponseModel != null) { + ollamaResult.setModel(ollamaGenerateResponseModel.getModel()); + ollamaResult.setCreatedAt(ollamaGenerateResponseModel.getCreatedAt()); + ollamaResult.setDone(ollamaGenerateResponseModel.isDone()); + ollamaResult.setDoneReason(ollamaGenerateResponseModel.getDoneReason()); + ollamaResult.setContext(ollamaGenerateResponseModel.getContext()); + ollamaResult.setTotalDuration(ollamaGenerateResponseModel.getTotalDuration()); + ollamaResult.setLoadDuration(ollamaGenerateResponseModel.getLoadDuration()); + ollamaResult.setPromptEvalCount(ollamaGenerateResponseModel.getPromptEvalCount()); + ollamaResult.setPromptEvalDuration( + ollamaGenerateResponseModel.getPromptEvalDuration()); + ollamaResult.setEvalCount(ollamaGenerateResponseModel.getEvalCount()); + ollamaResult.setEvalDuration(ollamaGenerateResponseModel.getEvalDuration()); + } + LOG.debug("Model plain response: {}", ollamaGenerateResponseModel); LOG.debug("Model response: {}", ollamaResult); return ollamaResult; } From 6df57c4a23939a963d9ff39b1e997b7a19fde5b5 Mon Sep 17 00:00:00 2001 From: amithkoujalgi Date: Sat, 11 Oct 2025 00:00:42 +0530 Subject: [PATCH 2/9] Enhance SampleAgent with improved Javadoc comments for tool specifications and descriptions, clarifying usage and parameters for weather, calculator, and hotel booking tools. --- .../io/github/ollama4j/agent/SampleAgent.java | 73 ++++++++++++------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/src/main/java/io/github/ollama4j/agent/SampleAgent.java b/src/main/java/io/github/ollama4j/agent/SampleAgent.java index 7e98ecc..3f55273 100644 --- a/src/main/java/io/github/ollama4j/agent/SampleAgent.java +++ b/src/main/java/io/github/ollama4j/agent/SampleAgent.java @@ -16,6 +16,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +/** + * 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"); @@ -31,8 +34,8 @@ public class SampleAgent { Tools.ToolSpec.builder() .name("weather-tool") .description( - "Gets current weather for a given location and a" - + " given day") + "Gets the current weather for a given location and" + + " day.") .parameters( Tools.Parameters.of( Map.of( @@ -40,10 +43,10 @@ public class SampleAgent { Tools.Property.builder() .type("string") .description( - "The location to" - + " get the" - + " weather" - + " for.") + "The location for" + + " which to" + + " get the" + + " weather.") .required(true) .build(), "day", @@ -51,9 +54,10 @@ public class SampleAgent { .type("string") .description( "The day of the" - + " week to get" - + " the weather" - + " for.") + + " week for" + + " which to" + + " get the" + + " weather.") .required(true) .build()))) .build()) @@ -68,7 +72,7 @@ public class SampleAgent { .name("calculator-tool") .description( "Performs a simple arithmetic operation between two" - + " numbers") + + " numbers.") .parameters( Tools.Parameters.of( Map.of( @@ -76,20 +80,22 @@ public class SampleAgent { Tools.Property.builder() .type("string") .description( - "Arithmetic" + "The arithmetic" + " operation" - + " to perform:" + + " to perform." + + " One of:" + " add," + " subtract," + " multiply," - + " divide") + + " divide.") .required(true) .build(), "a", Tools.Property.builder() .type("number") .description( - "The first operand") + "The first" + + " operand.") .required(true) .build(), "b", @@ -97,7 +103,7 @@ public class SampleAgent { .type("number") .description( "The second" - + " operand") + + " operand.") .required(true) .build()))) .build()) @@ -111,8 +117,8 @@ public class SampleAgent { Tools.ToolSpec.builder() .name("hotel-booking-tool") .description( - "Helps with booking a hotel room in a specified" - + " city for given dates and number of guests.") + "Books a hotel room in a specified city for given" + + " dates and number of guests.") .parameters( Tools.Parameters.of( Map.of( @@ -120,31 +126,36 @@ public class SampleAgent { Tools.Property.builder() .type("string") .description( - "The city where you" - + " want to" - + " book the" - + " hotel.") + "The city where the" + + " hotel will" + + " be booked.") .required(true) .build(), "checkin_date", Tools.Property.builder() .type("string") .description( - "Check-in date.") + "Check-in date" + + " (e.g." + + " 2025-08-10).") .required(true) .build(), "checkout_date", Tools.Property.builder() .type("string") .description( - "Check-out date.") + "Check-out date" + + " (e.g." + + " 2025-08-12).") .required(true) .build(), "guests", Tools.Property.builder() .type("number") .description( - "Number of guests.") + "Number of guests" + + " for the" + + " booking.") .required(true) .build()))) .build()) @@ -156,18 +167,22 @@ public class SampleAgent { } } -/** ToolFunction implementation for diff checking. */ +/** + * 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:" + "Monday: Pleasant, Tuesday: Sunny, Wednesday: Windy, Thursday: Cloudy, Friday:" + " Rainy, Saturday: Heavy rains, Sunday: Clear"; return response; } } -/** ToolFunction implementation for simple calculations. */ +/** + * ToolFunction implementation for basic arithmetic calculations. + */ class CalculatorToolFunction implements ToolFunction { @Override public Object apply(Map arguments) { @@ -198,7 +213,9 @@ class CalculatorToolFunction implements ToolFunction { } } -/** ToolFunction implementation for a dummy hotel booking agent. */ +/** + * ToolFunction implementation simulating a hotel booking. + */ class HotelBookingToolFunction implements ToolFunction { @Override public Object apply(Map arguments) { From cbf65fef4819c8a6bf41b99248e09387fd387445 Mon Sep 17 00:00:00 2001 From: amithkoujalgi Date: Mon, 13 Oct 2025 14:34:03 +0530 Subject: [PATCH 3/9] 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 From bec634dd37f35820d1d009e7d00c2fe7a5c2feed Mon Sep 17 00:00:00 2001 From: amithkoujalgi Date: Sat, 18 Oct 2025 20:19:43 +0530 Subject: [PATCH 4/9] Refactor Agent class to include request timeout configuration and enhance interactive input display. Remove commented-out code for clarity. Update SampleAgent to utilize YAML configuration for agent instantiation. --- .../java/io/github/ollama4j/agent/Agent.java | 37 ++-- .../io/github/ollama4j/agent/SampleAgent.java | 183 ------------------ src/main/resources/agent.yaml | 1 + 3 files changed, 14 insertions(+), 207 deletions(-) diff --git a/src/main/java/io/github/ollama4j/agent/Agent.java b/src/main/java/io/github/ollama4j/agent/Agent.java index 7b3f665..f7d82be 100644 --- a/src/main/java/io/github/ollama4j/agent/Agent.java +++ b/src/main/java/io/github/ollama4j/agent/Agent.java @@ -87,9 +87,11 @@ public class Agent { t.setToolSpec(ts); agentTools.add(t); } + Ollama ollama = new Ollama(agentSpec.getHost()); + ollama.setRequestTimeoutSeconds(120); return new Agent( agentSpec.getName(), - new Ollama(agentSpec.getHost()), + ollama, agentSpec.getModel(), agentSpec.getCustomPrompt(), agentTools); @@ -114,27 +116,6 @@ 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 " @@ -167,7 +148,7 @@ public class Agent { public void runInteractive() throws OllamaException { Scanner sc = new Scanner(System.in); while (true) { - System.out.print("\nYou: "); + System.out.print("\n[You]: "); String input = sc.nextLine(); if ("exit".equalsIgnoreCase(input)) break; String response = this.think(input); @@ -179,10 +160,10 @@ public class Agent { private String name; private String description; private List tools; - private Tools.Parameters parameters; private String host; private String model; private String customPrompt; + private int requestTimeoutSeconds; } @Data @@ -192,4 +173,12 @@ public class Agent { private String toolFunctionFQCN = null; private ToolFunction toolFunctionInstance = null; } + + @Data + public class AgentToolParameter { + private String type; + private String description; + private boolean required; + private List _enum; // `enum` is a reserved keyword, so use _enum or similar + } } diff --git a/src/main/java/io/github/ollama4j/agent/SampleAgent.java b/src/main/java/io/github/ollama4j/agent/SampleAgent.java index 43652e4..bdce2a8 100644 --- a/src/main/java/io/github/ollama4j/agent/SampleAgent.java +++ b/src/main/java/io/github/ollama4j/agent/SampleAgent.java @@ -15,189 +15,6 @@ import java.util.Map; /** 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( - // "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(); } diff --git a/src/main/resources/agent.yaml b/src/main/resources/agent.yaml index 380e8b2..5d3bb51 100644 --- a/src/main/resources/agent.yaml +++ b/src/main/resources/agent.yaml @@ -1,6 +1,7 @@ name: Nimma Mitra host: http://192.168.29.224:11434 model: mistral:7b +requestTimeoutSeconds: 120 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. From 866c08f5904840cd5ed0fccf1dac7707737c8143 Mon Sep 17 00:00:00 2001 From: amithkoujalgi Date: Sun, 19 Oct 2025 11:22:16 +0530 Subject: [PATCH 5/9] Remove SampleAgent class and associated YAML configuration file, streamlining the project by eliminating example implementations and their dependencies. --- pom.xml | 9 +- .../java/io/github/ollama4j/agent/Agent.java | 175 ++++++++++++++---- .../io/github/ollama4j/agent/SampleAgent.java | 84 --------- .../java/io/github/ollama4j/tools/Tools.java | 6 +- src/main/resources/agent.yaml | 46 ----- 5 files changed, 147 insertions(+), 173 deletions(-) delete mode 100644 src/main/java/io/github/ollama4j/agent/SampleAgent.java delete mode 100644 src/main/resources/agent.yaml diff --git a/pom.xml b/pom.xml index d05cf48..e7c70b6 100644 --- a/pom.xml +++ b/pom.xml @@ -260,11 +260,10 @@ 2.20.0 - tools.jackson.dataformat + com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 3.0.0 + 2.20.0 - com.fasterxml.jackson.datatype jackson-datatype-jsr310 @@ -281,7 +280,6 @@ slf4j-api 2.0.17 - org.junit.jupiter junit-jupiter-api @@ -300,7 +298,6 @@ 20250517 test - org.testcontainers ollama @@ -313,14 +310,12 @@ 1.21.3 test - io.prometheus simpleclient 0.16.0 - com.google.guava guava diff --git a/src/main/java/io/github/ollama4j/agent/Agent.java b/src/main/java/io/github/ollama4j/agent/Agent.java index f7d82be..4e3ac57 100644 --- a/src/main/java/io/github/ollama4j/agent/Agent.java +++ b/src/main/java/io/github/ollama4j/agent/Agent.java @@ -8,6 +8,8 @@ */ package io.github.ollama4j.agent; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.github.ollama4j.Ollama; import io.github.ollama4j.exceptions.OllamaException; import io.github.ollama4j.impl.ConsoleOutputGenerateTokenHandler; @@ -19,16 +21,50 @@ import java.util.ArrayList; import java.util.List; import java.util.Scanner; import lombok.*; -import tools.jackson.dataformat.yaml.YAMLMapper; +/** + * The {@code Agent} class represents an AI assistant capable of interacting with the Ollama API + * server. + * + *

It supports the use of tools (interchangeable code components), persistent chat history, and + * interactive as well as pre-scripted chat sessions. + * + *

Usage

+ * + *
    + *
  • Instantiate an Agent via {@link #fromYaml(String)} for YAML-based configuration. + *
  • Handle conversation turns via {@link #think(String)}. + *
  • Use {@link #runInteractive()} for an interactive console-based session. + *
+ */ public class Agent { + /** The agent's display name */ private final String name; + + /** List of supported tools for this agent */ private final List tools; + + /** Ollama client instance for communication with the API */ private final Ollama ollamaClient; + + /** The model name used for chat completions */ private final String model; + + /** Persists chat message history across rounds */ private final List chatHistory; + + /** Optional custom system prompt for the agent */ private final String customPrompt; + /** + * Constructs a new Agent. + * + * @param name The agent's given name. + * @param ollamaClient The Ollama API client instance to use. + * @param model The model name to use for chat completion. + * @param customPrompt A custom prompt to prepend to all conversations (may be null). + * @param tools List of available tools for function calling. + */ public Agent( String name, Ollama ollamaClient, @@ -43,17 +79,29 @@ public class Agent { this.customPrompt = customPrompt; } - public static Agent fromYaml(String agentYaml) { + /** + * Loads and constructs an Agent from a YAML configuration file (classpath or filesystem). + * + *

The YAML should define the agent, the model, and the desired tool functions (using their + * fully qualified class names for auto-discovery). + * + * @param yamlPathOrResource Path or classpath resource name of the YAML file. + * @return New Agent instance loaded according to the YAML definition. + * @throws RuntimeException if the YAML cannot be read or agent cannot be constructed. + */ + public static Agent load(String yamlPathOrResource) { try { - YAMLMapper mapper = new YAMLMapper(); - InputStream input = Agent.class.getClassLoader().getResourceAsStream(agentYaml); + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + InputStream input = + Agent.class.getClassLoader().getResourceAsStream(yamlPathOrResource); if (input == null) { - java.nio.file.Path filePath = java.nio.file.Paths.get(agentYaml); + java.nio.file.Path filePath = java.nio.file.Paths.get(yamlPathOrResource); 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"); + yamlPathOrResource + " not found in classpath or file system"); } } AgentSpec agentSpec = mapper.readValue(input, AgentSpec.class); @@ -100,61 +148,105 @@ public class Agent { } } - public String think(String userInput) throws OllamaException { - StringBuilder availableToolsDescription = new StringBuilder(); - if (!tools.isEmpty()) { - for (Tools.Tool t : tools) { - String toolName = t.getToolSpec().getName(); - String toolDescription = t.getToolSpec().getDescription(); - availableToolsDescription.append( - "\nTool name: '" - + toolName - + "'. Tool Description: '" - + toolDescription - + "'.\n"); - } - } + /** + * Facilitates a single round of chat for the agent: + * + *

    + *
  • Builds/promotes the system prompt on the first turn if necessary + *
  • Adds the user's input to chat history + *
  • Submits the chat turn to the Ollama model (with tool/function support) + *
  • Updates internal chat history in accordance with the Ollama chat result + *
+ * + * @param userInput The user's message or question for the agent. + * @return The model's response as a string. + * @throws OllamaException If there is a problem with the Ollama API. + */ + public String interact(String userInput) throws OllamaException { + // Build a concise and readable description of available tools + String availableToolsDescription = + tools.isEmpty() + ? "" + : tools.stream() + .map( + t -> + String.format( + "- %s: %s", + t.getToolSpec().getName(), + t.getToolSpec().getDescription() != null + ? t.getToolSpec().getDescription() + : "No description")) + .reduce((a, b) -> a + "\n" + b) + .map(desc -> "\nYou have access to the following tools:\n" + desc) + .orElse(""); + + // Add system prompt if chatHistory is empty 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. " - + customPrompt - + ". Following are the tools that you have access to and" - + " you can perform right actions using right tools." - + availableToolsDescription)); + String systemPrompt = + String.format( + "You are a helpful AI assistant named %s. Your actions are limited to" + + " using the available tools. %s%s", + name, + (customPrompt != null ? customPrompt : ""), + availableToolsDescription); + chatHistory.add(new OllamaChatMessage(OllamaChatMessageRole.SYSTEM, systemPrompt)); } + + // Add the user input as a message before sending request + chatHistory.add(new OllamaChatMessage(OllamaChatMessageRole.USER, userInput)); + OllamaChatRequest request = OllamaChatRequest.builder() .withTools(tools) .withUseTools(true) .withModel(model) .withMessages(chatHistory) - .withMessage(OllamaChatMessageRole.USER, userInput) .build(); - request.withMessage(OllamaChatMessageRole.USER, userInput); + OllamaChatStreamObserver chatTokenHandler = new OllamaChatStreamObserver( new ConsoleOutputGenerateTokenHandler(), new ConsoleOutputGenerateTokenHandler()); OllamaChatResult response = ollamaClient.chat(request, chatTokenHandler); + + // Update chat history for continuity chatHistory.clear(); chatHistory.addAll(response.getChatHistory()); + return response.getResponseModel().getMessage().getResponse(); } + /** + * Launches an endless interactive console session with the agent, echoing user input and the + * agent's response using the provided chat model and tools. + * + *

Type {@code exit} to break the loop and terminate the session. + * + * @throws OllamaException if any errors occur talking to the Ollama API. + */ public void runInteractive() throws OllamaException { Scanner sc = new Scanner(System.in); while (true) { System.out.print("\n[You]: "); String input = sc.nextLine(); if ("exit".equalsIgnoreCase(input)) break; - String response = this.think(input); + this.interact(input); } } + /** + * Bean describing an agent as definable from YAML. + * + *

    + *
  • {@code name}: Agent display name + *
  • {@code description}: Freeform description + *
  • {@code tools}: List of tools/functions to enable + *
  • {@code host}: Target Ollama host address + *
  • {@code model}: Name of Ollama model to use + *
  • {@code customPrompt}: Agent's custom base prompt + *
  • {@code requestTimeoutSeconds}: Timeout for requests + *
+ */ @Data public static class AgentSpec { private String name; @@ -166,19 +258,36 @@ public class Agent { private int requestTimeoutSeconds; } + /** + * Subclass extension of {@link Tools.ToolSpec}, which allows associating a tool with a function + * implementation (via FQCN). + */ @Data @Setter @Getter private static class AgentToolSpec extends Tools.ToolSpec { + /** Fully qualified class name of the tool's {@link ToolFunction} implementation */ private String toolFunctionFQCN = null; + + /** Instance of the {@link ToolFunction} to invoke */ private ToolFunction toolFunctionInstance = null; } + /** Bean for describing a tool function parameter for use in agent YAML definitions. */ @Data public class AgentToolParameter { + /** The parameter's type (e.g., string, number, etc.) */ private String type; + + /** Description of the parameter */ private String description; + + /** Whether this parameter is required */ private boolean required; + + /** + * Enum values (if any) that this parameter may take; _enum used because 'enum' is reserved + */ private List _enum; // `enum` is a reserved keyword, so use _enum or similar } } diff --git a/src/main/java/io/github/ollama4j/agent/SampleAgent.java b/src/main/java/io/github/ollama4j/agent/SampleAgent.java deleted file mode 100644 index bdce2a8..0000000 --- a/src/main/java/io/github/ollama4j/agent/SampleAgent.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Ollama4j - Java library for interacting with Ollama server. - * Copyright (c) 2025 Amith Koujalgi and contributors. - * - * Licensed under the MIT License (the "License"); - * you may not use this file except in compliance with the License. - * -*/ -package io.github.ollama4j.agent; - -import io.github.ollama4j.exceptions.OllamaException; -import io.github.ollama4j.tools.ToolFunction; -import java.util.Map; - -/** Example usage of the Agent API with some dummy tool functions. */ -public class SampleAgent { - public static void main(String[] args) throws OllamaException { - Agent agent = Agent.fromYaml("agent.yaml"); - agent.runInteractive(); - } -} - -/** 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."; - return response; - } -} - -/** ToolFunction implementation for basic arithmetic calculations. */ -class CalculatorToolFunction implements ToolFunction { - @Override - public Object apply(Map arguments) { - String operation = (String) arguments.get("operation"); - double a = ((Number) arguments.get("a")).doubleValue(); - double b = ((Number) arguments.get("b")).doubleValue(); - double result; - switch (operation.toLowerCase()) { - case "add": - result = a + b; - break; - case "subtract": - result = a - b; - break; - case "multiply": - result = a * b; - break; - case "divide": - if (b == 0) { - return "Cannot divide by zero."; - } - result = a / b; - break; - default: - return "Unknown operation: " + operation; - } - return "Result: " + result; - } -} - -/** ToolFunction implementation simulating a hotel booking. */ -class HotelBookingToolFunction implements ToolFunction { - @Override - public Object apply(Map arguments) { - String city = (String) arguments.get("city"); - String checkin = (String) arguments.get("checkin_date"); - String checkout = (String) arguments.get("checkout_date"); - int guests = ((Number) arguments.get("guests")).intValue(); - - // Dummy booking confirmation logic - return String.format( - "Booking confirmed! %d guest(s) in %s from %s to %s. (Confirmation #DUMMY1234)", - guests, city, checkin, checkout); - } -} diff --git a/src/main/java/io/github/ollama4j/tools/Tools.java b/src/main/java/io/github/ollama4j/tools/Tools.java index b1b4795..f7f1701 100644 --- a/src/main/java/io/github/ollama4j/tools/Tools.java +++ b/src/main/java/io/github/ollama4j/tools/Tools.java @@ -11,8 +11,10 @@ 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.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import java.io.File; import java.util.ArrayList; import java.util.List; @@ -21,8 +23,6 @@ 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() {} @@ -150,7 +150,7 @@ public class Tools { public static List fromYAMLFile(String filePath, Map functionMap) { try { - YAMLMapper mapper = new YAMLMapper(); + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); List> rawTools = mapper.readValue(new File(filePath), new TypeReference<>() {}); List tools = new ArrayList<>(); diff --git a/src/main/resources/agent.yaml b/src/main/resources/agent.yaml deleted file mode 100644 index 5d3bb51..0000000 --- a/src/main/resources/agent.yaml +++ /dev/null @@ -1,46 +0,0 @@ -name: Nimma Mitra -host: http://192.168.29.224:11434 -model: mistral:7b -requestTimeoutSeconds: 120 -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 From f0e5a9e1724cf3246100508c4d113ababeb12d20 Mon Sep 17 00:00:00 2001 From: Amith Koujalgi Date: Sun, 19 Oct 2025 14:03:10 +0530 Subject: [PATCH 6/9] Add documentation for the new Agent feature and update sidebar positions for Metrics and API categories. Adjust code examples in various API documentation to reflect correct paths and improve clarity. Enhance the Agent class with an equals and hash code method for better functionality. --- docs/docs/agent.md | 13 +++++++ docs/docs/apis-extras/_category_.json | 4 +-- docs/docs/apis-generate/_category_.json | 2 +- docs/docs/apis-generate/chat-with-tools.md | 4 +-- docs/docs/apis-generate/chat.md | 2 +- docs/docs/apis-generate/generate-thinking.md | 8 ++--- .../apis-generate/generate-with-images.md | 35 +++++++------------ .../docs/apis-generate/generate-with-tools.md | 12 +++---- .../apis-model-management/_category_.json | 2 +- .../apis-model-management/create-model.md | 14 ++++---- docs/docs/metrics.md | 2 +- .../java/io/github/ollama4j/agent/Agent.java | 1 + 12 files changed, 52 insertions(+), 47 deletions(-) create mode 100644 docs/docs/agent.md diff --git a/docs/docs/agent.md b/docs/docs/agent.md new file mode 100644 index 0000000..68963c0 --- /dev/null +++ b/docs/docs/agent.md @@ -0,0 +1,13 @@ +--- +sidebar_position: 4 + +title: Agent +--- + +import CodeEmbed from '@site/src/components/CodeEmbed'; + +# Agent + +:::warning[Note] +This is work in progress +::: \ No newline at end of file diff --git a/docs/docs/apis-extras/_category_.json b/docs/docs/apis-extras/_category_.json index 09fa3cc..e7dc41c 100644 --- a/docs/docs/apis-extras/_category_.json +++ b/docs/docs/apis-extras/_category_.json @@ -1,6 +1,6 @@ { - "label": "APIs - Extras", - "position": 4, + "label": "Extras", + "position": 5, "link": { "type": "generated-index", "description": "Details of APIs to handle bunch of extra stuff." diff --git a/docs/docs/apis-generate/_category_.json b/docs/docs/apis-generate/_category_.json index f7c2b23..f8e6802 100644 --- a/docs/docs/apis-generate/_category_.json +++ b/docs/docs/apis-generate/_category_.json @@ -1,5 +1,5 @@ { - "label": "APIs - Generate", + "label": "Generate", "position": 3, "link": { "type": "generated-index", diff --git a/docs/docs/apis-generate/chat-with-tools.md b/docs/docs/apis-generate/chat-with-tools.md index 31f91bd..e7859dd 100644 --- a/docs/docs/apis-generate/chat-with-tools.md +++ b/docs/docs/apis-generate/chat-with-tools.md @@ -66,11 +66,11 @@ To use a method as a tool within a chat call, follow these steps: Let's try an example. Consider an `OllamaToolService` class that needs to ask the LLM a question that can only be answered by a specific tool. This tool is implemented within a `GlobalConstantGenerator` class. Following is the code that exposes an annotated method as a tool: - + The annotated method can then be used as a tool in the chat session: - + Running the above would produce a response similar to: diff --git a/docs/docs/apis-generate/chat.md b/docs/docs/apis-generate/chat.md index af53342..a247582 100644 --- a/docs/docs/apis-generate/chat.md +++ b/docs/docs/apis-generate/chat.md @@ -63,7 +63,7 @@ You will get a response similar to: ### Using a simple Console Output Stream Handler - + ### With a Stream Handler to receive the tokens as they are generated diff --git a/docs/docs/apis-generate/generate-thinking.md b/docs/docs/apis-generate/generate-thinking.md index d38634d..2de37e6 100644 --- a/docs/docs/apis-generate/generate-thinking.md +++ b/docs/docs/apis-generate/generate-thinking.md @@ -19,11 +19,11 @@ You can use this feature to receive both the thinking and the response as separa You will get a response similar to: :::tip[Thinking Tokens] -User asks "Who are you?" It's a request for identity. As ChatGPT, we should explain that I'm an AI developed by OpenAI, etc. Provide friendly explanation. +USER ASKS "WHO ARE YOU?" IT'S A REQUEST FOR IDENTITY. AS CHATGPT, WE SHOULD EXPLAIN THAT I'M AN AI DEVELOPED BY OPENAI, ETC. PROVIDE FRIENDLY EXPLANATION. ::: :::tip[Response Tokens] -I’m ChatGPT, a large language model created by OpenAI. I’m designed to understand and generate natural‑language text, so I can answer questions, help with writing, explain concepts, brainstorm ideas, and chat about almost any topic. I don’t have a personal life or consciousness—I’m a tool that processes input and produces responses based on patterns in the data I was trained on. If you have any questions about how I work or what I can do, feel free to ask! +i’m chatgpt, a large language model created by openai. i’m designed to understand and generate natural‑language text, so i can answer questions, help with writing, explain concepts, brainstorm ideas, and chat about almost any topic. i don’t have a personal life or consciousness—i’m a tool that processes input and produces responses based on patterns in the data i was trained on. if you have any questions about how i work or what i can do, feel free to ask! ::: ### Generate response and receive the thinking and response tokens streamed @@ -34,7 +34,7 @@ You will get a response similar to: :::tip[Thinking Tokens] + You will get a response similar to: @@ -32,30 +31,22 @@ This image features a white boat with brown cushions, where a dog is sitting on be enjoying its time outdoors, perhaps on a lake. ::: -# Generate with Image URLs -This API lets you ask questions along with the image files to the LLMs. -This API corresponds to -the [completion](https://github.com/jmorganca/ollama/blob/main/docs/api.md#generate-a-completion) API. - -:::note - -Executing this on Ollama server running in CPU-mode will take longer to generate response. Hence, GPU-mode is -recommended. - -::: - -## Ask (Sync) - -Passing the link of this image the following code: +If you want the response to be streamed, you can use the following code: ![Img](https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg) - + You will get a response similar to: -:::tip[LLM Response] -This image features a white boat with brown cushions, where a dog is sitting on the back of the boat. The dog seems to -be enjoying its time outdoors, perhaps on a lake. +:::tip[Response Tokens] + ::: \ No newline at end of file diff --git a/docs/docs/apis-generate/generate-with-tools.md b/docs/docs/apis-generate/generate-with-tools.md index 291ccd5..236e832 100644 --- a/docs/docs/apis-generate/generate-with-tools.md +++ b/docs/docs/apis-generate/generate-with-tools.md @@ -36,19 +36,19 @@ We can create static functions as our tools. This function takes the arguments `location` and `fuelType` and performs an operation with these arguments and returns fuel price value. - + This function takes the argument `city` and performs an operation with the argument and returns the weather for a location. - + Another way to create our tools is by creating classes by extending `ToolFunction`. This function takes the argument `employee-name` and performs an operation with the argument and returns employee details. - + ### Define Tool Specifications @@ -57,21 +57,21 @@ Lets define a sample tool specification called **Fuel Price Tool** for getting t - Specify the function `name`, `description`, and `required` properties (`location` and `fuelType`). - Associate the `getCurrentFuelPrice` function you defined earlier. - + Lets also define a sample tool specification called **Weather Tool** for getting the current weather. - Specify the function `name`, `description`, and `required` property (`city`). - Associate the `getCurrentWeather` function you defined earlier. - + Lets also define a sample tool specification called **DBQueryFunction** for getting the employee details from database. - Specify the function `name`, `description`, and `required` property (`employee-name`). - Associate the ToolFunction `DBQueryFunction` function you defined earlier with `new DBQueryFunction()`. - + Now put it all together by registering the tools and prompting with tools. diff --git a/docs/docs/apis-model-management/_category_.json b/docs/docs/apis-model-management/_category_.json index 48f345c..7a88175 100644 --- a/docs/docs/apis-model-management/_category_.json +++ b/docs/docs/apis-model-management/_category_.json @@ -1,5 +1,5 @@ { - "label": "APIs - Manage Models", + "label": "Manage Models", "position": 2, "link": { "type": "generated-index", diff --git a/docs/docs/apis-model-management/create-model.md b/docs/docs/apis-model-management/create-model.md index 67b0de3..a8c0d7a 100644 --- a/docs/docs/apis-model-management/create-model.md +++ b/docs/docs/apis-model-management/create-model.md @@ -15,13 +15,13 @@ This API lets you create a custom model on the Ollama server. You would see these logs while the custom model is being created: ``` -{"status":"using existing layer sha256:fad2a06e4cc705c2fa8bec5477ddb00dc0c859ac184c34dcc5586663774161ca"} -{"status":"using existing layer sha256:41c2cf8c272f6fb0080a97cd9d9bd7d4604072b80a0b10e7d65ca26ef5000c0c"} -{"status":"using existing layer sha256:1da0581fd4ce92dcf5a66b1da737cf215d8dcf25aa1b98b44443aaf7173155f5"} -{"status":"creating new layer sha256:941b69ca7dc2a85c053c38d9e8029c9df6224e545060954fa97587f87c044a64"} -{"status":"using existing layer sha256:f02dd72bb2423204352eabc5637b44d79d17f109fdb510a7c51455892aa2d216"} -{"status":"writing manifest"} -{"status":"success"} +using existing layer sha256:fad2a06e4cc705c2fa8bec5477ddb00dc0c859ac184c34dcc5586663774161ca +using existing layer sha256:41c2cf8c272f6fb0080a97cd9d9bd7d4604072b80a0b10e7d65ca26ef5000c0c +using existing layer sha256:1da0581fd4ce92dcf5a66b1da737cf215d8dcf25aa1b98b44443aaf7173155f5 +creating new layer sha256:941b69ca7dc2a85c053c38d9e8029c9df6224e545060954fa97587f87c044a64 +using existing layer sha256:f02dd72bb2423204352eabc5637b44d79d17f109fdb510a7c51455892aa2d216 +writing manifest +success ``` Once created, you can see it when you use [list models](./list-models) API. diff --git a/docs/docs/metrics.md b/docs/docs/metrics.md index 6ecbd9f..10bc4b1 100644 --- a/docs/docs/metrics.md +++ b/docs/docs/metrics.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 6 title: Metrics --- diff --git a/src/main/java/io/github/ollama4j/agent/Agent.java b/src/main/java/io/github/ollama4j/agent/Agent.java index 4e3ac57..f9fdf7c 100644 --- a/src/main/java/io/github/ollama4j/agent/Agent.java +++ b/src/main/java/io/github/ollama4j/agent/Agent.java @@ -265,6 +265,7 @@ public class Agent { @Data @Setter @Getter + @EqualsAndHashCode(callSuper = false) private static class AgentToolSpec extends Tools.ToolSpec { /** Fully qualified class name of the tool's {@link ToolFunction} implementation */ private String toolFunctionFQCN = null; From d55d1c0fd9ea6b14abbbb1152e4dae773ffa04ad Mon Sep 17 00:00:00 2001 From: Amith Koujalgi Date: Sun, 19 Oct 2025 14:34:21 +0530 Subject: [PATCH 7/9] Update Javadoc comments in Ollama and Agent classes to reflect correct method references for chat request construction and agent instantiation. --- src/main/java/io/github/ollama4j/Ollama.java | 2 +- src/main/java/io/github/ollama4j/agent/Agent.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/ollama4j/Ollama.java b/src/main/java/io/github/ollama4j/Ollama.java index e79f53d..cfbdf41 100644 --- a/src/main/java/io/github/ollama4j/Ollama.java +++ b/src/main/java/io/github/ollama4j/Ollama.java @@ -862,7 +862,7 @@ public class Ollama { /** * Sends a chat request to a model using an {@link OllamaChatRequest} and sets up streaming response. - * This can be constructed using an {@link OllamaChatRequestBuilder}. + * This can be constructed using an {@link OllamaChatRequest#builder()}. * *

Note: the OllamaChatRequestModel#getStream() property is not implemented. * diff --git a/src/main/java/io/github/ollama4j/agent/Agent.java b/src/main/java/io/github/ollama4j/agent/Agent.java index f9fdf7c..5712cdf 100644 --- a/src/main/java/io/github/ollama4j/agent/Agent.java +++ b/src/main/java/io/github/ollama4j/agent/Agent.java @@ -32,8 +32,8 @@ import lombok.*; *

Usage

* *
    - *
  • Instantiate an Agent via {@link #fromYaml(String)} for YAML-based configuration. - *
  • Handle conversation turns via {@link #think(String)}. + *
  • Instantiate an Agent via {@link #load(String)} for YAML-based configuration. + *
  • Handle conversation turns via {@link #interact(String)}. *
  • Use {@link #runInteractive()} for an interactive console-based session. *
*/ From fe43e87e1a94c4c11167218f552b4a7a763d492c Mon Sep 17 00:00:00 2001 From: Amith Koujalgi Date: Mon, 20 Oct 2025 20:07:15 +0530 Subject: [PATCH 8/9] Enhance agent documentation with detailed YAML configuration instructions and benefits. Update CodeEmbed component to support customizable language for syntax highlighting. Refactor Agent class to improve Javadoc comments and method signatures for better clarity and functionality. --- docs/docs/agent.md | 57 +++++++++- docs/src/components/CodeEmbed/index.js | 102 ++++-------------- .../java/io/github/ollama4j/agent/Agent.java | 68 ++++++++---- 3 files changed, 117 insertions(+), 110 deletions(-) diff --git a/docs/docs/agent.md b/docs/docs/agent.md index 68963c0..40d4244 100644 --- a/docs/docs/agent.md +++ b/docs/docs/agent.md @@ -1,13 +1,60 @@ --- sidebar_position: 4 -title: Agent +title: Agents --- import CodeEmbed from '@site/src/components/CodeEmbed'; -# Agent +# Agents + +Build powerful, flexible agents—backed by LLMs and tools—in a few minutes. + +Ollama4j’s agent system lets you bring together the best of LLM reasoning and external tool-use using a simple, declarative YAML configuration. No framework bloat, no complicated setup—just describe your agent, plug in your logic, and go. + +--- + +**Why use agents in Ollama4j?** + +- **Effortless Customization:** Instantly adjust your agent’s persona, reasoning strategies, or domain by tweaking YAML. No need to touch your compiled Java code. +- **Easy Extensibility:** Want new capabilities? Just add or change tools and logic classes—no framework glue or plumbing required. +- **Fast Experimentation:** Mix-and-match models, instructions, and tools—prototype sophisticated behaviors or orchestrators in minutes. +- **Clean Separation:** Keep business logic (Java) and agent personality/configuration (YAML) separate for maintainability and clarity. + +--- + +## Define an Agent in YAML + +Specify everything about your agent—what LLM it uses, its “personality,” and all callable tools—in a single YAML file. + +**Agent YAML keys:** + +| Field | Description | +|-------------------------|-----------------------------------------------------------------------------------------------------------------------| +| `name` | Name of your agent. | +| `host` | The base URL for your Ollama server (e.g., `http://localhost:11434`). | +| `model` | The LLM backing your agent (e.g., `llama2`, `mistral`, `mixtral`, etc). | +| `customPrompt` | _(optional)_ System prompt—instructions or persona for your agent. | +| `tools` | List of tools the agent can use. Each tool entry describes the name, function, and parameters. | +| `toolFunctionFQCN` | Fully qualified Java class name implementing the tool logic. Must be present on classpath. | +| `requestTimeoutSeconds` | _(optional)_ How long (seconds) to wait for agent replies. | + +YAML makes it effortless to configure and tweak your agent’s powers and behavior—no code changes needed! + +**Example agent YAML:** + + + +--- + +## Instantiating and Running Agents in Java + +Once your agent is described in YAML, bringing it to life in Java takes only a couple of lines: + + + +- **No boilerplate.** Just load and start chatting or calling tools. +- The API takes care of wiring up LLMs, tool invocation, and instruction handling. + +Ready to build your own AI-powered assistant? Just write your YAML, implement the tool logic in Java, and go! -:::warning[Note] -This is work in progress -::: \ No newline at end of file diff --git a/docs/src/components/CodeEmbed/index.js b/docs/src/components/CodeEmbed/index.js index 1a269d1..5a38aac 100644 --- a/docs/src/components/CodeEmbed/index.js +++ b/docs/src/components/CodeEmbed/index.js @@ -1,84 +1,14 @@ -// import React, { useState, useEffect } from 'react'; -// import CodeBlock from '@theme/CodeBlock'; -// import Icon from '@site/src/components/Icon'; - - -// const CodeEmbed = ({ src }) => { -// const [code, setCode] = useState(''); -// const [loading, setLoading] = useState(true); -// const [error, setError] = useState(null); - -// useEffect(() => { -// let isMounted = true; - -// const fetchCodeFromUrl = async (url) => { -// if (!isMounted) return; - -// setLoading(true); -// setError(null); - -// try { -// const response = await fetch(url); -// if (!response.ok) { -// throw new Error(`HTTP error! status: ${response.status}`); -// } -// const data = await response.text(); -// if (isMounted) { -// setCode(data); -// } -// } catch (err) { -// console.error('Failed to fetch code:', err); -// if (isMounted) { -// setError(err); -// setCode(`// Failed to load code from ${url}\n// ${err.message}`); -// } -// } finally { -// if (isMounted) { -// setLoading(false); -// } -// } -// }; - -// if (src) { -// fetchCodeFromUrl(src); -// } - -// return () => { -// isMounted = false; -// }; -// }, [src]); - -// const githubUrl = src ? src.replace('https://raw.githubusercontent.com', 'https://github.com').replace('/refs/heads/', '/blob/') : null; -// const fileName = src ? src.substring(src.lastIndexOf('/') + 1) : null; - -// return ( -// loading ? ( -//
Loading code...
-// ) : error ? ( -//
Error: {error.message}
-// ) : ( -//
-//
-// {githubUrl && ( -// -// View on GitHub -// -// -// )} -//
-// {code} -//
-// ) -// ); -// }; - -// export default CodeEmbed; -import React, { useState, useEffect } from 'react'; +import React, {useState, useEffect} from 'react'; import CodeBlock from '@theme/CodeBlock'; import Icon from '@site/src/components/Icon'; - -const CodeEmbed = ({ src }) => { +/** + * CodeEmbed component to display code fetched from a URL in a CodeBlock. + * @param {object} props + * @param {string} props.src - Source URL to fetch the code from. + * @param {string} [props.language='java'] - Language for syntax highlighting in CodeBlock. + */ +const CodeEmbed = ({src, language = 'java'}) => { const [code, setCode] = useState(''); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -127,7 +57,7 @@ const CodeEmbed = ({ src }) => { const fileName = src ? src.substring(src.lastIndexOf('/') + 1) : null; const title = ( -
+ @@ -160,8 +96,8 @@ const CodeEmbed = ({ src }) => { ) : error ? (
Error: {error.message}
) : ( -
- {code} +
+ {code}
) ); diff --git a/src/main/java/io/github/ollama4j/agent/Agent.java b/src/main/java/io/github/ollama4j/agent/Agent.java index 5712cdf..bd17232 100644 --- a/src/main/java/io/github/ollama4j/agent/Agent.java +++ b/src/main/java/io/github/ollama4j/agent/Agent.java @@ -38,32 +38,44 @@ import lombok.*; * */ public class Agent { - /** The agent's display name */ + /** + * The agent's display name + */ private final String name; - /** List of supported tools for this agent */ + /** + * List of supported tools for this agent + */ private final List tools; - /** Ollama client instance for communication with the API */ + /** + * Ollama client instance for communication with the API + */ private final Ollama ollamaClient; - /** The model name used for chat completions */ + /** + * The model name used for chat completions + */ private final String model; - /** Persists chat message history across rounds */ + /** + * Persists chat message history across rounds + */ private final List chatHistory; - /** Optional custom system prompt for the agent */ + /** + * Optional custom system prompt for the agent + */ private final String customPrompt; /** * Constructs a new Agent. * - * @param name The agent's given name. + * @param name The agent's given name. * @param ollamaClient The Ollama API client instance to use. - * @param model The model name to use for chat completion. + * @param model The model name to use for chat completion. * @param customPrompt A custom prompt to prepend to all conversations (may be null). - * @param tools List of available tools for function calling. + * @param tools List of available tools for function calling. */ public Agent( String name, @@ -162,7 +174,8 @@ public class Agent { * @return The model's response as a string. * @throws OllamaException If there is a problem with the Ollama API. */ - public String interact(String userInput) throws OllamaException { + public String interact(String userInput, OllamaChatStreamObserver chatTokenHandler) + throws OllamaException { // Build a concise and readable description of available tools String availableToolsDescription = tools.isEmpty() @@ -202,11 +215,6 @@ public class Agent { .withModel(model) .withMessages(chatHistory) .build(); - - OllamaChatStreamObserver chatTokenHandler = - new OllamaChatStreamObserver( - new ConsoleOutputGenerateTokenHandler(), - new ConsoleOutputGenerateTokenHandler()); OllamaChatResult response = ollamaClient.chat(request, chatTokenHandler); // Update chat history for continuity @@ -230,7 +238,11 @@ public class Agent { System.out.print("\n[You]: "); String input = sc.nextLine(); if ("exit".equalsIgnoreCase(input)) break; - this.interact(input); + this.interact( + input, + new OllamaChatStreamObserver( + new ConsoleOutputGenerateTokenHandler(), + new ConsoleOutputGenerateTokenHandler())); } } @@ -267,23 +279,35 @@ public class Agent { @Getter @EqualsAndHashCode(callSuper = false) private static class AgentToolSpec extends Tools.ToolSpec { - /** Fully qualified class name of the tool's {@link ToolFunction} implementation */ + /** + * Fully qualified class name of the tool's {@link ToolFunction} implementation + */ private String toolFunctionFQCN = null; - /** Instance of the {@link ToolFunction} to invoke */ + /** + * Instance of the {@link ToolFunction} to invoke + */ private ToolFunction toolFunctionInstance = null; } - /** Bean for describing a tool function parameter for use in agent YAML definitions. */ + /** + * Bean for describing a tool function parameter for use in agent YAML definitions. + */ @Data public class AgentToolParameter { - /** The parameter's type (e.g., string, number, etc.) */ + /** + * The parameter's type (e.g., string, number, etc.) + */ private String type; - /** Description of the parameter */ + /** + * Description of the parameter + */ private String description; - /** Whether this parameter is required */ + /** + * Whether this parameter is required + */ private boolean required; /** From 7ce89a3e89828f93240f6e311ebf1689ac511a5f Mon Sep 17 00:00:00 2001 From: amithkoujalgi Date: Mon, 20 Oct 2025 22:31:04 +0530 Subject: [PATCH 9/9] Update Javadoc in Agent class to reflect changes in the interact method signature, now including an OllamaChatStreamObserver parameter for improved conversation handling. --- src/main/java/io/github/ollama4j/agent/Agent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/ollama4j/agent/Agent.java b/src/main/java/io/github/ollama4j/agent/Agent.java index bd17232..72bf2ed 100644 --- a/src/main/java/io/github/ollama4j/agent/Agent.java +++ b/src/main/java/io/github/ollama4j/agent/Agent.java @@ -33,7 +33,7 @@ import lombok.*; * *
    *
  • Instantiate an Agent via {@link #load(String)} for YAML-based configuration. - *
  • Handle conversation turns via {@link #interact(String)}. + *
  • Handle conversation turns via {@link #interact(String, OllamaChatStreamObserver)}. *
  • Use {@link #runInteractive()} for an interactive console-based session. *
*/