From da6d20d118c79eb07cf60497dc3c683b12177e48 Mon Sep 17 00:00:00 2001 From: amithkoujalgi Date: Fri, 10 Oct 2025 23:56:31 +0530 Subject: [PATCH] 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; }