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; }