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