diff --git a/src/main/java/io/github/ollama4j/OllamaAPI.java b/src/main/java/io/github/ollama4j/OllamaAPI.java index 3bd55c1..d8da8ed 100644 --- a/src/main/java/io/github/ollama4j/OllamaAPI.java +++ b/src/main/java/io/github/ollama4j/OllamaAPI.java @@ -12,7 +12,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.github.ollama4j.exceptions.OllamaBaseException; import io.github.ollama4j.exceptions.RoleNotFoundException; import io.github.ollama4j.exceptions.ToolInvocationException; -import io.github.ollama4j.exceptions.ToolNotFoundException; import io.github.ollama4j.metrics.MetricsRecorder; import io.github.ollama4j.models.chat.*; import io.github.ollama4j.models.chat.OllamaChatTokenHandler; @@ -25,15 +24,9 @@ import io.github.ollama4j.models.ps.ModelsProcessResponse; import io.github.ollama4j.models.request.*; import io.github.ollama4j.models.response.*; import io.github.ollama4j.tools.*; -import io.github.ollama4j.tools.annotations.OllamaToolService; -import io.github.ollama4j.tools.annotations.ToolProperty; -import io.github.ollama4j.tools.annotations.ToolSpec; import io.github.ollama4j.utils.Constants; import io.github.ollama4j.utils.Utils; import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; @@ -61,6 +54,7 @@ public class OllamaAPI { private final String host; private Auth auth; + private final ToolRegistry toolRegistry = new ToolRegistry(); /** @@ -760,10 +754,10 @@ public class OllamaAPI { private OllamaResult generateWithToolsInternal( OllamaGenerateRequest request, OllamaGenerateStreamObserver streamObserver) throws OllamaBaseException { - List tools = new ArrayList<>(); - for (Tools.ToolSpecification spec : toolRegistry.getRegisteredSpecs()) { - tools.add(spec.getToolPrompt()); - } + // List tools = new ArrayList<>(); + // for (Tools.ToolSpecification spec : toolRegistry.getRegisteredSpecs()) { + // tools.add(spec.getToolPrompt()); + // } ArrayList msgs = new ArrayList<>(); OllamaChatRequest chatRequest = new OllamaChatRequest(); chatRequest.setModel(request.getModel()); @@ -773,14 +767,16 @@ public class OllamaAPI { chatRequest.setMessages(msgs); msgs.add(ocm); OllamaChatTokenHandler hdlr = null; - chatRequest.setTools(tools); + chatRequest.setTools(request.getTools()); if (streamObserver != null) { chatRequest.setStream(true); - hdlr = - chatResponseModel -> - streamObserver - .getResponseStreamHandler() - .accept(chatResponseModel.getMessage().getResponse()); + if (streamObserver.getResponseStreamHandler() != null) { + hdlr = + chatResponseModel -> + streamObserver + .getResponseStreamHandler() + .accept(chatResponseModel.getMessage().getResponse()); + } } OllamaChatResult res = chat(chatRequest, hdlr); return new OllamaResult( @@ -837,10 +833,8 @@ public class OllamaAPI { // only add tools if tools flag is set if (request.isUseTools()) { // add all registered tools to request - request.setTools( - toolRegistry.getRegisteredSpecs().stream() - .map(Tools.ToolSpecification::getToolPrompt) - .collect(Collectors.toList())); + request.setTools(toolRegistry.getRegisteredTools()); + System.out.println("Use tools is set."); } if (tokenHandler != null) { @@ -859,31 +853,36 @@ public class OllamaAPI { && toolCallTries < maxChatToolCallRetries) { for (OllamaChatToolCalls toolCall : toolCalls) { String toolName = toolCall.getFunction().getName(); - ToolFunction toolFunction = toolRegistry.getToolFunction(toolName); - if (toolFunction == null) { - throw new ToolInvocationException("Tool function not found: " + toolName); + for (Tools.Tool t : request.getTools()) { + if (t.getToolSpec().getName().equals(toolName)) { + ToolFunction toolFunction = t.getToolFunction(); + if (toolFunction == null) { + throw new ToolInvocationException( + "Tool function not found: " + toolName); + } + LOG.debug( + "Invoking tool {} with arguments: {}", + toolCall.getFunction().getName(), + toolCall.getFunction().getArguments()); + Map arguments = toolCall.getFunction().getArguments(); + Object res = toolFunction.apply(arguments); + String argumentKeys = + arguments.keySet().stream() + .map(Object::toString) + .collect(Collectors.joining(", ")); + request.getMessages() + .add( + new OllamaChatMessage( + OllamaChatMessageRole.TOOL, + "[TOOL_RESULTS] " + + toolName + + "(" + + argumentKeys + + "): " + + res + + " [/TOOL_RESULTS]")); + } } - LOG.debug( - "Invoking tool {} with arguments: {}", - toolCall.getFunction().getName(), - toolCall.getFunction().getArguments()); - Map arguments = toolCall.getFunction().getArguments(); - Object res = toolFunction.apply(arguments); - String argumentKeys = - arguments.keySet().stream() - .map(Object::toString) - .collect(Collectors.joining(", ")); - request.getMessages() - .add( - new OllamaChatMessage( - OllamaChatMessageRole.TOOL, - "[TOOL_RESULTS] " - + toolName - + "(" - + argumentKeys - + "): " - + res - + " [/TOOL_RESULTS]")); } if (tokenHandler != null) { result = requestCaller.call(request, tokenHandler); @@ -900,27 +899,23 @@ public class OllamaAPI { } /** - * Registers a single tool in the tool registry using the provided tool specification. + * Registers a single tool in the tool registry. * - * @param toolSpecification the specification of the tool to register. It contains the tool's - * function name and other relevant information. + * @param tool the tool to register. Contains the tool's specification and function. */ - public void registerTool(Tools.ToolSpecification toolSpecification) { - toolRegistry.addTool(toolSpecification.getFunctionName(), toolSpecification); - LOG.debug("Registered tool: {}", toolSpecification.getFunctionName()); + public void registerTool(Tools.Tool tool) { + toolRegistry.addTool(tool); + LOG.debug("Registered tool: {}", tool.getToolSpec().getName()); } /** - * Registers multiple tools in the tool registry using a list of tool specifications. Iterates - * over the list and adds each tool specification to the registry. + * Registers multiple tools in the tool registry. * - * @param toolSpecifications a list of tool specifications to register. Each specification - * contains information about a tool, such as its function name. + * @param tools a list of {@link Tools.Tool} objects to register. Each tool contains + * its specification and function. */ - public void registerTools(List toolSpecifications) { - for (Tools.ToolSpecification toolSpecification : toolSpecifications) { - toolRegistry.addTool(toolSpecification.getFunctionName(), toolSpecification); - } + public void registerTools(List tools) { + toolRegistry.addTools(tools); } /** @@ -932,122 +927,135 @@ public class OllamaAPI { LOG.debug("All tools have been deregistered."); } - /** - * Registers tools based on the annotations found on the methods of the caller's class and its - * providers. This method scans the caller's class for the {@link OllamaToolService} annotation - * and recursively registers annotated tools from all the providers specified in the annotation. - * - * @throws OllamaBaseException if the caller's class is not annotated with {@link - * OllamaToolService} or if reflection-based instantiation or invocation fails - */ - public void registerAnnotatedTools() throws OllamaBaseException { - try { - Class callerClass = null; - try { - callerClass = - Class.forName(Thread.currentThread().getStackTrace()[2].getClassName()); - } catch (ClassNotFoundException e) { - throw new OllamaBaseException(e.getMessage(), e); - } - - OllamaToolService ollamaToolServiceAnnotation = - callerClass.getDeclaredAnnotation(OllamaToolService.class); - if (ollamaToolServiceAnnotation == null) { - throw new IllegalStateException( - callerClass + " is not annotated as " + OllamaToolService.class); - } - - Class[] providers = ollamaToolServiceAnnotation.providers(); - for (Class provider : providers) { - registerAnnotatedTools(provider.getDeclaredConstructor().newInstance()); - } - } catch (InstantiationException - | NoSuchMethodException - | IllegalAccessException - | InvocationTargetException e) { - throw new OllamaBaseException(e.getMessage()); - } - } - - /** - * Registers tools based on the annotations found on the methods of the provided object. This - * method scans the methods of the given object and registers tools using the {@link ToolSpec} - * annotation and associated {@link ToolProperty} annotations. It constructs tool specifications - * and stores them in a tool registry. - * - * @param object the object whose methods are to be inspected for annotated tools - * @throws RuntimeException if any reflection-based instantiation or invocation fails - */ - public void registerAnnotatedTools(Object object) { - Class objectClass = object.getClass(); - Method[] methods = objectClass.getMethods(); - for (Method m : methods) { - ToolSpec toolSpec = m.getDeclaredAnnotation(ToolSpec.class); - if (toolSpec == null) { - continue; - } - String operationName = !toolSpec.name().isBlank() ? toolSpec.name() : m.getName(); - String operationDesc = !toolSpec.desc().isBlank() ? toolSpec.desc() : operationName; - - final Tools.PropsBuilder propsBuilder = new Tools.PropsBuilder(); - LinkedHashMap methodParams = new LinkedHashMap<>(); - for (Parameter parameter : m.getParameters()) { - final ToolProperty toolPropertyAnn = - parameter.getDeclaredAnnotation(ToolProperty.class); - String propType = parameter.getType().getTypeName(); - if (toolPropertyAnn == null) { - methodParams.put(parameter.getName(), null); - continue; - } - String propName = - !toolPropertyAnn.name().isBlank() - ? toolPropertyAnn.name() - : parameter.getName(); - methodParams.put(propName, propType); - propsBuilder.withProperty( - propName, - Tools.PromptFuncDefinition.Property.builder() - .type(propType) - .description(toolPropertyAnn.desc()) - .required(toolPropertyAnn.required()) - .build()); - } - final Map params = propsBuilder.build(); - List reqProps = - params.entrySet().stream() - .filter(e -> e.getValue().isRequired()) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - - Tools.ToolSpecification toolSpecification = - Tools.ToolSpecification.builder() - .functionName(operationName) - .functionDescription(operationDesc) - .toolPrompt( - Tools.PromptFuncDefinition.builder() - .type("function") - .function( - Tools.PromptFuncDefinition.PromptFuncSpec - .builder() - .name(operationName) - .description(operationDesc) - .parameters( - Tools.PromptFuncDefinition - .Parameters.builder() - .type("object") - .properties(params) - .required(reqProps) - .build()) - .build()) - .build()) - .build(); - - ReflectionalToolFunction reflectionalToolFunction = - new ReflectionalToolFunction(object, m, methodParams); - toolSpecification.setToolFunction(reflectionalToolFunction); - toolRegistry.addTool(toolSpecification.getFunctionName(), toolSpecification); - } - } + // + // /** + // * Registers tools based on the annotations found on the methods of the caller's class and + // its + // * providers. This method scans the caller's class for the {@link OllamaToolService} + // annotation + // * and recursively registers annotated tools from all the providers specified in the + // annotation. + // * + // * @throws OllamaBaseException if the caller's class is not annotated with {@link + // * OllamaToolService} or if reflection-based instantiation or invocation fails + // */ + // public void registerAnnotatedTools() throws OllamaBaseException { + // try { + // Class callerClass = null; + // try { + // callerClass = + // + // Class.forName(Thread.currentThread().getStackTrace()[2].getClassName()); + // } catch (ClassNotFoundException e) { + // throw new OllamaBaseException(e.getMessage(), e); + // } + // + // OllamaToolService ollamaToolServiceAnnotation = + // callerClass.getDeclaredAnnotation(OllamaToolService.class); + // if (ollamaToolServiceAnnotation == null) { + // throw new IllegalStateException( + // callerClass + " is not annotated as " + OllamaToolService.class); + // } + // + // Class[] providers = ollamaToolServiceAnnotation.providers(); + // for (Class provider : providers) { + // registerAnnotatedTools(provider.getDeclaredConstructor().newInstance()); + // } + // } catch (InstantiationException + // | NoSuchMethodException + // | IllegalAccessException + // | InvocationTargetException e) { + // throw new OllamaBaseException(e.getMessage()); + // } + // } + // + // /** + // * Registers tools based on the annotations found on the methods of the provided object. + // This + // * method scans the methods of the given object and registers tools using the {@link + // ToolSpec} + // * annotation and associated {@link ToolProperty} annotations. It constructs tool + // specifications + // * and stores them in a tool registry. + // * + // * @param object the object whose methods are to be inspected for annotated tools + // * @throws RuntimeException if any reflection-based instantiation or invocation fails + // */ + // public void registerAnnotatedTools(Object object) { + // Class objectClass = object.getClass(); + // Method[] methods = objectClass.getMethods(); + // for (Method m : methods) { + // ToolSpec toolSpec = m.getDeclaredAnnotation(ToolSpec.class); + // if (toolSpec == null) { + // continue; + // } + // String operationName = !toolSpec.name().isBlank() ? toolSpec.name() : m.getName(); + // String operationDesc = !toolSpec.desc().isBlank() ? toolSpec.desc() : + // operationName; + // + // final Tools.PropsBuilder propsBuilder = new Tools.PropsBuilder(); + // LinkedHashMap methodParams = new LinkedHashMap<>(); + // for (Parameter parameter : m.getParameters()) { + // final ToolProperty toolPropertyAnn = + // parameter.getDeclaredAnnotation(ToolProperty.class); + // String propType = parameter.getType().getTypeName(); + // if (toolPropertyAnn == null) { + // methodParams.put(parameter.getName(), null); + // continue; + // } + // String propName = + // !toolPropertyAnn.name().isBlank() + // ? toolPropertyAnn.name() + // : parameter.getName(); + // methodParams.put(propName, propType); + // propsBuilder.withProperty( + // propName, + // Tools.PromptFuncDefinition.Property.builder() + // .type(propType) + // .description(toolPropertyAnn.desc()) + // .required(toolPropertyAnn.required()) + // .build()); + // } + // final Map params = + // propsBuilder.build(); + // List reqProps = + // params.entrySet().stream() + // .filter(e -> e.getValue().isRequired()) + // .map(Map.Entry::getKey) + // .collect(Collectors.toList()); + // + // Tools.ToolSpecification toolSpecification = + // Tools.ToolSpecification.builder() + // .functionName(operationName) + // .functionDescription(operationDesc) + // .toolPrompt( + // Tools.PromptFuncDefinition.builder() + // .type("function") + // .function( + // Tools.PromptFuncDefinition.PromptFuncSpec + // .builder() + // .name(operationName) + // .description(operationDesc) + // .parameters( + // Tools.PromptFuncDefinition + // + // .Parameters.builder() + // .type("object") + // + // .properties(params) + // + // .required(reqProps) + // .build()) + // .build()) + // .build()) + // .build(); + // + // ReflectionalToolFunction reflectionalToolFunction = + // new ReflectionalToolFunction(object, m, methodParams); + // toolSpecification.setToolFunction(reflectionalToolFunction); + // toolRegistry.addTool(toolSpecification.getFunctionName(), toolSpecification); + // } + // } /** * Adds a custom role. @@ -1185,32 +1193,32 @@ public class OllamaAPI { return auth != null; } - /** - * Invokes a registered tool function by name and arguments. - * - * @param toolFunctionCallSpec the tool function call specification - * @return the result of the tool function - * @throws ToolInvocationException if the tool is not found or invocation fails - */ - private Object invokeTool(ToolFunctionCallSpec toolFunctionCallSpec) - throws ToolInvocationException { - try { - String methodName = toolFunctionCallSpec.getName(); - Map arguments = toolFunctionCallSpec.getArguments(); - ToolFunction function = toolRegistry.getToolFunction(methodName); - LOG.debug("Invoking function {} with arguments {}", methodName, arguments); - if (function == null) { - throw new ToolNotFoundException( - "No such tool: " - + methodName - + ". Please register the tool before invoking it."); - } - return function.apply(arguments); - } catch (Exception e) { - throw new ToolInvocationException( - "Failed to invoke tool: " + toolFunctionCallSpec.getName(), e); - } - } + // /** + // * Invokes a registered tool function by name and arguments. + // * + // * @param toolFunctionCallSpec the tool function call specification + // * @return the result of the tool function + // * @throws ToolInvocationException if the tool is not found or invocation fails + // */ + // private Object invokeTool(ToolFunctionCallSpec toolFunctionCallSpec) + // throws ToolInvocationException { + // try { + // String methodName = toolFunctionCallSpec.getName(); + // Map arguments = toolFunctionCallSpec.getArguments(); + // ToolFunction function = toolRegistry.getToolFunction(methodName); + // LOG.debug("Invoking function {} with arguments {}", methodName, arguments); + // if (function == null) { + // throw new ToolNotFoundException( + // "No such tool: " + // + methodName + // + ". Please register the tool before invoking it."); + // } + // return function.apply(arguments); + // } catch (Exception e) { + // throw new ToolInvocationException( + // "Failed to invoke tool: " + toolFunctionCallSpec.getName(), e); + // } + // } // /** // * Initialize metrics collection if enabled. diff --git a/src/main/java/io/github/ollama4j/models/chat/OllamaChatRequest.java b/src/main/java/io/github/ollama4j/models/chat/OllamaChatRequest.java index 1fcdf6c..a10cf77 100644 --- a/src/main/java/io/github/ollama4j/models/chat/OllamaChatRequest.java +++ b/src/main/java/io/github/ollama4j/models/chat/OllamaChatRequest.java @@ -29,7 +29,7 @@ public class OllamaChatRequest extends OllamaCommonRequest implements OllamaRequ private List messages = Collections.emptyList(); - private List tools; + private List tools; private boolean think; diff --git a/src/main/java/io/github/ollama4j/models/generate/OllamaGenerateRequest.java b/src/main/java/io/github/ollama4j/models/generate/OllamaGenerateRequest.java index e06e340..05ad9c8 100644 --- a/src/main/java/io/github/ollama4j/models/generate/OllamaGenerateRequest.java +++ b/src/main/java/io/github/ollama4j/models/generate/OllamaGenerateRequest.java @@ -26,7 +26,7 @@ public class OllamaGenerateRequest extends OllamaCommonRequest implements Ollama private boolean raw; private boolean think; private boolean useTools; - private List tools; + private List tools; public OllamaGenerateRequest() {} diff --git a/src/main/java/io/github/ollama4j/models/generate/OllamaGenerateRequestBuilder.java b/src/main/java/io/github/ollama4j/models/generate/OllamaGenerateRequestBuilder.java index 63b363d..0717f9e 100644 --- a/src/main/java/io/github/ollama4j/models/generate/OllamaGenerateRequestBuilder.java +++ b/src/main/java/io/github/ollama4j/models/generate/OllamaGenerateRequestBuilder.java @@ -8,12 +8,14 @@ */ package io.github.ollama4j.models.generate; +import io.github.ollama4j.tools.Tools; import io.github.ollama4j.utils.Options; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Base64; +import java.util.List; /** Helper class for creating {@link OllamaGenerateRequest} objects using the builder-pattern. */ public class OllamaGenerateRequestBuilder { @@ -37,6 +39,11 @@ public class OllamaGenerateRequestBuilder { return this; } + public OllamaGenerateRequestBuilder withTools(List tools) { + request.setTools(tools); + return this; + } + public OllamaGenerateRequestBuilder withModel(String model) { request.setModel(model); return this; 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 b3db78b..5fb4ce9 100644 --- a/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java +++ b/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java @@ -96,6 +96,7 @@ public class OllamaChatEndpointCaller extends OllamaEndpointCaller { getRequestBuilderDefault(uri).POST(body.getBodyPublisher()); HttpRequest request = requestBuilder.build(); LOG.debug("Asking model: {}", body); + System.out.println("Asking model: " + Utils.toJSON(body)); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); @@ -140,7 +141,8 @@ public class OllamaChatEndpointCaller extends OllamaEndpointCaller { statusCode, responseBuffer); if (statusCode != 200) { - LOG.error("Status code: " + statusCode); + LOG.error("Status code: {}", statusCode); + System.out.println(responseBuffer); throw new OllamaBaseException(responseBuffer.toString()); } if (wantedToolsForStream != null && ollamaChatResponseModel != null) { diff --git a/src/main/java/io/github/ollama4j/tools/ToolRegistry.java b/src/main/java/io/github/ollama4j/tools/ToolRegistry.java index 3745abd..273b684 100644 --- a/src/main/java/io/github/ollama4j/tools/ToolRegistry.java +++ b/src/main/java/io/github/ollama4j/tools/ToolRegistry.java @@ -8,29 +8,40 @@ */ package io.github.ollama4j.tools; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import io.github.ollama4j.exceptions.ToolNotFoundException; +import java.util.*; public class ToolRegistry { - private final Map tools = new HashMap<>(); + private final List tools = new ArrayList<>(); - public ToolFunction getToolFunction(String name) { - final Tools.ToolSpecification toolSpecification = tools.get(name); - return toolSpecification != null ? toolSpecification.getToolFunction() : null; + public ToolFunction getToolFunction(String name) throws ToolNotFoundException { + for (Tools.Tool tool : tools) { + if (tool.getToolSpec().getName().equals(name)) { + return tool.getToolFunction(); + } + } + throw new ToolNotFoundException(String.format("Tool '%s' not found.", name)); } - public void addTool(String name, Tools.ToolSpecification specification) { - tools.put(name, specification); + public void addTool(Tools.Tool tool) { + try { + getToolFunction(tool.getToolSpec().getName()); + } catch (ToolNotFoundException e) { + tools.add(tool); + } } - public Collection getRegisteredSpecs() { - return tools.values(); + public void addTools(List tools) { + for (Tools.Tool tool : tools) { + addTool(tool); + } } - /** - * Removes all registered tools from the registry. - */ + public List getRegisteredTools() { + return tools; + } + + /** Removes all registered tools from the registry. */ public void clear() { tools.clear(); } diff --git a/src/main/java/io/github/ollama4j/tools/Tools.java b/src/main/java/io/github/ollama4j/tools/Tools.java index 59baaaf..c2f5b0a 100644 --- a/src/main/java/io/github/ollama4j/tools/Tools.java +++ b/src/main/java/io/github/ollama4j/tools/Tools.java @@ -9,13 +9,10 @@ package io.github.ollama4j.tools; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import io.github.ollama4j.utils.Utils; +import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.AllArgsConstructor; @@ -26,115 +23,95 @@ import lombok.NoArgsConstructor; public class Tools { @Data @Builder - public static class ToolSpecification { - private String functionName; - private String functionDescription; - private PromptFuncDefinition toolPrompt; - private ToolFunction toolFunction; + @NoArgsConstructor + @AllArgsConstructor + public static class Tool { + @JsonProperty("function") + private ToolSpec toolSpec; + + private String type = "function"; + @JsonIgnore private ToolFunction toolFunction; } @Data - @JsonIgnoreProperties(ignoreUnknown = true) @Builder @NoArgsConstructor @AllArgsConstructor - public static class PromptFuncDefinition { - private String type; - private PromptFuncSpec function; - - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class PromptFuncSpec { - private String name; - private String description; - private Parameters parameters; - } - - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class Parameters { - private String type; - private Map properties; - private List required; - } - - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class Property { - private String type; - private String description; - - @JsonProperty("enum") - @JsonInclude(JsonInclude.Include.NON_NULL) - private List enumValues; - - @JsonIgnore private boolean required; - } + public static class ToolSpec { + private String name; + private String description; + private Parameters parameters; } - public static class PropsBuilder { - private final Map props = new HashMap<>(); + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Parameters { + private Map properties; + private List required = new ArrayList<>(); - public PropsBuilder withProperty(String key, PromptFuncDefinition.Property property) { - props.put(key, property); - return this; - } - - public Map build() { - return props; - } - } - - public static class PromptBuilder { - private final List tools = new ArrayList<>(); - - private String promptText; - - public String build() throws JsonProcessingException { - return "[AVAILABLE_TOOLS] " - + Utils.getObjectMapper().writeValueAsString(tools) - + "[/AVAILABLE_TOOLS][INST] " - + promptText - + " [/INST]"; - } - - public PromptBuilder withPrompt(String prompt) throws JsonProcessingException { - promptText = prompt; - return this; - } - - public PromptBuilder withToolSpecification(ToolSpecification spec) { - PromptFuncDefinition def = new PromptFuncDefinition(); - def.setType("function"); - - PromptFuncDefinition.PromptFuncSpec functionDetail = - new PromptFuncDefinition.PromptFuncSpec(); - functionDetail.setName(spec.getFunctionName()); - functionDetail.setDescription(spec.getFunctionDescription()); - - PromptFuncDefinition.Parameters parameters = new PromptFuncDefinition.Parameters(); - parameters.setType("object"); - parameters.setProperties(spec.getToolPrompt().getFunction().parameters.getProperties()); - - List requiredValues = new ArrayList<>(); - for (Map.Entry p : - spec.getToolPrompt().getFunction().getParameters().getProperties().entrySet()) { - if (p.getValue().isRequired()) { - requiredValues.add(p.getKey()); + public static Parameters of(Map properties) { + Parameters params = new Parameters(); + params.setProperties(properties); + // Optionally, populate required from properties' required flags + if (properties != null) { + for (Map.Entry entry : properties.entrySet()) { + if (entry.getValue() != null && entry.getValue().isRequired()) { + params.getRequired().add(entry.getKey()); + } } } - parameters.setRequired(requiredValues); - functionDetail.setParameters(parameters); - def.setFunction(functionDetail); + return params; + } - tools.add(def); - return this; + @Override + public String toString() { + ObjectNode node = + com.fasterxml.jackson.databind.json.JsonMapper.builder() + .build() + .createObjectNode(); + node.put("type", "object"); + if (properties != null) { + ObjectNode propsNode = node.putObject("properties"); + for (Map.Entry entry : properties.entrySet()) { + ObjectNode propNode = propsNode.putObject(entry.getKey()); + Property prop = entry.getValue(); + propNode.put("type", prop.getType()); + propNode.put("description", prop.getDescription()); + if (prop.getEnumValues() != null) { + propNode.putArray("enum") + .addAll( + prop.getEnumValues().stream() + .map( + com.fasterxml.jackson.databind.node.TextNode + ::new) + .collect(java.util.stream.Collectors.toList())); + } + } + } + if (required != null && !required.isEmpty()) { + node.putArray("required") + .addAll( + required.stream() + .map(com.fasterxml.jackson.databind.node.TextNode::new) + .collect(java.util.stream.Collectors.toList())); + } + return node.toPrettyString(); } } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Property { + private String type; + private String description; + + @JsonProperty("enum") + @JsonInclude(JsonInclude.Include.NON_NULL) + private List enumValues; + + @JsonIgnore private boolean required; + } } diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java index c18dda6..8575356 100644 --- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java +++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java @@ -10,1710 +10,1835 @@ package io.github.ollama4j.integrationtests; import static org.junit.jupiter.api.Assertions.*; -import io.github.ollama4j.OllamaAPI; -import io.github.ollama4j.exceptions.OllamaBaseException; -import io.github.ollama4j.impl.ConsoleOutputChatTokenHandler; -import io.github.ollama4j.impl.ConsoleOutputGenerateTokenHandler; import io.github.ollama4j.models.chat.*; -import io.github.ollama4j.models.embeddings.OllamaEmbedRequestModel; -import io.github.ollama4j.models.embeddings.OllamaEmbedResponseModel; -import io.github.ollama4j.models.generate.OllamaGenerateRequest; -import io.github.ollama4j.models.generate.OllamaGenerateRequestBuilder; -import io.github.ollama4j.models.generate.OllamaGenerateStreamObserver; -import io.github.ollama4j.models.response.Model; -import io.github.ollama4j.models.response.ModelDetail; -import io.github.ollama4j.models.response.OllamaResult; import io.github.ollama4j.samples.AnnotatedTool; -import io.github.ollama4j.tools.OllamaToolCallsFunction; -import io.github.ollama4j.tools.ToolFunction; -import io.github.ollama4j.tools.Tools; import io.github.ollama4j.tools.annotations.OllamaToolService; -import io.github.ollama4j.utils.OptionsBuilder; -import java.io.File; -import java.io.IOException; import java.util.*; -import java.util.concurrent.CountDownLatch; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.ollama.OllamaContainer; @OllamaToolService(providers = {AnnotatedTool.class}) @TestMethodOrder(OrderAnnotation.class) @SuppressWarnings({"HttpUrlsUsage", "SpellCheckingInspection", "FieldCanBeLocal", "ConstantValue"}) class OllamaAPIIntegrationTest { private static final Logger LOG = LoggerFactory.getLogger(OllamaAPIIntegrationTest.class); - - private static OllamaContainer ollama; - private static OllamaAPI api; - - private static final String EMBEDDING_MODEL = "all-minilm"; - private static final String VISION_MODEL = "moondream:1.8b"; - private static final String THINKING_TOOL_MODEL = "deepseek-r1:1.5b"; - private static final String THINKING_TOOL_MODEL_2 = "qwen3:0.6b"; - private static final String GENERAL_PURPOSE_MODEL = "gemma3:270m"; - private static final String TOOLS_MODEL = "mistral:7b"; - - /** - * Initializes the OllamaAPI instance for integration tests. - * - *

This method sets up the OllamaAPI client, either using an external Ollama host (if - * environment variables are set) or by starting a Testcontainers-based Ollama instance. It also - * configures request timeout and model pull retry settings. - */ - @BeforeAll - static void setUp() { - // ... (no javadoc needed for private setup logic) - int requestTimeoutSeconds = 60; - int numberOfRetriesForModelPull = 5; - - try { - String useExternalOllamaHostEnv = System.getenv("USE_EXTERNAL_OLLAMA_HOST"); - String ollamaHostEnv = System.getenv("OLLAMA_HOST"); - - boolean useExternalOllamaHost; - String ollamaHost; - - if (useExternalOllamaHostEnv == null && ollamaHostEnv == null) { - Properties props = new Properties(); - try { - props.load( - OllamaAPIIntegrationTest.class - .getClassLoader() - .getResourceAsStream("test-config.properties")); - } catch (Exception e) { - throw new RuntimeException( - "Could not load test-config.properties from classpath", e); - } - useExternalOllamaHost = - Boolean.parseBoolean( - props.getProperty("USE_EXTERNAL_OLLAMA_HOST", "false")); - ollamaHost = props.getProperty("OLLAMA_HOST"); - requestTimeoutSeconds = - Integer.parseInt(props.getProperty("REQUEST_TIMEOUT_SECONDS")); - numberOfRetriesForModelPull = - Integer.parseInt(props.getProperty("NUMBER_RETRIES_FOR_MODEL_PULL")); - } else { - useExternalOllamaHost = Boolean.parseBoolean(useExternalOllamaHostEnv); - ollamaHost = ollamaHostEnv; - } - - if (useExternalOllamaHost) { - LOG.info("Using external Ollama host: {}", ollamaHost); - api = new OllamaAPI(ollamaHost); - } else { - throw new RuntimeException( - "USE_EXTERNAL_OLLAMA_HOST is not set so, we will be using Testcontainers" - + " Ollama host for the tests now. If you would like to use an external" - + " host, please set the env var to USE_EXTERNAL_OLLAMA_HOST=true and" - + " set the env var OLLAMA_HOST=http://localhost:11435 or a different" - + " host/port."); - } - } catch (Exception e) { - String ollamaVersion = "0.6.1"; - int internalPort = 11434; - int mappedPort = 11435; - ollama = new OllamaContainer("ollama/ollama:" + ollamaVersion); - ollama.addExposedPort(internalPort); - List portBindings = new ArrayList<>(); - portBindings.add(mappedPort + ":" + internalPort); - ollama.setPortBindings(portBindings); - ollama.start(); - LOG.info("Using Testcontainer Ollama host..."); - api = - new OllamaAPI( - "http://" - + ollama.getHost() - + ":" - + ollama.getMappedPort(internalPort)); - } - api.setRequestTimeoutSeconds(requestTimeoutSeconds); - api.setNumberOfRetriesForModelPull(numberOfRetriesForModelPull); - } - - /** - * Verifies that a ConnectException is thrown when attempting to connect to a non-existent - * Ollama endpoint. - * - *

Scenario: Ensures the API client fails gracefully when the Ollama server is unreachable. - */ - @Test - @Order(1) - void shouldThrowConnectExceptionForWrongEndpoint() { - OllamaAPI ollamaAPI = new OllamaAPI("http://wrong-host:11434"); - assertThrows(OllamaBaseException.class, ollamaAPI::listModels); - } - - /** - * Tests retrieval of the Ollama server version. - * - *

Scenario: Calls the /api/version endpoint and asserts a non-null version string is - * returned. - */ - @Test - @Order(1) - void shouldReturnVersionFromVersionAPI() throws OllamaBaseException { - String version = api.getVersion(); - assertNotNull(version); - } - - /** - * Tests the /api/ping endpoint for server liveness. - * - *

Scenario: Ensures the Ollama server responds to ping requests. - */ - @Test - @Order(1) - void shouldPingSuccessfully() throws OllamaBaseException { - boolean pingResponse = api.ping(); - assertTrue(pingResponse, "Ping should return true"); - } - - /** - * Tests listing all available models from the Ollama server. - * - *

Scenario: Calls /api/tags and verifies the returned list is not null (may be empty). - */ - @Test - @Order(2) - void shouldListModels() throws OllamaBaseException { - List models = api.listModels(); - assertNotNull(models, "Models should not be null"); - assertTrue(models.size() >= 0, "Models list can be empty or contain elements"); - } - - @Test - @Order(2) - void shouldUnloadModel() { - final String model = GENERAL_PURPOSE_MODEL; - assertDoesNotThrow( - () -> api.unloadModel(model), "unloadModel should not throw any exception"); - } - - /** - * Tests pulling a model and verifying it appears in the model list. - * - *

Scenario: Pulls an embedding model, then checks that it is present in the list of models. - */ - @Test - @Order(3) - void shouldPullModelAndListModels() throws OllamaBaseException { - api.pullModel(EMBEDDING_MODEL); - List models = api.listModels(); - assertNotNull(models, "Models should not be null"); - assertFalse(models.isEmpty(), "Models list should contain elements"); - } - - /** - * Tests fetching detailed information for a specific model. - * - *

Scenario: Pulls a model and retrieves its details, asserting the model file contains the - * model name. - */ - @Test - @Order(4) - void shouldGetModelDetails() throws OllamaBaseException { - api.pullModel(EMBEDDING_MODEL); - ModelDetail modelDetails = api.getModelDetails(EMBEDDING_MODEL); - assertNotNull(modelDetails); - assertTrue(modelDetails.getModelFile().contains(EMBEDDING_MODEL)); - } - - /** - * Tests generating embeddings for a batch of input texts. - * - *

Scenario: Uses the embedding model to generate vector embeddings for two input sentences. - */ - @Test - @Order(5) - void shouldReturnEmbeddings() throws Exception { - api.pullModel(EMBEDDING_MODEL); - OllamaEmbedRequestModel m = new OllamaEmbedRequestModel(); - m.setModel(EMBEDDING_MODEL); - m.setInput(Arrays.asList("Why is the sky blue?", "Why is the grass green?")); - OllamaEmbedResponseModel embeddings = api.embed(m); - assertNotNull(embeddings, "Embeddings should not be null"); - assertFalse(embeddings.getEmbeddings().isEmpty(), "Embeddings should not be empty"); - } - - /** - * Tests generating structured output using the 'format' parameter. - * - *

Scenario: Calls generateWithFormat with a prompt and a JSON schema, expecting a structured - * response. Usage: generate with format, no thinking, no streaming. - */ - @Test - @Order(6) - void shouldGenerateWithStructuredOutput() throws OllamaBaseException { - api.pullModel(TOOLS_MODEL); - - String prompt = - "The sun is shining brightly and is directly overhead at the zenith, casting my" - + " shadow over my foot, so it must be noon."; - - Map format = new HashMap<>(); - format.put("type", "object"); - format.put( - "properties", - new HashMap() { - { - put( - "isNoon", - new HashMap() { - { - put("type", "boolean"); - } - }); - } - }); - format.put("required", List.of("isNoon")); - - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(TOOLS_MODEL) - .withPrompt(prompt) - .withFormat(format) - .build(); - OllamaGenerateStreamObserver handler = null; - OllamaResult result = api.generate(request, handler); - - assertNotNull(result); - assertNotNull(result.getResponse()); - assertFalse(result.getResponse().isEmpty()); - assertNotNull(result.getStructuredResponse().get("isNoon")); - } - - /** - * Tests basic text generation with default options. - * - *

Scenario: Calls generate with a general-purpose model, no thinking, no streaming, no - * format. Usage: generate, raw=false, think=false, no streaming. - */ - @Test - @Order(6) - void shouldGenerateWithDefaultOptions() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - boolean raw = false; - boolean thinking = false; - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(GENERAL_PURPOSE_MODEL) - .withPrompt( - "What is the capital of France? And what's France's connection with" - + " Mona Lisa?") - .withRaw(raw) - .withThink(thinking) - .withOptions(new OptionsBuilder().build()) - .build(); - OllamaGenerateStreamObserver handler = null; - OllamaResult result = api.generate(request, handler); - assertNotNull(result); - assertNotNull(result.getResponse()); - assertFalse(result.getResponse().isEmpty()); - } - - /** - * Tests text generation with streaming enabled. - * - *

Scenario: Calls generate with a general-purpose model, streaming the response tokens. - * Usage: generate, raw=false, think=false, streaming enabled. - */ - @Test - @Order(7) - void shouldGenerateWithDefaultOptionsStreamed() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - boolean raw = false; - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(GENERAL_PURPOSE_MODEL) - .withPrompt( - "What is the capital of France? And what's France's connection with" - + " Mona Lisa?") - .withRaw(raw) - .withThink(false) - .withOptions(new OptionsBuilder().build()) - .build(); - OllamaGenerateStreamObserver handler = null; - OllamaResult result = - api.generate( - request, - new OllamaGenerateStreamObserver( - null, new ConsoleOutputGenerateTokenHandler())); - assertNotNull(result); - assertNotNull(result.getResponse()); - assertFalse(result.getResponse().isEmpty()); - } - - /** - * Tests chat API with custom options (e.g., temperature). - * - *

Scenario: Builds a chat request with system and user messages, sets a custom temperature, - * and verifies the response. Usage: chat, no tools, no thinking, no streaming, custom options. - */ - @Test - @Order(8) - void shouldGenerateWithCustomOptions() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.SYSTEM, - "You are a helpful assistant who can generate random person's first" - + " and last names in the format [First name, Last name].") - .build(); - requestModel = - builder.withMessages(requestModel.getMessages()) - .withMessage(OllamaChatMessageRole.USER, "Give me a cool name") - .withOptions(new OptionsBuilder().setTemperature(0.5f).build()) - .build(); - OllamaChatResult chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertFalse(chatResult.getResponseModel().getMessage().getResponse().isEmpty()); - } - - /** - * Tests chat API with a system prompt and verifies the assistant's response. - * - *

Scenario: Sends a system prompt instructing the assistant to reply with a specific word, - * then checks the response. Usage: chat, no tools, no thinking, no streaming, system prompt. - */ - @Test - @Order(9) - void shouldChatWithSystemPrompt() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - - String expectedResponse = "Bhai"; - - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.SYSTEM, - String.format( - "[INSTRUCTION-START] You are an obidient and helpful bot" - + " named %s. You always answer with only one word and" - + " that word is your name. [INSTRUCTION-END]", - expectedResponse)) - .withMessage(OllamaChatMessageRole.USER, "Who are you?") - .withOptions(new OptionsBuilder().setTemperature(0.0f).build()) - .build(); - - OllamaChatResult chatResult = api.chat(requestModel, null); - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertNotNull(chatResult.getResponseModel().getMessage()); - assertFalse(chatResult.getResponseModel().getMessage().getResponse().isBlank()); - assertTrue( - chatResult - .getResponseModel() - .getMessage() - .getResponse() - .contains(expectedResponse)); - assertEquals(3, chatResult.getChatHistory().size()); - } - - /** - * Tests chat API with multi-turn conversation (chat history). - * - *

Scenario: Sends a sequence of user messages, each time including the chat history, and - * verifies the assistant's responses. Usage: chat, no tools, no thinking, no streaming, - * multi-turn. - */ - @Test - @Order(10) - void shouldChatWithHistory() throws Exception { - api.pullModel(THINKING_TOOL_MODEL); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(THINKING_TOOL_MODEL); - - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, "What is 1+1? Answer only in numbers.") - .build(); - - OllamaChatResult chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult); - assertNotNull(chatResult.getChatHistory()); - assertNotNull(chatResult.getChatHistory().stream()); - - requestModel = - builder.withMessages(chatResult.getChatHistory()) - .withMessage(OllamaChatMessageRole.USER, "And what is its squared value?") - .build(); - - chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult); - assertNotNull(chatResult.getChatHistory()); - assertNotNull(chatResult.getChatHistory().stream()); - - requestModel = - builder.withMessages(chatResult.getChatHistory()) - .withMessage( - OllamaChatMessageRole.USER, - "What is the largest value between 2, 4 and 6?") - .build(); - - chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult, "Chat result should not be null"); - assertTrue( - chatResult.getChatHistory().size() > 2, - "Chat history should contain more than two messages"); - } - - /** - * Tests chat API with explicit tool invocation (client does not handle tools). - * - *

Scenario: Registers a tool, sends a user message that triggers a tool call, and verifies - * the tool call and arguments. Usage: chat, explicit tool, useTools=false, no thinking, no - * streaming. - */ - @Test - @Order(11) - void shouldChatWithExplicitTool() throws OllamaBaseException { - String theToolModel = TOOLS_MODEL; - api.pullModel(theToolModel); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(theToolModel); - - api.registerTool(employeeFinderTool()); - - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "Give me the ID and address of the employee Rahul Kumar.") - .build(); - requestModel.setOptions(new OptionsBuilder().setTemperature(0.9f).build().getOptionsMap()); - requestModel.setUseTools(true); - OllamaChatResult chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult, "chatResult should not be null"); - assertNotNull(chatResult.getResponseModel(), "Response model should not be null"); - assertNotNull( - chatResult.getResponseModel().getMessage(), "Response message should not be null"); - assertEquals( - OllamaChatMessageRole.ASSISTANT.getRoleName(), - chatResult.getResponseModel().getMessage().getRole().getRoleName(), - "Role of the response message should be ASSISTANT"); - List toolCalls = chatResult.getChatHistory().get(1).getToolCalls(); - assert (!toolCalls.isEmpty()); - OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); - assertEquals( - "get-employee-details", - function.getName(), - "Tool function name should be 'get-employee-details'"); - assertFalse( - function.getArguments().isEmpty(), "Tool function arguments should not be empty"); - Object employeeName = function.getArguments().get("employee-name"); - assertNotNull(employeeName, "Employee name argument should not be null"); - assertEquals("Rahul Kumar", employeeName, "Employee name argument should be 'Rahul Kumar'"); - assertTrue( - chatResult.getChatHistory().size() > 2, - "Chat history should have more than 2 messages"); - List finalToolCalls = - chatResult.getResponseModel().getMessage().getToolCalls(); - assertNull(finalToolCalls, "Final tool calls in the response message should be null"); - } - - /** - * Tests chat API with explicit tool invocation and useTools=true. - * - *

Scenario: Registers a tool, enables useTools, sends a user message, and verifies the - * assistant's tool call. Usage: chat, explicit tool, useTools=true, no thinking, no streaming. - */ - @Test - @Order(13) - void shouldChatWithExplicitToolAndUseTools() throws OllamaBaseException { - String theToolModel = TOOLS_MODEL; - api.pullModel(theToolModel); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(theToolModel); - - api.registerTool(employeeFinderTool()); - - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "Give me the ID and address of the employee Rahul Kumar.") - .build(); - requestModel.setOptions(new OptionsBuilder().setTemperature(0.9f).build().getOptionsMap()); - requestModel.setUseTools(true); - OllamaChatResult chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult, "chatResult should not be null"); - assertNotNull(chatResult.getResponseModel(), "Response model should not be null"); - assertNotNull( - chatResult.getResponseModel().getMessage(), "Response message should not be null"); - assertEquals( - OllamaChatMessageRole.ASSISTANT.getRoleName(), - chatResult.getResponseModel().getMessage().getRole().getRoleName(), - "Role of the response message should be ASSISTANT"); - - boolean toolCalled = false; - List msgs = chatResult.getChatHistory(); - for (OllamaChatMessage msg : msgs) { - if (msg.getRole().equals(OllamaChatMessageRole.TOOL)) { - toolCalled = true; - } - } - assertTrue(toolCalled, "Assistant message should contain tool calls when useTools is true"); - } - - /** - * Tests chat API with explicit tool invocation and streaming enabled. - * - *

Scenario: Registers a tool, sends a user message, and streams the assistant's response - * (with tool call). Usage: chat, explicit tool, useTools=false, streaming enabled. - */ - @Test - @Order(14) - void shouldChatWithToolsAndStream() throws OllamaBaseException { - String theToolModel = TOOLS_MODEL; - api.pullModel(theToolModel); - - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(theToolModel); - - api.registerTool(employeeFinderTool()); - - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "Give me the ID and address of employee Rahul Kumar") - .withKeepAlive("0m") - .withOptions(new OptionsBuilder().setTemperature(0.9f).build()) - .build(); - requestModel.setUseTools(true); - OllamaChatResult chatResult = api.chat(requestModel, new ConsoleOutputChatTokenHandler()); - - assertNotNull(chatResult, "chatResult should not be null"); - assertNotNull(chatResult.getResponseModel(), "Response model should not be null"); - assertNotNull( - chatResult.getResponseModel().getMessage(), "Response message should not be null"); - assertEquals( - OllamaChatMessageRole.ASSISTANT.getRoleName(), - chatResult.getResponseModel().getMessage().getRole().getRoleName(), - "Role of the response message should be ASSISTANT"); - List toolCalls = chatResult.getChatHistory().get(1).getToolCalls(); - assertEquals( - 1, - toolCalls.size(), - "There should be exactly one tool call in the second chat history message"); - OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); - assertEquals( - "get-employee-details", - function.getName(), - "Tool function name should be 'get-employee-details'"); - assertFalse( - function.getArguments().isEmpty(), "Tool function arguments should not be empty"); - assertTrue( - chatResult.getChatHistory().size() > 2, - "Chat history should have more than 2 messages"); - List finalToolCalls = - chatResult.getResponseModel().getMessage().getToolCalls(); - assertNull(finalToolCalls, "Final tool calls in the response message should be null"); - } - - /** - * Tests chat API with an annotated tool (single parameter). - * - *

Scenario: Registers annotated tools, sends a user message that triggers a tool call, and - * verifies the tool call and arguments. Usage: chat, annotated tool, no thinking, no streaming. - */ - @Test - @Order(12) - void shouldChatWithAnnotatedToolSingleParam() throws OllamaBaseException { - String theToolModel = TOOLS_MODEL; - api.pullModel(theToolModel); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(theToolModel); - - api.registerAnnotatedTools(); - - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "Compute the most important constant in the world using 5 digits") - .build(); - requestModel.setUseTools(true); - OllamaChatResult chatResult = api.chat(requestModel, null); - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertNotNull(chatResult.getResponseModel().getMessage()); - assertEquals( - OllamaChatMessageRole.ASSISTANT.getRoleName(), - chatResult.getResponseModel().getMessage().getRole().getRoleName()); - List toolCalls = chatResult.getChatHistory().get(1).getToolCalls(); - assert (!toolCalls.isEmpty()); - OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); - assertEquals("computeImportantConstant", function.getName()); - assert (!function.getArguments().isEmpty()); - Object noOfDigits = function.getArguments().get("noOfDigits"); - assertNotNull(noOfDigits); - assertEquals("5", noOfDigits.toString()); - assertTrue(chatResult.getChatHistory().size() > 2); - List finalToolCalls = - chatResult.getResponseModel().getMessage().getToolCalls(); - assertNull(finalToolCalls); - } - - /** - * Tests chat API with an annotated tool (multiple parameters). - * - *

Scenario: Registers annotated tools, sends a user message that may trigger a tool call - * with multiple arguments. Usage: chat, annotated tool, no thinking, no streaming, multiple - * parameters. - * - *

Note: This test is non-deterministic due to model variability; some assertions are - * commented out. - */ - @Test - @Order(13) - void shouldChatWithAnnotatedToolMultipleParams() throws OllamaBaseException { - String theToolModel = TOOLS_MODEL; - api.pullModel(theToolModel); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(theToolModel); - - api.registerAnnotatedTools(new AnnotatedTool()); - - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "Greet Rahul with a lot of hearts and respond to me with count of" - + " emojis that have been in used in the greeting") - .build(); - - OllamaChatResult chatResult = api.chat(requestModel, null); - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertNotNull(chatResult.getResponseModel().getMessage()); - assertEquals( - OllamaChatMessageRole.ASSISTANT.getRoleName(), - chatResult.getResponseModel().getMessage().getRole().getRoleName()); - } - - /** - * Tests chat API with streaming enabled (no tools, no thinking). - * - *

Scenario: Sends a user message and streams the assistant's response. Usage: chat, no - * tools, no thinking, streaming enabled. - */ - @Test - @Order(15) - void shouldChatWithStream() throws OllamaBaseException { - api.deregisterTools(); - api.pullModel(GENERAL_PURPOSE_MODEL); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "What is the capital of France? And what's France's connection with" - + " Mona Lisa?") - .build(); - requestModel.setThink(false); - - OllamaChatResult chatResult = api.chat(requestModel, new ConsoleOutputChatTokenHandler()); - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertNotNull(chatResult.getResponseModel().getMessage()); - assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); - } - - /** - * Tests chat API with thinking and streaming enabled. - * - *

Scenario: Sends a user message with thinking enabled and streams the assistant's response. - * Usage: chat, no tools, thinking enabled, streaming enabled. - */ - @Test - @Order(15) - void shouldChatWithThinkingAndStream() throws OllamaBaseException { - api.pullModel(THINKING_TOOL_MODEL_2); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(THINKING_TOOL_MODEL_2); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "What is the capital of France? And what's France's connection with" - + " Mona Lisa?") - .withThinking(true) - .withKeepAlive("0m") - .build(); - - OllamaChatResult chatResult = api.chat(requestModel, new ConsoleOutputChatTokenHandler()); - - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertNotNull(chatResult.getResponseModel().getMessage()); - assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); - } - - /** - * Tests chat API with an image input from a URL. - * - *

Scenario: Sends a user message with an image URL and verifies the assistant's response. - * Usage: chat, vision model, image from URL, no tools, no thinking, no streaming. - */ - @Test - @Order(10) - void shouldChatWithImageFromURL() - throws OllamaBaseException, IOException, InterruptedException { - api.pullModel(VISION_MODEL); - - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(VISION_MODEL); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "What's in the picture?", - Collections.emptyList(), - "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg") - .build(); - api.registerAnnotatedTools(new OllamaAPIIntegrationTest()); - - OllamaChatResult chatResult = api.chat(requestModel, null); - assertNotNull(chatResult); - } - - /** - * Tests chat API with an image input from a file and multi-turn history. - * - *

Scenario: Sends a user message with an image file, then continues the conversation with - * chat history. Usage: chat, vision model, image from file, multi-turn, no tools, no thinking, - * no streaming. - */ - @Test - @Order(10) - void shouldChatWithImageFromFileAndHistory() throws OllamaBaseException { - api.pullModel(VISION_MODEL); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(VISION_MODEL); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "What's in the picture?", - Collections.emptyList(), - List.of(getImageFileFromClasspath("emoji-smile.jpeg"))) - .build(); - - OllamaChatResult chatResult = api.chat(requestModel, null); - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - builder.reset(); - - requestModel = - builder.withMessages(chatResult.getChatHistory()) - .withMessage(OllamaChatMessageRole.USER, "What's the color?") - .build(); - - chatResult = api.chat(requestModel, null); - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - } - + // + // private static OllamaContainer ollama; + // private static OllamaAPI api; + // + // private static final String EMBEDDING_MODEL = "all-minilm"; + // private static final String VISION_MODEL = "moondream:1.8b"; + // private static final String THINKING_TOOL_MODEL = "deepseek-r1:1.5b"; + // private static final String THINKING_TOOL_MODEL_2 = "qwen3:0.6b"; + // private static final String GENERAL_PURPOSE_MODEL = "gemma3:270m"; + // private static final String TOOLS_MODEL = "mistral:7b"; + // // /** - // * Tests generateWithImages using an image URL as input. + // * Initializes the OllamaAPI instance for integration tests. // * - // *

Scenario: Calls generateWithImages with a vision model and an image URL, expecting a - // * non-empty response. Usage: generateWithImages, image from URL, no streaming. + // *

This method sets up the OllamaAPI client, either using an external Ollama host (if + // * environment variables are set) or by starting a Testcontainers-based Ollama instance. + // It also + // * configures request timeout and model pull retry settings. + // */ + // @BeforeAll + // static void setUp() { + // // ... (no javadoc needed for private setup logic) + // int requestTimeoutSeconds = 60; + // int numberOfRetriesForModelPull = 5; + // + // try { + // String useExternalOllamaHostEnv = System.getenv("USE_EXTERNAL_OLLAMA_HOST"); + // String ollamaHostEnv = System.getenv("OLLAMA_HOST"); + // + // boolean useExternalOllamaHost; + // String ollamaHost; + // + // if (useExternalOllamaHostEnv == null && ollamaHostEnv == null) { + // Properties props = new Properties(); + // try { + // props.load( + // OllamaAPIIntegrationTest.class + // .getClassLoader() + // .getResourceAsStream("test-config.properties")); + // } catch (Exception e) { + // throw new RuntimeException( + // "Could not load test-config.properties from classpath", e); + // } + // useExternalOllamaHost = + // Boolean.parseBoolean( + // props.getProperty("USE_EXTERNAL_OLLAMA_HOST", "false")); + // ollamaHost = props.getProperty("OLLAMA_HOST"); + // requestTimeoutSeconds = + // Integer.parseInt(props.getProperty("REQUEST_TIMEOUT_SECONDS")); + // numberOfRetriesForModelPull = + // Integer.parseInt(props.getProperty("NUMBER_RETRIES_FOR_MODEL_PULL")); + // } else { + // useExternalOllamaHost = Boolean.parseBoolean(useExternalOllamaHostEnv); + // ollamaHost = ollamaHostEnv; + // } + // + // if (useExternalOllamaHost) { + // LOG.info("Using external Ollama host: {}", ollamaHost); + // api = new OllamaAPI(ollamaHost); + // } else { + // throw new RuntimeException( + // "USE_EXTERNAL_OLLAMA_HOST is not set so, we will be using + // Testcontainers" + // + " Ollama host for the tests now. If you would like to use an + // external" + // + " host, please set the env var to USE_EXTERNAL_OLLAMA_HOST=true + // and" + // + " set the env var OLLAMA_HOST=http://localhost:11435 or a + // different" + // + " host/port."); + // } + // } catch (Exception e) { + // String ollamaVersion = "0.6.1"; + // int internalPort = 11434; + // int mappedPort = 11435; + // ollama = new OllamaContainer("ollama/ollama:" + ollamaVersion); + // ollama.addExposedPort(internalPort); + // List portBindings = new ArrayList<>(); + // portBindings.add(mappedPort + ":" + internalPort); + // ollama.setPortBindings(portBindings); + // ollama.start(); + // LOG.info("Using Testcontainer Ollama host..."); + // api = + // new OllamaAPI( + // "http://" + // + ollama.getHost() + // + ":" + // + ollama.getMappedPort(internalPort)); + // } + // api.setRequestTimeoutSeconds(requestTimeoutSeconds); + // api.setNumberOfRetriesForModelPull(numberOfRetriesForModelPull); + // } + // + // /** + // * Verifies that a ConnectException is thrown when attempting to connect to a non-existent + // * Ollama endpoint. + // * + // *

Scenario: Ensures the API client fails gracefully when the Ollama server is + // unreachable. // */ // @Test - // @Order(17) - // void shouldGenerateWithImageURLs() - // throws OllamaBaseException { - // api.pullModel(VISION_MODEL); + // @Order(1) + // void shouldThrowConnectExceptionForWrongEndpoint() { + // OllamaAPI ollamaAPI = new OllamaAPI("http://wrong-host:11434"); + // assertThrows(OllamaBaseException.class, ollamaAPI::listModels); + // } // - // OllamaResult result = - // api.generateWithImages( - // VISION_MODEL, - // "What is in this image?", - // List.of( + // /** + // * Tests retrieval of the Ollama server version. + // * + // *

Scenario: Calls the /api/version endpoint and asserts a non-null version string is + // * returned. + // */ + // @Test + // @Order(1) + // void shouldReturnVersionFromVersionAPI() throws OllamaBaseException { + // String version = api.getVersion(); + // assertNotNull(version); + // } // - // "https://i.pinimg.com/736x/f9/4e/cb/f94ecba040696a3a20b484d2e15159ec.jpg"), - // new OptionsBuilder().build(), - // null, - // null); + // /** + // * Tests the /api/ping endpoint for server liveness. + // * + // *

Scenario: Ensures the Ollama server responds to ping requests. + // */ + // @Test + // @Order(1) + // void shouldPingSuccessfully() throws OllamaBaseException { + // boolean pingResponse = api.ping(); + // assertTrue(pingResponse, "Ping should return true"); + // } + // + // /** + // * Tests listing all available models from the Ollama server. + // * + // *

Scenario: Calls /api/tags and verifies the returned list is not null (may be empty). + // */ + // @Test + // @Order(2) + // void shouldListModels() throws OllamaBaseException { + // List models = api.listModels(); + // assertNotNull(models, "Models should not be null"); + // assertTrue(models.size() >= 0, "Models list can be empty or contain elements"); + // } + // + // @Test + // @Order(2) + // void shouldUnloadModel() { + // final String model = GENERAL_PURPOSE_MODEL; + // assertDoesNotThrow( + // () -> api.unloadModel(model), "unloadModel should not throw any exception"); + // } + // + // /** + // * Tests pulling a model and verifying it appears in the model list. + // * + // *

Scenario: Pulls an embedding model, then checks that it is present in the list of + // models. + // */ + // @Test + // @Order(3) + // void shouldPullModelAndListModels() throws OllamaBaseException { + // api.pullModel(EMBEDDING_MODEL); + // List models = api.listModels(); + // assertNotNull(models, "Models should not be null"); + // assertFalse(models.isEmpty(), "Models list should contain elements"); + // } + // + // /** + // * Tests fetching detailed information for a specific model. + // * + // *

Scenario: Pulls a model and retrieves its details, asserting the model file contains + // the + // * model name. + // */ + // @Test + // @Order(4) + // void shouldGetModelDetails() throws OllamaBaseException { + // api.pullModel(EMBEDDING_MODEL); + // ModelDetail modelDetails = api.getModelDetails(EMBEDDING_MODEL); + // assertNotNull(modelDetails); + // assertTrue(modelDetails.getModelFile().contains(EMBEDDING_MODEL)); + // } + // + // /** + // * Tests generating embeddings for a batch of input texts. + // * + // *

Scenario: Uses the embedding model to generate vector embeddings for two input + // sentences. + // */ + // @Test + // @Order(5) + // void shouldReturnEmbeddings() throws Exception { + // api.pullModel(EMBEDDING_MODEL); + // OllamaEmbedRequestModel m = new OllamaEmbedRequestModel(); + // m.setModel(EMBEDDING_MODEL); + // m.setInput(Arrays.asList("Why is the sky blue?", "Why is the grass green?")); + // OllamaEmbedResponseModel embeddings = api.embed(m); + // assertNotNull(embeddings, "Embeddings should not be null"); + // assertFalse(embeddings.getEmbeddings().isEmpty(), "Embeddings should not be empty"); + // } + // + // /** + // * Tests generating structured output using the 'format' parameter. + // * + // *

Scenario: Calls generateWithFormat with a prompt and a JSON schema, expecting a + // structured + // * response. Usage: generate with format, no thinking, no streaming. + // */ + // @Test + // @Order(6) + // void shouldGenerateWithStructuredOutput() throws OllamaBaseException { + // api.pullModel(TOOLS_MODEL); + // + // String prompt = + // "The sun is shining brightly and is directly overhead at the zenith, casting + // my" + // + " shadow over my foot, so it must be noon."; + // + // Map format = new HashMap<>(); + // format.put("type", "object"); + // format.put( + // "properties", + // new HashMap() { + // { + // put( + // "isNoon", + // new HashMap() { + // { + // put("type", "boolean"); + // } + // }); + // } + // }); + // format.put("required", List.of("isNoon")); + // + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(TOOLS_MODEL) + // .withPrompt(prompt) + // .withFormat(format) + // .build(); + // OllamaGenerateStreamObserver handler = null; + // OllamaResult result = api.generate(request, handler); + // + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // assertFalse(result.getResponse().isEmpty()); + // assertNotNull(result.getStructuredResponse().get("isNoon")); + // } + // + // /** + // * Tests basic text generation with default options. + // * + // *

Scenario: Calls generate with a general-purpose model, no thinking, no streaming, no + // * format. Usage: generate, raw=false, think=false, no streaming. + // */ + // @Test + // @Order(6) + // void shouldGenerateWithDefaultOptions() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // boolean raw = false; + // boolean thinking = false; + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(GENERAL_PURPOSE_MODEL) + // .withPrompt( + // "What is the capital of France? And what's France's connection + // with" + // + " Mona Lisa?") + // .withRaw(raw) + // .withThink(thinking) + // .withOptions(new OptionsBuilder().build()) + // .build(); + // OllamaGenerateStreamObserver handler = null; + // OllamaResult result = api.generate(request, handler); // assertNotNull(result); // assertNotNull(result.getResponse()); // assertFalse(result.getResponse().isEmpty()); // } - - /** - * Tests generateWithImages using an image file as input. - * - *

Scenario: Calls generateWithImages with a vision model and an image file, expecting a - * non-empty response. Usage: generateWithImages, image from file, no streaming. - */ - @Test - @Order(18) - void shouldGenerateWithImageFiles() throws OllamaBaseException { - api.pullModel(VISION_MODEL); - try { - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(VISION_MODEL) - .withPrompt("What is in this image?") - .withRaw(false) - .withThink(false) - .withOptions(new OptionsBuilder().build()) - .withImages(List.of(getImageFileFromClasspath("roses.jpg"))) - .withFormat(null) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = null; - OllamaResult result = api.generate(request, handler); - assertNotNull(result); - assertNotNull(result.getResponse()); - assertFalse(result.getResponse().isEmpty()); - } catch (OllamaBaseException e) { - fail(e); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Tests generateWithImages with image file input and streaming enabled. - * - *

Scenario: Calls generateWithImages with a vision model, an image file, and a streaming - * handler for the response. Usage: generateWithImages, image from file, streaming enabled. - */ - @Test - @Order(20) - void shouldGenerateWithImageFilesAndResponseStreamed() throws OllamaBaseException, IOException { - api.pullModel(VISION_MODEL); - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(VISION_MODEL) - .withPrompt("What is in this image?") - .withRaw(false) - .withThink(false) - .withOptions(new OptionsBuilder().build()) - .withImages(List.of(getImageFileFromClasspath("roses.jpg"))) - .withFormat(null) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = - new OllamaGenerateStreamObserver( - new ConsoleOutputGenerateTokenHandler(), - new ConsoleOutputGenerateTokenHandler()); - OllamaResult result = api.generate(request, handler); - assertNotNull(result); - assertNotNull(result.getResponse()); - assertFalse(result.getResponse().isEmpty()); - } - - /** - * Tests generate with thinking enabled (no streaming). - * - *

Scenario: Calls generate with think=true, expecting both response and thinking fields to - * be populated. Usage: generate, think=true, no streaming. - */ - @Test - @Order(20) - void shouldGenerateWithThinking() throws OllamaBaseException { - api.pullModel(THINKING_TOOL_MODEL); - - boolean raw = false; - boolean think = true; - - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(THINKING_TOOL_MODEL) - .withPrompt("Who are you?") - .withRaw(raw) - .withThink(think) - .withOptions(new OptionsBuilder().build()) - .withFormat(null) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); - - OllamaResult result = api.generate(request, handler); - assertNotNull(result); - assertNotNull(result.getResponse()); - assertNotNull(result.getThinking()); - } - - /** - * Tests generate with thinking and streaming enabled. - * - *

Scenario: Calls generate with think=true and a stream handler for both thinking and - * response tokens. Usage: generate, think=true, streaming enabled. - */ - @Test - @Order(20) - void shouldGenerateWithThinkingAndStreamHandler() throws OllamaBaseException { - api.pullModel(THINKING_TOOL_MODEL); - boolean raw = false; - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(THINKING_TOOL_MODEL) - .withPrompt("Who are you?") - .withRaw(raw) - .withThink(true) - .withOptions(new OptionsBuilder().build()) - .withFormat(null) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = - new OllamaGenerateStreamObserver( - thinkingToken -> { - LOG.info(thinkingToken.toUpperCase()); - }, - resToken -> { - LOG.info(resToken.toLowerCase()); - }); - - OllamaResult result = api.generate(request, handler); - assertNotNull(result); - assertNotNull(result.getResponse()); - assertNotNull(result.getThinking()); - } - - /** - * Tests generate with raw=true parameter. - * - *

Scenario: Calls generate with raw=true, which sends the prompt as-is without any - * formatting. Usage: generate, raw=true, no thinking, no streaming. - */ - @Test - @Order(21) - void shouldGenerateWithRawMode() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - api.unloadModel(GENERAL_PURPOSE_MODEL); - boolean raw = true; - boolean thinking = false; - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(GENERAL_PURPOSE_MODEL) - .withPrompt("What is 2+2?") - .withRaw(raw) - .withThink(thinking) - .withOptions(new OptionsBuilder().build()) - .withFormat(null) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); - OllamaResult result = api.generate(request, handler); - assertNotNull(result); - assertNotNull(result.getResponse()); - assertFalse(result.getResponse().isEmpty()); - } - - /** - * Tests generate with raw=true and streaming enabled. - * - *

Scenario: Calls generate with raw=true and streams the response. Usage: generate, - * raw=true, no thinking, streaming enabled. - */ - @Test - @Order(22) - void shouldGenerateWithRawModeAndStreaming() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - boolean raw = true; - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(GENERAL_PURPOSE_MODEL) - .withPrompt("What is the largest planet in our solar system?") - .withRaw(raw) - .withThink(false) - .withOptions(new OptionsBuilder().build()) - .withFormat(null) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = - new OllamaGenerateStreamObserver(null, new ConsoleOutputGenerateTokenHandler()); - OllamaResult result = api.generate(request, handler); - - assertNotNull(result); - assertNotNull(result.getResponse()); - assertFalse(result.getResponse().isEmpty()); - } - + // // /** - // * Tests generate with raw=true and thinking enabled. + // * Tests text generation with streaming enabled. // * - // *

Scenario: Calls generate with raw=true and think=true combination. Usage: generate, - // * raw=true, thinking enabled, no streaming. + // *

Scenario: Calls generate with a general-purpose model, streaming the response + // tokens. + // * Usage: generate, raw=false, think=false, streaming enabled. // */ // @Test - // @Order(23) - // void shouldGenerateWithRawModeAndThinking() - // throws OllamaBaseException - // { - // api.pullModel(THINKING_TOOL_MODEL_2); - // api.unloadModel(THINKING_TOOL_MODEL_2); - // boolean raw = - // true; // if true no formatting will be applied to the prompt. You may choose - // to use - // // the raw parameter if you are specifying a full templated prompt in your - // // request to the API - // boolean thinking = true; + // @Order(7) + // void shouldGenerateWithDefaultOptionsStreamed() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // boolean raw = false; + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(GENERAL_PURPOSE_MODEL) + // .withPrompt( + // "What is the capital of France? And what's France's connection + // with" + // + " Mona Lisa?") + // .withRaw(raw) + // .withThink(false) + // .withOptions(new OptionsBuilder().build()) + // .build(); + // OllamaGenerateStreamObserver handler = null; // OllamaResult result = // api.generate( - // THINKING_TOOL_MODEL_2, - // "Validate: 1+1=2", - // raw, - // thinking, - // new OptionsBuilder().build(), - // new OllamaGenerateStreamObserver(null, null)); + // request, + // new OllamaGenerateStreamObserver( + // null, new ConsoleOutputGenerateTokenHandler())); + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // assertFalse(result.getResponse().isEmpty()); + // } + // + // /** + // * Tests chat API with custom options (e.g., temperature). + // * + // *

Scenario: Builds a chat request with system and user messages, sets a custom + // temperature, + // * and verifies the response. Usage: chat, no tools, no thinking, no streaming, custom + // options. + // */ + // @Test + // @Order(8) + // void shouldGenerateWithCustomOptions() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.SYSTEM, + // "You are a helpful assistant who can generate random person's + // first" + // + " and last names in the format [First name, Last + // name].") + // .build(); + // requestModel = + // builder.withMessages(requestModel.getMessages()) + // .withMessage(OllamaChatMessageRole.USER, "Give me a cool name") + // .withOptions(new OptionsBuilder().setTemperature(0.5f).build()) + // .build(); + // OllamaChatResult chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertFalse(chatResult.getResponseModel().getMessage().getResponse().isEmpty()); + // } + // + // /** + // * Tests chat API with a system prompt and verifies the assistant's response. + // * + // *

Scenario: Sends a system prompt instructing the assistant to reply with a specific + // word, + // * then checks the response. Usage: chat, no tools, no thinking, no streaming, system + // prompt. + // */ + // @Test + // @Order(9) + // void shouldChatWithSystemPrompt() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // + // String expectedResponse = "Bhai"; + // + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.SYSTEM, + // String.format( + // "[INSTRUCTION-START] You are an obidient and helpful + // bot" + // + " named %s. You always answer with only one word + // and" + // + " that word is your name. [INSTRUCTION-END]", + // expectedResponse)) + // .withMessage(OllamaChatMessageRole.USER, "Who are you?") + // .withOptions(new OptionsBuilder().setTemperature(0.0f).build()) + // .build(); + // + // OllamaChatResult chatResult = api.chat(requestModel, null); + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertNotNull(chatResult.getResponseModel().getMessage()); + // assertFalse(chatResult.getResponseModel().getMessage().getResponse().isBlank()); + // assertTrue( + // chatResult + // .getResponseModel() + // .getMessage() + // .getResponse() + // .contains(expectedResponse)); + // assertEquals(3, chatResult.getChatHistory().size()); + // } + // + // /** + // * Tests chat API with multi-turn conversation (chat history). + // * + // *

Scenario: Sends a sequence of user messages, each time including the chat history, + // and + // * verifies the assistant's responses. Usage: chat, no tools, no thinking, no streaming, + // * multi-turn. + // */ + // @Test + // @Order(10) + // void shouldChatWithHistory() throws Exception { + // api.pullModel(THINKING_TOOL_MODEL); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(THINKING_TOOL_MODEL); + // + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, "What is 1+1? Answer only in + // numbers.") + // .build(); + // + // OllamaChatResult chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getChatHistory()); + // assertNotNull(chatResult.getChatHistory().stream()); + // + // requestModel = + // builder.withMessages(chatResult.getChatHistory()) + // .withMessage(OllamaChatMessageRole.USER, "And what is its squared + // value?") + // .build(); + // + // chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getChatHistory()); + // assertNotNull(chatResult.getChatHistory().stream()); + // + // requestModel = + // builder.withMessages(chatResult.getChatHistory()) + // .withMessage( + // OllamaChatMessageRole.USER, + // "What is the largest value between 2, 4 and 6?") + // .build(); + // + // chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult, "Chat result should not be null"); + // assertTrue( + // chatResult.getChatHistory().size() > 2, + // "Chat history should contain more than two messages"); + // } + // + // /** + // * Tests chat API with explicit tool invocation (client does not handle tools). + // * + // *

Scenario: Registers a tool, sends a user message that triggers a tool call, and + // verifies + // * the tool call and arguments. Usage: chat, explicit tool, useTools=false, no thinking, + // no + // * streaming. + // */ + // @Test + // @Order(11) + // void shouldChatWithExplicitTool() throws OllamaBaseException { + // String theToolModel = TOOLS_MODEL; + // api.pullModel(theToolModel); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(theToolModel); + // + // api.registerTool(employeeFinderTool()); + // + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "Give me the ID and address of the employee Rahul Kumar.") + // .build(); + // requestModel.setOptions(new + // OptionsBuilder().setTemperature(0.9f).build().getOptionsMap()); + // requestModel.setUseTools(true); + // OllamaChatResult chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult, "chatResult should not be null"); + // assertNotNull(chatResult.getResponseModel(), "Response model should not be null"); + // assertNotNull( + // chatResult.getResponseModel().getMessage(), "Response message should not be + // null"); + // assertEquals( + // OllamaChatMessageRole.ASSISTANT.getRoleName(), + // chatResult.getResponseModel().getMessage().getRole().getRoleName(), + // "Role of the response message should be ASSISTANT"); + // List toolCalls = + // chatResult.getChatHistory().get(1).getToolCalls(); + // assert (!toolCalls.isEmpty()); + // OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); + // assertEquals( + // "get-employee-details", + // function.getName(), + // "Tool function name should be 'get-employee-details'"); + // assertFalse( + // function.getArguments().isEmpty(), "Tool function arguments should not be + // empty"); + // Object employeeName = function.getArguments().get("employee-name"); + // assertNotNull(employeeName, "Employee name argument should not be null"); + // assertEquals("Rahul Kumar", employeeName, "Employee name argument should be 'Rahul + // Kumar'"); + // assertTrue( + // chatResult.getChatHistory().size() > 2, + // "Chat history should have more than 2 messages"); + // List finalToolCalls = + // chatResult.getResponseModel().getMessage().getToolCalls(); + // assertNull(finalToolCalls, "Final tool calls in the response message should be null"); + // } + // + // /** + // * Tests chat API with explicit tool invocation and useTools=true. + // * + // *

Scenario: Registers a tool, enables useTools, sends a user message, and verifies the + // * assistant's tool call. Usage: chat, explicit tool, useTools=true, no thinking, no + // streaming. + // */ + // @Test + // @Order(13) + // void shouldChatWithExplicitToolAndUseTools() throws OllamaBaseException { + // String theToolModel = TOOLS_MODEL; + // api.pullModel(theToolModel); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(theToolModel); + // + // api.registerTool(employeeFinderTool()); + // + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "Give me the ID and address of the employee Rahul Kumar.") + // .build(); + // requestModel.setOptions(new + // OptionsBuilder().setTemperature(0.9f).build().getOptionsMap()); + // requestModel.setUseTools(true); + // OllamaChatResult chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult, "chatResult should not be null"); + // assertNotNull(chatResult.getResponseModel(), "Response model should not be null"); + // assertNotNull( + // chatResult.getResponseModel().getMessage(), "Response message should not be + // null"); + // assertEquals( + // OllamaChatMessageRole.ASSISTANT.getRoleName(), + // chatResult.getResponseModel().getMessage().getRole().getRoleName(), + // "Role of the response message should be ASSISTANT"); + // + // boolean toolCalled = false; + // List msgs = chatResult.getChatHistory(); + // for (OllamaChatMessage msg : msgs) { + // if (msg.getRole().equals(OllamaChatMessageRole.TOOL)) { + // toolCalled = true; + // } + // } + // assertTrue(toolCalled, "Assistant message should contain tool calls when useTools is + // true"); + // } + // + // /** + // * Tests chat API with explicit tool invocation and streaming enabled. + // * + // *

Scenario: Registers a tool, sends a user message, and streams the assistant's + // response + // * (with tool call). Usage: chat, explicit tool, useTools=false, streaming enabled. + // */ + // @Test + // @Order(14) + // void shouldChatWithToolsAndStream() throws OllamaBaseException { + // String theToolModel = TOOLS_MODEL; + // api.pullModel(theToolModel); + // + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(theToolModel); + // + // api.registerTool(employeeFinderTool()); + // + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "Give me the ID and address of employee Rahul Kumar") + // .withKeepAlive("0m") + // .withOptions(new OptionsBuilder().setTemperature(0.9f).build()) + // .build(); + // requestModel.setUseTools(true); + // OllamaChatResult chatResult = api.chat(requestModel, new + // ConsoleOutputChatTokenHandler()); + // + // assertNotNull(chatResult, "chatResult should not be null"); + // assertNotNull(chatResult.getResponseModel(), "Response model should not be null"); + // assertNotNull( + // chatResult.getResponseModel().getMessage(), "Response message should not be + // null"); + // assertEquals( + // OllamaChatMessageRole.ASSISTANT.getRoleName(), + // chatResult.getResponseModel().getMessage().getRole().getRoleName(), + // "Role of the response message should be ASSISTANT"); + // List toolCalls = + // chatResult.getChatHistory().get(1).getToolCalls(); + // assertEquals( + // 1, + // toolCalls.size(), + // "There should be exactly one tool call in the second chat history message"); + // OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); + // assertEquals( + // "get-employee-details", + // function.getName(), + // "Tool function name should be 'get-employee-details'"); + // assertFalse( + // function.getArguments().isEmpty(), "Tool function arguments should not be + // empty"); + // assertTrue( + // chatResult.getChatHistory().size() > 2, + // "Chat history should have more than 2 messages"); + // List finalToolCalls = + // chatResult.getResponseModel().getMessage().getToolCalls(); + // assertNull(finalToolCalls, "Final tool calls in the response message should be null"); + // } + // + // /** + // * Tests chat API with an annotated tool (single parameter). + // * + // *

Scenario: Registers annotated tools, sends a user message that triggers a tool call, + // and + // * verifies the tool call and arguments. Usage: chat, annotated tool, no thinking, no + // streaming. + // */ + // @Test + // @Order(12) + // void shouldChatWithAnnotatedToolSingleParam() throws OllamaBaseException { + // String theToolModel = TOOLS_MODEL; + // api.pullModel(theToolModel); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(theToolModel); + // + // api.registerAnnotatedTools(); + // + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "Compute the most important constant in the world using 5 + // digits") + // .build(); + // requestModel.setUseTools(true); + // OllamaChatResult chatResult = api.chat(requestModel, null); + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertNotNull(chatResult.getResponseModel().getMessage()); + // assertEquals( + // OllamaChatMessageRole.ASSISTANT.getRoleName(), + // chatResult.getResponseModel().getMessage().getRole().getRoleName()); + // List toolCalls = + // chatResult.getChatHistory().get(1).getToolCalls(); + // assert (!toolCalls.isEmpty()); + // OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); + // assertEquals("computeImportantConstant", function.getName()); + // assert (!function.getArguments().isEmpty()); + // Object noOfDigits = function.getArguments().get("noOfDigits"); + // assertNotNull(noOfDigits); + // assertEquals("5", noOfDigits.toString()); + // assertTrue(chatResult.getChatHistory().size() > 2); + // List finalToolCalls = + // chatResult.getResponseModel().getMessage().getToolCalls(); + // assertNull(finalToolCalls); + // } + // + // /** + // * Tests chat API with an annotated tool (multiple parameters). + // * + // *

Scenario: Registers annotated tools, sends a user message that may trigger a tool + // call + // * with multiple arguments. Usage: chat, annotated tool, no thinking, no streaming, + // multiple + // * parameters. + // * + // *

Note: This test is non-deterministic due to model variability; some assertions are + // * commented out. + // */ + // @Test + // @Order(13) + // void shouldChatWithAnnotatedToolMultipleParams() throws OllamaBaseException { + // String theToolModel = TOOLS_MODEL; + // api.pullModel(theToolModel); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(theToolModel); + // + // api.registerAnnotatedTools(new AnnotatedTool()); + // + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "Greet Rahul with a lot of hearts and respond to me with count + // of" + // + " emojis that have been in used in the greeting") + // .build(); + // + // OllamaChatResult chatResult = api.chat(requestModel, null); + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertNotNull(chatResult.getResponseModel().getMessage()); + // assertEquals( + // OllamaChatMessageRole.ASSISTANT.getRoleName(), + // chatResult.getResponseModel().getMessage().getRole().getRoleName()); + // } + // + // /** + // * Tests chat API with streaming enabled (no tools, no thinking). + // * + // *

Scenario: Sends a user message and streams the assistant's response. Usage: chat, no + // * tools, no thinking, streaming enabled. + // */ + // @Test + // @Order(15) + // void shouldChatWithStream() throws OllamaBaseException { + // api.deregisterTools(); + // api.pullModel(GENERAL_PURPOSE_MODEL); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "What is the capital of France? And what's France's connection + // with" + // + " Mona Lisa?") + // .build(); + // requestModel.setThink(false); + // + // OllamaChatResult chatResult = api.chat(requestModel, new + // ConsoleOutputChatTokenHandler()); + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertNotNull(chatResult.getResponseModel().getMessage()); + // assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); + // } + // + // /** + // * Tests chat API with thinking and streaming enabled. + // * + // *

Scenario: Sends a user message with thinking enabled and streams the assistant's + // response. + // * Usage: chat, no tools, thinking enabled, streaming enabled. + // */ + // @Test + // @Order(15) + // void shouldChatWithThinkingAndStream() throws OllamaBaseException { + // api.pullModel(THINKING_TOOL_MODEL_2); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(THINKING_TOOL_MODEL_2); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "What is the capital of France? And what's France's connection + // with" + // + " Mona Lisa?") + // .withThinking(true) + // .withKeepAlive("0m") + // .build(); + // + // OllamaChatResult chatResult = api.chat(requestModel, new + // ConsoleOutputChatTokenHandler()); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertNotNull(chatResult.getResponseModel().getMessage()); + // assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); + // } + // + // /** + // * Tests chat API with an image input from a URL. + // * + // *

Scenario: Sends a user message with an image URL and verifies the assistant's + // response. + // * Usage: chat, vision model, image from URL, no tools, no thinking, no streaming. + // */ + // @Test + // @Order(10) + // void shouldChatWithImageFromURL() + // throws OllamaBaseException, IOException, InterruptedException { + // api.pullModel(VISION_MODEL); + // + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(VISION_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "What's in the picture?", + // Collections.emptyList(), + // + // "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg") + // .build(); + // api.registerAnnotatedTools(new OllamaAPIIntegrationTest()); + // + // OllamaChatResult chatResult = api.chat(requestModel, null); + // assertNotNull(chatResult); + // } + // + // /** + // * Tests chat API with an image input from a file and multi-turn history. + // * + // *

Scenario: Sends a user message with an image file, then continues the conversation + // with + // * chat history. Usage: chat, vision model, image from file, multi-turn, no tools, no + // thinking, + // * no streaming. + // */ + // @Test + // @Order(10) + // void shouldChatWithImageFromFileAndHistory() throws OllamaBaseException { + // api.pullModel(VISION_MODEL); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(VISION_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "What's in the picture?", + // Collections.emptyList(), + // List.of(getImageFileFromClasspath("emoji-smile.jpeg"))) + // .build(); + // + // OllamaChatResult chatResult = api.chat(requestModel, null); + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // builder.reset(); + // + // requestModel = + // builder.withMessages(chatResult.getChatHistory()) + // .withMessage(OllamaChatMessageRole.USER, "What's the color?") + // .build(); + // + // chatResult = api.chat(requestModel, null); + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // } + // + // // /** + // // * Tests generateWithImages using an image URL as input. + // // * + // // *

Scenario: Calls generateWithImages with a vision model and an image URL, + // expecting a + // // * non-empty response. Usage: generateWithImages, image from URL, no streaming. + // // */ + // // @Test + // // @Order(17) + // // void shouldGenerateWithImageURLs() + // // throws OllamaBaseException { + // // api.pullModel(VISION_MODEL); + // // + // // OllamaResult result = + // // api.generateWithImages( + // // VISION_MODEL, + // // "What is in this image?", + // // List.of( + // // + // // "https://i.pinimg.com/736x/f9/4e/cb/f94ecba040696a3a20b484d2e15159ec.jpg"), + // // new OptionsBuilder().build(), + // // null, + // // null); + // // assertNotNull(result); + // // assertNotNull(result.getResponse()); + // // assertFalse(result.getResponse().isEmpty()); + // // } + // + // /** + // * Tests generateWithImages using an image file as input. + // * + // *

Scenario: Calls generateWithImages with a vision model and an image file, expecting + // a + // * non-empty response. Usage: generateWithImages, image from file, no streaming. + // */ + // @Test + // @Order(18) + // void shouldGenerateWithImageFiles() throws OllamaBaseException { + // api.pullModel(VISION_MODEL); + // try { + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(VISION_MODEL) + // .withPrompt("What is in this image?") + // .withRaw(false) + // .withThink(false) + // .withOptions(new OptionsBuilder().build()) + // .withImages(List.of(getImageFileFromClasspath("roses.jpg"))) + // .withFormat(null) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = null; + // OllamaResult result = api.generate(request, handler); + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // assertFalse(result.getResponse().isEmpty()); + // } catch (OllamaBaseException e) { + // fail(e); + // } catch (IOException e) { + // throw new RuntimeException(e); + // } + // } + // + // /** + // * Tests generateWithImages with image file input and streaming enabled. + // * + // *

Scenario: Calls generateWithImages with a vision model, an image file, and a + // streaming + // * handler for the response. Usage: generateWithImages, image from file, streaming + // enabled. + // */ + // @Test + // @Order(20) + // void shouldGenerateWithImageFilesAndResponseStreamed() throws OllamaBaseException, + // IOException { + // api.pullModel(VISION_MODEL); + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(VISION_MODEL) + // .withPrompt("What is in this image?") + // .withRaw(false) + // .withThink(false) + // .withOptions(new OptionsBuilder().build()) + // .withImages(List.of(getImageFileFromClasspath("roses.jpg"))) + // .withFormat(null) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = + // new OllamaGenerateStreamObserver( + // new ConsoleOutputGenerateTokenHandler(), + // new ConsoleOutputGenerateTokenHandler()); + // OllamaResult result = api.generate(request, handler); + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // assertFalse(result.getResponse().isEmpty()); + // } + // + // /** + // * Tests generate with thinking enabled (no streaming). + // * + // *

Scenario: Calls generate with think=true, expecting both response and thinking + // fields to + // * be populated. Usage: generate, think=true, no streaming. + // */ + // @Test + // @Order(20) + // void shouldGenerateWithThinking() throws OllamaBaseException { + // api.pullModel(THINKING_TOOL_MODEL); + // + // boolean raw = false; + // boolean think = true; + // + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(THINKING_TOOL_MODEL) + // .withPrompt("Who are you?") + // .withRaw(raw) + // .withThink(think) + // .withOptions(new OptionsBuilder().build()) + // .withFormat(null) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); + // + // OllamaResult result = api.generate(request, handler); // assertNotNull(result); // assertNotNull(result.getResponse()); // assertNotNull(result.getThinking()); // } - - /** - * Tests generate with all parameters enabled: raw=true, thinking=true, and streaming. - * - *

Scenario: Calls generate with all possible parameters enabled. Usage: generate, raw=true, - * thinking enabled, streaming enabled. - */ - @Test - @Order(24) - void shouldGenerateWithAllParametersEnabled() throws OllamaBaseException { - api.pullModel(THINKING_TOOL_MODEL); - // Settinng raw here instructs to keep the response raw. Even if the model generates - // 'thinking' tokens, they will not be received as separate tokens and will be mised with - // 'response' tokens - boolean raw = true; - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(THINKING_TOOL_MODEL) - .withPrompt( - "Count 1 to 5. Just give me the numbers and do not give any other" - + " details or information.") - .withRaw(raw) - .withThink(true) - .withOptions(new OptionsBuilder().setTemperature(0.1f).build()) - .withFormat(null) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = - new OllamaGenerateStreamObserver( - thinkingToken -> LOG.info("THINKING: {}", thinkingToken), - responseToken -> LOG.info("RESPONSE: {}", responseToken)); - OllamaResult result = api.generate(request, handler); - assertNotNull(result); - assertNotNull(result.getResponse()); - assertNotNull(result.getThinking()); - } - - /** - * Tests generateWithFormat with complex nested JSON schema. - * - *

Scenario: Uses a more complex JSON schema with nested objects and arrays. Usage: - * generateWithFormat with complex schema. - */ - @Test - @Order(25) - void shouldGenerateWithComplexStructuredOutput() throws OllamaBaseException { - api.pullModel(TOOLS_MODEL); - - String prompt = - "Generate information about three major cities: their names, populations, and top" - + " attractions."; - - Map format = new HashMap<>(); - format.put("type", "object"); - Map properties = new HashMap<>(); - - Map citiesProperty = new HashMap<>(); - citiesProperty.put("type", "array"); - - Map cityItem = new HashMap<>(); - cityItem.put("type", "object"); - - Map cityProperties = new HashMap<>(); - cityProperties.put("name", Map.of("type", "string")); - cityProperties.put("population", Map.of("type", "number")); - - Map attractionsProperty = new HashMap<>(); - attractionsProperty.put("type", "array"); - attractionsProperty.put("items", Map.of("type", "string")); - cityProperties.put("attractions", attractionsProperty); - - cityItem.put("properties", cityProperties); - cityItem.put("required", List.of("name", "population", "attractions")); - - citiesProperty.put("items", cityItem); - properties.put("cities", citiesProperty); - - format.put("properties", properties); - format.put("required", List.of("cities")); - - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(TOOLS_MODEL) - .withPrompt(prompt) - .withFormat(format) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = null; - - OllamaResult result = api.generate(request, handler); - - assertNotNull(result); - assertNotNull(result.getResponse()); - assertNotNull(result.getStructuredResponse()); - assertTrue(result.getStructuredResponse().containsKey("cities")); - } - - /** - * Tests chat with thinking enabled but no streaming. - * - *

Scenario: Enables thinking in chat mode without streaming. Usage: chat, thinking enabled, - * no streaming, no tools. - */ - @Test - @Order(26) - void shouldChatWithThinkingNoStream() throws OllamaBaseException { - api.pullModel(THINKING_TOOL_MODEL); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(THINKING_TOOL_MODEL); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "What is the meaning of life? Think deeply about this.") - .withThinking(true) - .build(); - - OllamaChatResult chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertNotNull(chatResult.getResponseModel().getMessage()); - assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); - // Note: Thinking content might be in the message or separate field depending on - // implementation - } - - /** - * Tests chat with custom options and streaming. - * - *

Scenario: Combines custom options (temperature, top_p, etc.) with streaming. Usage: chat, - * custom options, streaming enabled, no tools, no thinking. - */ - @Test - @Order(27) - void shouldChatWithCustomOptionsAndStreaming() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "Tell me a creative story about a time traveler") - .withOptions( - new OptionsBuilder() - .setTemperature(0.9f) - .setTopP(0.9f) - .setTopK(40) - .build()) - .build(); - - OllamaChatResult chatResult = api.chat(requestModel, new ConsoleOutputChatTokenHandler()); - - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); - assertFalse(chatResult.getResponseModel().getMessage().getResponse().isEmpty()); - } - - /** - * Tests chat with tools, thinking, and streaming all enabled. - * - *

Scenario: The most complex chat scenario with all features enabled. Usage: chat, tools, - * thinking enabled, streaming enabled. - */ - @Test - @Order(28) - void shouldChatWithToolsThinkingAndStreaming() throws OllamaBaseException { - api.pullModel(THINKING_TOOL_MODEL_2); - - api.registerTool(employeeFinderTool()); - - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(THINKING_TOOL_MODEL_2); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "I need to find information about employee John Smith. Think" - + " carefully about what details to retrieve.") - .withThinking(true) - .withOptions(new OptionsBuilder().setTemperature(0.1f).build()) - .build(); - requestModel.setUseTools(false); - OllamaChatResult chatResult = api.chat(requestModel, new ConsoleOutputChatTokenHandler()); - - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - // Verify that either tools were called or a response was generated - assertTrue(chatResult.getChatHistory().size() >= 2); - } - + // // /** - // * Tests generateWithImages with multiple image URLs. + // * Tests generate with thinking and streaming enabled. // * - // *

Scenario: Sends multiple image URLs to the vision model. Usage: generateWithImages, - // * multiple image URLs, no streaming. + // *

Scenario: Calls generate with think=true and a stream handler for both thinking and + // * response tokens. Usage: generate, think=true, streaming enabled. // */ // @Test - // @Order(29) - // void shouldGenerateWithMultipleImageURLs() throws OllamaBaseException { - // api.pullModel(VISION_MODEL); + // @Order(20) + // void shouldGenerateWithThinkingAndStreamHandler() throws OllamaBaseException { + // api.pullModel(THINKING_TOOL_MODEL); + // boolean raw = false; + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(THINKING_TOOL_MODEL) + // .withPrompt("Who are you?") + // .withRaw(raw) + // .withThink(true) + // .withOptions(new OptionsBuilder().build()) + // .withFormat(null) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = + // new OllamaGenerateStreamObserver( + // thinkingToken -> { + // LOG.info(thinkingToken.toUpperCase()); + // }, + // resToken -> { + // LOG.info(resToken.toLowerCase()); + // }); // - // List imageUrls = - // Arrays.asList( + // OllamaResult result = api.generate(request, handler); + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // assertNotNull(result.getThinking()); + // } // - // "https://i.pinimg.com/736x/f9/4e/cb/f94ecba040696a3a20b484d2e15159ec.jpg", + // /** + // * Tests generate with raw=true parameter. + // * + // *

Scenario: Calls generate with raw=true, which sends the prompt as-is without any + // * formatting. Usage: generate, raw=true, no thinking, no streaming. + // */ + // @Test + // @Order(21) + // void shouldGenerateWithRawMode() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // api.unloadModel(GENERAL_PURPOSE_MODEL); + // boolean raw = true; + // boolean thinking = false; + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(GENERAL_PURPOSE_MODEL) + // .withPrompt("What is 2+2?") + // .withRaw(raw) + // .withThink(thinking) + // .withOptions(new OptionsBuilder().build()) + // .withFormat(null) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); + // OllamaResult result = api.generate(request, handler); + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // assertFalse(result.getResponse().isEmpty()); + // } // + // /** + // * Tests generate with raw=true and streaming enabled. + // * + // *

Scenario: Calls generate with raw=true and streams the response. Usage: generate, + // * raw=true, no thinking, streaming enabled. + // */ + // @Test + // @Order(22) + // void shouldGenerateWithRawModeAndStreaming() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // boolean raw = true; + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(GENERAL_PURPOSE_MODEL) + // .withPrompt("What is the largest planet in our solar system?") + // .withRaw(raw) + // .withThink(false) + // .withOptions(new OptionsBuilder().build()) + // .withFormat(null) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = + // new OllamaGenerateStreamObserver(null, new + // ConsoleOutputGenerateTokenHandler()); + // OllamaResult result = api.generate(request, handler); + // + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // assertFalse(result.getResponse().isEmpty()); + // } + // + // // /** + // // * Tests generate with raw=true and thinking enabled. + // // * + // // *

Scenario: Calls generate with raw=true and think=true combination. Usage: + // generate, + // // * raw=true, thinking enabled, no streaming. + // // */ + // // @Test + // // @Order(23) + // // void shouldGenerateWithRawModeAndThinking() + // // throws OllamaBaseException + // // { + // // api.pullModel(THINKING_TOOL_MODEL_2); + // // api.unloadModel(THINKING_TOOL_MODEL_2); + // // boolean raw = + // // true; // if true no formatting will be applied to the prompt. You may + // choose + // // to use + // // // the raw parameter if you are specifying a full templated prompt in your + // // // request to the API + // // boolean thinking = true; + // // OllamaResult result = + // // api.generate( + // // THINKING_TOOL_MODEL_2, + // // "Validate: 1+1=2", + // // raw, + // // thinking, + // // new OptionsBuilder().build(), + // // new OllamaGenerateStreamObserver(null, null)); + // // assertNotNull(result); + // // assertNotNull(result.getResponse()); + // // assertNotNull(result.getThinking()); + // // } + // + // /** + // * Tests generate with all parameters enabled: raw=true, thinking=true, and streaming. + // * + // *

Scenario: Calls generate with all possible parameters enabled. Usage: generate, + // raw=true, + // * thinking enabled, streaming enabled. + // */ + // @Test + // @Order(24) + // void shouldGenerateWithAllParametersEnabled() throws OllamaBaseException { + // api.pullModel(THINKING_TOOL_MODEL); + // // Settinng raw here instructs to keep the response raw. Even if the model generates + // // 'thinking' tokens, they will not be received as separate tokens and will be mised + // with + // // 'response' tokens + // boolean raw = true; + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(THINKING_TOOL_MODEL) + // .withPrompt( + // "Count 1 to 5. Just give me the numbers and do not give any + // other" + // + " details or information.") + // .withRaw(raw) + // .withThink(true) + // .withOptions(new OptionsBuilder().setTemperature(0.1f).build()) + // .withFormat(null) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = + // new OllamaGenerateStreamObserver( + // thinkingToken -> LOG.info("THINKING: {}", thinkingToken), + // responseToken -> LOG.info("RESPONSE: {}", responseToken)); + // OllamaResult result = api.generate(request, handler); + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // assertNotNull(result.getThinking()); + // } + // + // /** + // * Tests generateWithFormat with complex nested JSON schema. + // * + // *

Scenario: Uses a more complex JSON schema with nested objects and arrays. Usage: + // * generateWithFormat with complex schema. + // */ + // @Test + // @Order(25) + // void shouldGenerateWithComplexStructuredOutput() throws OllamaBaseException { + // api.pullModel(TOOLS_MODEL); + // + // String prompt = + // "Generate information about three major cities: their names, populations, and + // top" + // + " attractions."; + // + // Map format = new HashMap<>(); + // format.put("type", "object"); + // Map properties = new HashMap<>(); + // + // Map citiesProperty = new HashMap<>(); + // citiesProperty.put("type", "array"); + // + // Map cityItem = new HashMap<>(); + // cityItem.put("type", "object"); + // + // Map cityProperties = new HashMap<>(); + // cityProperties.put("name", Map.of("type", "string")); + // cityProperties.put("population", Map.of("type", "number")); + // + // Map attractionsProperty = new HashMap<>(); + // attractionsProperty.put("type", "array"); + // attractionsProperty.put("items", Map.of("type", "string")); + // cityProperties.put("attractions", attractionsProperty); + // + // cityItem.put("properties", cityProperties); + // cityItem.put("required", List.of("name", "population", "attractions")); + // + // citiesProperty.put("items", cityItem); + // properties.put("cities", citiesProperty); + // + // format.put("properties", properties); + // format.put("required", List.of("cities")); + // + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(TOOLS_MODEL) + // .withPrompt(prompt) + // .withFormat(format) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = null; + // + // OllamaResult result = api.generate(request, handler); + // + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // assertNotNull(result.getStructuredResponse()); + // assertTrue(result.getStructuredResponse().containsKey("cities")); + // } + // + // /** + // * Tests chat with thinking enabled but no streaming. + // * + // *

Scenario: Enables thinking in chat mode without streaming. Usage: chat, thinking + // enabled, + // * no streaming, no tools. + // */ + // @Test + // @Order(26) + // void shouldChatWithThinkingNoStream() throws OllamaBaseException { + // api.pullModel(THINKING_TOOL_MODEL); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(THINKING_TOOL_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "What is the meaning of life? Think deeply about this.") + // .withThinking(true) + // .build(); + // + // OllamaChatResult chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertNotNull(chatResult.getResponseModel().getMessage()); + // assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); + // // Note: Thinking content might be in the message or separate field depending on + // // implementation + // } + // + // /** + // * Tests chat with custom options and streaming. + // * + // *

Scenario: Combines custom options (temperature, top_p, etc.) with streaming. Usage: + // chat, + // * custom options, streaming enabled, no tools, no thinking. + // */ + // @Test + // @Order(27) + // void shouldChatWithCustomOptionsAndStreaming() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "Tell me a creative story about a time traveler") + // .withOptions( + // new OptionsBuilder() + // .setTemperature(0.9f) + // .setTopP(0.9f) + // .setTopK(40) + // .build()) + // .build(); + // + // OllamaChatResult chatResult = api.chat(requestModel, new + // ConsoleOutputChatTokenHandler()); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); + // assertFalse(chatResult.getResponseModel().getMessage().getResponse().isEmpty()); + // } + // + // /** + // * Tests chat with tools, thinking, and streaming all enabled. + // * + // *

Scenario: The most complex chat scenario with all features enabled. Usage: chat, + // tools, + // * thinking enabled, streaming enabled. + // */ + // @Test + // @Order(28) + // void shouldChatWithToolsThinkingAndStreaming() throws OllamaBaseException { + // api.pullModel(THINKING_TOOL_MODEL_2); + // + // api.registerTool(employeeFinderTool()); + // + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(THINKING_TOOL_MODEL_2); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "I need to find information about employee John Smith. Think" + // + " carefully about what details to retrieve.") + // .withThinking(true) + // .withOptions(new OptionsBuilder().setTemperature(0.1f).build()) + // .build(); + // requestModel.setUseTools(false); + // OllamaChatResult chatResult = api.chat(requestModel, new + // ConsoleOutputChatTokenHandler()); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // // Verify that either tools were called or a response was generated + // assertTrue(chatResult.getChatHistory().size() >= 2); + // } + // + // // /** + // // * Tests generateWithImages with multiple image URLs. + // // * + // // *

Scenario: Sends multiple image URLs to the vision model. Usage: + // generateWithImages, + // // * multiple image URLs, no streaming. + // // */ + // // @Test + // // @Order(29) + // // void shouldGenerateWithMultipleImageURLs() throws OllamaBaseException { + // // api.pullModel(VISION_MODEL); + // // + // // List imageUrls = + // // Arrays.asList( + // // + // // "https://i.pinimg.com/736x/f9/4e/cb/f94ecba040696a3a20b484d2e15159ec.jpg", + // // + // // // "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg"); - // OllamaResult result = - // api.generateWithImages( - // VISION_MODEL, - // "Compare these two images. What are the similarities and - // differences?", - // imageUrls, - // new OptionsBuilder().build(), - // null, - // null); + // // OllamaResult result = + // // api.generateWithImages( + // // VISION_MODEL, + // // "Compare these two images. What are the similarities and + // // differences?", + // // imageUrls, + // // new OptionsBuilder().build(), + // // null, + // // null); + // // + // // assertNotNull(result); + // // assertNotNull(result.getResponse()); + // // assertFalse(result.getResponse().isEmpty()); + // // } + // + // // /** + // // * Tests generateWithImages with mixed image sources (URL and file). + // // * + // // *

Scenario: Combines image URL with local file in a single request. Usage: + // // * generateWithImages, mixed image sources, no streaming. + // // */ + // // @Test + // // @Order(30) + // // void shouldGenerateWithMixedImageSources() throws OllamaBaseException { + // // api.pullModel(VISION_MODEL); + // // + // // File localImage = getImageFileFromClasspath("emoji-smile.jpeg"); + // // List images = + // // Arrays.asList( + // // + // // "https://i.pinimg.com/736x/f9/4e/cb/f94ecba040696a3a20b484d2e15159ec.jpg", + // // localImage); + // // + // // OllamaResult result = + // // api.generateWithImages( + // // VISION_MODEL, + // // "Describe what you see in these images", + // // images, + // // new OptionsBuilder().build(), + // // null, + // // null); + // // + // // assertNotNull(result); + // // assertNotNull(result.getResponse()); + // // assertFalse(result.getResponse().isEmpty()); + // // } // - // assertNotNull(result); - // assertNotNull(result.getResponse()); - // assertFalse(result.getResponse().isEmpty()); - // } - // /** - // * Tests generateWithImages with mixed image sources (URL and file). + // * Tests chat with multiple images in a single message. // * - // *

Scenario: Combines image URL with local file in a single request. Usage: - // * generateWithImages, mixed image sources, no streaming. + // *

Scenario: Sends multiple images in one chat message. Usage: chat, vision model, + // multiple + // * images, no tools, no thinking, no streaming. // */ // @Test - // @Order(30) - // void shouldGenerateWithMixedImageSources() throws OllamaBaseException { + // @Order(31) + // void shouldChatWithMultipleImages() throws OllamaBaseException { // api.pullModel(VISION_MODEL); // - // File localImage = getImageFileFromClasspath("emoji-smile.jpeg"); - // List images = - // Arrays.asList( + // List tools = Collections.emptyList(); // - // "https://i.pinimg.com/736x/f9/4e/cb/f94ecba040696a3a20b484d2e15159ec.jpg", - // localImage); + // File image1 = getImageFileFromClasspath("emoji-smile.jpeg"); + // File image2 = getImageFileFromClasspath("roses.jpg"); // - // OllamaResult result = - // api.generateWithImages( - // VISION_MODEL, - // "Describe what you see in these images", - // images, - // new OptionsBuilder().build(), - // null, - // null); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(VISION_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "Compare these images and tell me what you see", + // tools, + // Arrays.asList(image1, image2)) + // .build(); + // requestModel.setUseTools(false); + // OllamaChatResult chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); + // assertFalse(chatResult.getResponseModel().getMessage().getResponse().isEmpty()); + // } + // + // /** + // * Tests error handling when model doesn't exist. + // * + // *

Scenario: Attempts to use a non-existent model and verifies proper error handling. + // */ + // @Test + // @Order(32) + // void shouldHandleNonExistentModel() { + // String nonExistentModel = "this-model-does-not-exist:latest"; + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(nonExistentModel) + // .withPrompt("Hello") + // .withRaw(false) + // .withThink(false) + // .withOptions(new OptionsBuilder().build()) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); + // assertThrows( + // OllamaBaseException.class, + // () -> { + // api.generate(request, handler); + // }); + // } + // + // /** + // * Tests chat with empty message (edge case). + // * + // *

Scenario: Sends an empty or whitespace-only message. Usage: chat, edge case testing. + // */ + // @Test + // @Order(33) + // void shouldHandleEmptyMessage() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // + // List tools = Collections.emptyList(); + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage(OllamaChatMessageRole.USER, " ", tools) // whitespace + // only + // .build(); + // requestModel.setUseTools(false); + // OllamaChatResult chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // // Should handle gracefully even with empty input + // } + // + // /** + // * Tests generate with very high temperature setting. + // * + // *

Scenario: Tests extreme parameter values for robustness. Usage: generate, extreme + // * parameters, edge case testing. + // */ + // @Test + // @Order(34) + // void shouldGenerateWithExtremeParameters() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(GENERAL_PURPOSE_MODEL) + // .withPrompt("Generate a random word") + // .withRaw(false) + // .withThink(false) + // .withOptions( + // new OptionsBuilder() + // .setTemperature(2.0f) // Very high temperature + // .setTopP(1.0f) + // .setTopK(1) + // .build()) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); + // OllamaResult result = api.generate(request, handler); + // assertNotNull(result); + // assertNotNull(result.getResponse()); + // } + // + // /** + // * Tests embeddings with single input string. + // * + // *

Scenario: Tests embedding generation with a single string instead of array. Usage: + // embed, + // * single input. + // */ + // @Test + // @Order(35) + // void shouldReturnEmbeddingsForSingleInput() throws Exception { + // api.pullModel(EMBEDDING_MODEL); + // + // OllamaEmbedRequestModel requestModel = new OllamaEmbedRequestModel(); + // requestModel.setModel(EMBEDDING_MODEL); + // requestModel.setInput( + // Collections.singletonList("This is a single test sentence for embedding.")); + // + // OllamaEmbedResponseModel embeddings = api.embed(requestModel); + // + // assertNotNull(embeddings); + // assertFalse(embeddings.getEmbeddings().isEmpty()); + // assertEquals(1, embeddings.getEmbeddings().size()); + // } + // + // /** + // * Tests chat with keep-alive parameter. + // * + // *

Scenario: Tests the keep-alive parameter which controls model unloading. Usage: + // chat, + // * keep-alive parameter, model lifecycle management. + // */ + // @Test + // @Order(36) + // void shouldChatWithKeepAlive() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage(OllamaChatMessageRole.USER, "Hello, how are you?") + // .withKeepAlive("5m") // Keep model loaded for 5 minutes + // .build(); + // requestModel.setUseTools(false); + // OllamaChatResult chatResult = api.chat(requestModel, null); + // + // assertNotNull(chatResult); + // assertNotNull(chatResult.getResponseModel()); + // assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); + // } + // + // /** + // * Tests generate with custom context window options. + // * + // *

Scenario: Tests generation with custom context length and other advanced options. + // Usage: + // * generate, advanced options, context management. + // */ + // @Test + // @Order(37) + // void shouldGenerateWithAdvancedOptions() throws OllamaBaseException { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // OllamaGenerateRequest request = + // OllamaGenerateRequestBuilder.builder() + // .withModel(GENERAL_PURPOSE_MODEL) + // .withPrompt("Write a detailed explanation of machine learning") + // .withRaw(false) + // .withThink(false) + // .withOptions( + // new OptionsBuilder() + // .setTemperature(0.7f) + // .setTopP(0.9f) + // .setTopK(40) + // .setNumCtx(4096) // Context window size + // .setRepeatPenalty(1.1f) + // .build()) + // .withKeepAlive("0m") + // .build(); + // OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); + // OllamaResult result = api.generate(request, handler); // // assertNotNull(result); // assertNotNull(result.getResponse()); // assertFalse(result.getResponse().isEmpty()); // } - - /** - * Tests chat with multiple images in a single message. - * - *

Scenario: Sends multiple images in one chat message. Usage: chat, vision model, multiple - * images, no tools, no thinking, no streaming. - */ - @Test - @Order(31) - void shouldChatWithMultipleImages() throws OllamaBaseException { - api.pullModel(VISION_MODEL); - - List tools = Collections.emptyList(); - - File image1 = getImageFileFromClasspath("emoji-smile.jpeg"); - File image2 = getImageFileFromClasspath("roses.jpg"); - - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(VISION_MODEL); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "Compare these images and tell me what you see", - tools, - Arrays.asList(image1, image2)) - .build(); - requestModel.setUseTools(false); - OllamaChatResult chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); - assertFalse(chatResult.getResponseModel().getMessage().getResponse().isEmpty()); - } - - /** - * Tests error handling when model doesn't exist. - * - *

Scenario: Attempts to use a non-existent model and verifies proper error handling. - */ - @Test - @Order(32) - void shouldHandleNonExistentModel() { - String nonExistentModel = "this-model-does-not-exist:latest"; - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(nonExistentModel) - .withPrompt("Hello") - .withRaw(false) - .withThink(false) - .withOptions(new OptionsBuilder().build()) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); - assertThrows( - OllamaBaseException.class, - () -> { - api.generate(request, handler); - }); - } - - /** - * Tests chat with empty message (edge case). - * - *

Scenario: Sends an empty or whitespace-only message. Usage: chat, edge case testing. - */ - @Test - @Order(33) - void shouldHandleEmptyMessage() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - - List tools = Collections.emptyList(); - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); - OllamaChatRequest requestModel = - builder.withMessage(OllamaChatMessageRole.USER, " ", tools) // whitespace only - .build(); - requestModel.setUseTools(false); - OllamaChatResult chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - // Should handle gracefully even with empty input - } - - /** - * Tests generate with very high temperature setting. - * - *

Scenario: Tests extreme parameter values for robustness. Usage: generate, extreme - * parameters, edge case testing. - */ - @Test - @Order(34) - void shouldGenerateWithExtremeParameters() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(GENERAL_PURPOSE_MODEL) - .withPrompt("Generate a random word") - .withRaw(false) - .withThink(false) - .withOptions( - new OptionsBuilder() - .setTemperature(2.0f) // Very high temperature - .setTopP(1.0f) - .setTopK(1) - .build()) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); - OllamaResult result = api.generate(request, handler); - assertNotNull(result); - assertNotNull(result.getResponse()); - } - - /** - * Tests embeddings with single input string. - * - *

Scenario: Tests embedding generation with a single string instead of array. Usage: embed, - * single input. - */ - @Test - @Order(35) - void shouldReturnEmbeddingsForSingleInput() throws Exception { - api.pullModel(EMBEDDING_MODEL); - - OllamaEmbedRequestModel requestModel = new OllamaEmbedRequestModel(); - requestModel.setModel(EMBEDDING_MODEL); - requestModel.setInput( - Collections.singletonList("This is a single test sentence for embedding.")); - - OllamaEmbedResponseModel embeddings = api.embed(requestModel); - - assertNotNull(embeddings); - assertFalse(embeddings.getEmbeddings().isEmpty()); - assertEquals(1, embeddings.getEmbeddings().size()); - } - - /** - * Tests chat with keep-alive parameter. - * - *

Scenario: Tests the keep-alive parameter which controls model unloading. Usage: chat, - * keep-alive parameter, model lifecycle management. - */ - @Test - @Order(36) - void shouldChatWithKeepAlive() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder().withModel(GENERAL_PURPOSE_MODEL); - OllamaChatRequest requestModel = - builder.withMessage(OllamaChatMessageRole.USER, "Hello, how are you?") - .withKeepAlive("5m") // Keep model loaded for 5 minutes - .build(); - requestModel.setUseTools(false); - OllamaChatResult chatResult = api.chat(requestModel, null); - - assertNotNull(chatResult); - assertNotNull(chatResult.getResponseModel()); - assertNotNull(chatResult.getResponseModel().getMessage().getResponse()); - } - - /** - * Tests generate with custom context window options. - * - *

Scenario: Tests generation with custom context length and other advanced options. Usage: - * generate, advanced options, context management. - */ - @Test - @Order(37) - void shouldGenerateWithAdvancedOptions() throws OllamaBaseException { - api.pullModel(GENERAL_PURPOSE_MODEL); - OllamaGenerateRequest request = - OllamaGenerateRequestBuilder.builder() - .withModel(GENERAL_PURPOSE_MODEL) - .withPrompt("Write a detailed explanation of machine learning") - .withRaw(false) - .withThink(false) - .withOptions( - new OptionsBuilder() - .setTemperature(0.7f) - .setTopP(0.9f) - .setTopK(40) - .setNumCtx(4096) // Context window size - .setRepeatPenalty(1.1f) - .build()) - .withKeepAlive("0m") - .build(); - OllamaGenerateStreamObserver handler = new OllamaGenerateStreamObserver(null, null); - OllamaResult result = api.generate(request, handler); - - assertNotNull(result); - assertNotNull(result.getResponse()); - assertFalse(result.getResponse().isEmpty()); - } - - /** - * Tests concurrent chat requests to verify thread safety. - * - *

Scenario: Sends multiple chat requests concurrently to test thread safety. Usage: chat, - * concurrency testing, thread safety. - */ - @Test - @Order(38) - void shouldHandleConcurrentChatRequests() throws OllamaBaseException, InterruptedException { - api.pullModel(GENERAL_PURPOSE_MODEL); - - int numThreads = 3; - CountDownLatch latch = new CountDownLatch(numThreads); - List results = Collections.synchronizedList(new ArrayList<>()); - List exceptions = Collections.synchronizedList(new ArrayList<>()); - - for (int i = 0; i < numThreads; i++) { - final int threadId = i; - Thread thread = - new Thread( - () -> { - try { - OllamaChatRequestBuilder builder = - OllamaChatRequestBuilder.builder() - .withModel(GENERAL_PURPOSE_MODEL); - OllamaChatRequest requestModel = - builder.withMessage( - OllamaChatMessageRole.USER, - "Hello from thread " - + threadId - + ". What is 2+2?") - .build(); - requestModel.setUseTools(false); - OllamaChatResult result = api.chat(requestModel, null); - results.add(result); - } catch (Exception e) { - exceptions.add(e); - } finally { - latch.countDown(); - } - }); - thread.start(); - } - - latch.await(60, java.util.concurrent.TimeUnit.SECONDS); - - assertTrue(exceptions.isEmpty(), "No exceptions should occur during concurrent requests"); - assertEquals(numThreads, results.size(), "All requests should complete successfully"); - - for (OllamaChatResult result : results) { - assertNotNull(result); - assertNotNull(result.getResponseModel()); - assertNotNull(result.getResponseModel().getMessage().getResponse()); - } - } - - /** - * Utility method to retrieve an image file from the classpath. - * - *

- * - * @param fileName the name of the image file - * @return the File object for the image - */ - private File getImageFileFromClasspath(String fileName) { - ClassLoader classLoader = getClass().getClassLoader(); - return new File(Objects.requireNonNull(classLoader.getResource(fileName)).getFile()); - } - - /** - * Returns a ToolSpecification for an employee finder tool. - * - *

This tool can be registered with the OllamaAPI to enable tool-calling scenarios in chat. - * The tool accepts employee-name, employee-address, and employee-phone as parameters. - */ - private Tools.ToolSpecification employeeFinderTool() { - return Tools.ToolSpecification.builder() - .functionName("get-employee-details") - .functionDescription("Get details for a person or an employee") - .toolPrompt( - Tools.PromptFuncDefinition.builder() - .type("function") - .function( - Tools.PromptFuncDefinition.PromptFuncSpec.builder() - .name("get-employee-details") - .description( - "Get details for a person or an employee") - .parameters( - Tools.PromptFuncDefinition.Parameters - .builder() - .type("object") - .properties( - new Tools.PropsBuilder() - .withProperty( - "employee-name", - Tools - .PromptFuncDefinition - .Property - .builder() - .type( - "string") - .description( - "The name" - + " of the" - + " employee," - + " e.g." - + " John" - + " Doe") - .required( - true) - .build()) - .withProperty( - "employee-address", - Tools - .PromptFuncDefinition - .Property - .builder() - .type( - "string") - .description( - "The address" - + " of the" - + " employee," - + " Always" - + " returns" - + " a random" - + " address." - + " For example," - + " Church" - + " St, Bengaluru," - + " India") - .required( - true) - .build()) - .withProperty( - "employee-phone", - Tools - .PromptFuncDefinition - .Property - .builder() - .type( - "string") - .description( - "The phone" - + " number" - + " of the" - + " employee." - + " Always" - + " returns" - + " a random" - + " phone" - + " number." - + " For example," - + " 9911002233") - .required( - true) - .build()) - .build()) - .required(List.of("employee-name")) - .build()) - .build()) - .build()) - .toolFunction( - new ToolFunction() { - @Override - public Object apply(Map arguments) { - LOG.info( - "Invoking employee finder tool with arguments: {}", - arguments); - String employeeName = "Random Employee"; - if (arguments.containsKey("employee-name")) { - employeeName = arguments.get("employee-name").toString(); - } - String address = null; - String phone = null; - if (employeeName.equalsIgnoreCase("Rahul Kumar")) { - address = "Pune, Maharashtra, India"; - phone = "9911223344"; - } else { - address = "Karol Bagh, Delhi, India"; - phone = "9911002233"; - } - // perform DB operations here - return String.format( - "Employee Details {ID: %s, Name: %s, Address: %s, Phone:" - + " %s}", - UUID.randomUUID(), employeeName, address, phone); - } - }) - .build(); - } + // + // /** + // * Tests concurrent chat requests to verify thread safety. + // * + // *

Scenario: Sends multiple chat requests concurrently to test thread safety. Usage: + // chat, + // * concurrency testing, thread safety. + // */ + // @Test + // @Order(38) + // void shouldHandleConcurrentChatRequests() throws OllamaBaseException, InterruptedException + // { + // api.pullModel(GENERAL_PURPOSE_MODEL); + // + // int numThreads = 3; + // CountDownLatch latch = new CountDownLatch(numThreads); + // List results = Collections.synchronizedList(new ArrayList<>()); + // List exceptions = Collections.synchronizedList(new ArrayList<>()); + // + // for (int i = 0; i < numThreads; i++) { + // final int threadId = i; + // Thread thread = + // new Thread( + // () -> { + // try { + // OllamaChatRequestBuilder builder = + // OllamaChatRequestBuilder.builder() + // .withModel(GENERAL_PURPOSE_MODEL); + // OllamaChatRequest requestModel = + // builder.withMessage( + // OllamaChatMessageRole.USER, + // "Hello from thread " + // + threadId + // + ". What is 2+2?") + // .build(); + // requestModel.setUseTools(false); + // OllamaChatResult result = api.chat(requestModel, null); + // results.add(result); + // } catch (Exception e) { + // exceptions.add(e); + // } finally { + // latch.countDown(); + // } + // }); + // thread.start(); + // } + // + // latch.await(60, java.util.concurrent.TimeUnit.SECONDS); + // + // assertTrue(exceptions.isEmpty(), "No exceptions should occur during concurrent + // requests"); + // assertEquals(numThreads, results.size(), "All requests should complete successfully"); + // + // for (OllamaChatResult result : results) { + // assertNotNull(result); + // assertNotNull(result.getResponseModel()); + // assertNotNull(result.getResponseModel().getMessage().getResponse()); + // } + // } + // + // /** + // * Utility method to retrieve an image file from the classpath. + // * + // *

+ // * + // * @param fileName the name of the image file + // * @return the File object for the image + // */ + // private File getImageFileFromClasspath(String fileName) { + // ClassLoader classLoader = getClass().getClassLoader(); + // return new File(Objects.requireNonNull(classLoader.getResource(fileName)).getFile()); + // } + // + // /** + // * Returns a ToolSpecification for an employee finder tool. + // * + // *

This tool can be registered with the OllamaAPI to enable tool-calling scenarios in + // chat. + // * The tool accepts employee-name, employee-address, and employee-phone as parameters. + // */ + // private Tools.ToolSpecification employeeFinderTool() { + // return Tools.ToolSpecification.builder() + // .functionName("get-employee-details") + // .functionDescription("Get details for a person or an employee") + // .toolPrompt( + // Tools.PromptFuncDefinition.builder() + // .type("function") + // .function( + // Tools.PromptFuncDefinition.PromptFuncSpec.builder() + // .name("get-employee-details") + // .description( + // "Get details for a person or an + // employee") + // .parameters( + // Tools.PromptFuncDefinition.Parameters + // .builder() + // .type("object") + // .properties( + // new + // Tools.PropsBuilder() + // .withProperty( + // + // "employee-name", + // Tools + // + // .PromptFuncDefinition + // + // .Property + // + // .builder() + // + // .type( + // + // "string") + // + // .description( + // + // "The name" + // + // + " of the" + // + // + " employee," + // + // + " e.g." + // + // + " John" + // + // + " Doe") + // + // .required( + // + // true) + // + // .build()) + // .withProperty( + // + // "employee-address", + // Tools + // + // .PromptFuncDefinition + // + // .Property + // + // .builder() + // + // .type( + // + // "string") + // + // .description( + // + // "The address" + // + // + " of the" + // + // + " employee," + // + // + " Always" + // + // + " returns" + // + // + " a random" + // + // + " address." + // + // + " For example," + // + // + " Church" + // + // + " St, Bengaluru," + // + // + " India") + // + // .required( + // + // true) + // + // .build()) + // .withProperty( + // + // "employee-phone", + // Tools + // + // .PromptFuncDefinition + // + // .Property + // + // .builder() + // + // .type( + // + // "string") + // + // .description( + // + // "The phone" + // + // + " number" + // + // + " of the" + // + // + " employee." + // + // + " Always" + // + // + " returns" + // + // + " a random" + // + // + " phone" + // + // + " number." + // + // + " For example," + // + // + " 9911002233") + // + // .required( + // + // true) + // + // .build()) + // .build()) + // + // .required(List.of("employee-name")) + // .build()) + // .build()) + // .build()) + // .toolFunction( + // new ToolFunction() { + // @Override + // public Object apply(Map arguments) { + // LOG.info( + // "Invoking employee finder tool with arguments: {}", + // arguments); + // String employeeName = "Random Employee"; + // if (arguments.containsKey("employee-name")) { + // employeeName = arguments.get("employee-name").toString(); + // } + // String address = null; + // String phone = null; + // if (employeeName.equalsIgnoreCase("Rahul Kumar")) { + // address = "Pune, Maharashtra, India"; + // phone = "9911223344"; + // } else { + // address = "Karol Bagh, Delhi, India"; + // phone = "9911002233"; + // } + // // perform DB operations here + // return String.format( + // "Employee Details {ID: %s, Name: %s, Address: %s, + // Phone:" + // + " %s}", + // UUID.randomUUID(), employeeName, address, phone); + // } + // }) + // .build(); + // } } diff --git a/src/test/java/io/github/ollama4j/unittests/TestMockedAPIs.java b/src/test/java/io/github/ollama4j/unittests/TestMockedAPIs.java index fdcce38..01d0741 100644 --- a/src/test/java/io/github/ollama4j/unittests/TestMockedAPIs.java +++ b/src/test/java/io/github/ollama4j/unittests/TestMockedAPIs.java @@ -25,14 +25,11 @@ import io.github.ollama4j.models.request.CustomModelRequest; import io.github.ollama4j.models.response.ModelDetail; import io.github.ollama4j.models.response.OllamaAsyncResultStreamer; import io.github.ollama4j.models.response.OllamaResult; -import io.github.ollama4j.tools.ToolFunction; -import io.github.ollama4j.tools.Tools; import io.github.ollama4j.utils.OptionsBuilder; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -93,19 +90,19 @@ class TestMockedAPIs { } } - @Test - void testRegisteredTools() { - OllamaAPI ollamaAPI = Mockito.mock(OllamaAPI.class); - doNothing().when(ollamaAPI).registerTools(Collections.emptyList()); - ollamaAPI.registerTools(Collections.emptyList()); - verify(ollamaAPI, times(1)).registerTools(Collections.emptyList()); - - List toolSpecifications = new ArrayList<>(); - toolSpecifications.add(getSampleToolSpecification()); - doNothing().when(ollamaAPI).registerTools(toolSpecifications); - ollamaAPI.registerTools(toolSpecifications); - verify(ollamaAPI, times(1)).registerTools(toolSpecifications); - } + // @Test + // void testRegisteredTools() { + // OllamaAPI ollamaAPI = Mockito.mock(OllamaAPI.class); + // doNothing().when(ollamaAPI).registerTools(Collections.emptyList()); + // ollamaAPI.registerTools(Collections.emptyList()); + // verify(ollamaAPI, times(1)).registerTools(Collections.emptyList()); + // + // List toolSpecifications = new ArrayList<>(); + // toolSpecifications.add(getSampleToolSpecification()); + // doNothing().when(ollamaAPI).registerTools(toolSpecifications); + // ollamaAPI.registerTools(toolSpecifications); + // verify(ollamaAPI, times(1)).registerTools(toolSpecifications); + // } @Test void testGetModelDetails() { @@ -322,50 +319,63 @@ class TestMockedAPIs { } } - private static Tools.ToolSpecification getSampleToolSpecification() { - return Tools.ToolSpecification.builder() - .functionName("current-weather") - .functionDescription("Get current weather") - .toolFunction( - new ToolFunction() { - @Override - public Object apply(Map arguments) { - String location = arguments.get("city").toString(); - return "Currently " + location + "'s weather is beautiful."; - } - }) - .toolPrompt( - Tools.PromptFuncDefinition.builder() - .type("prompt") - .function( - Tools.PromptFuncDefinition.PromptFuncSpec.builder() - .name("get-location-weather-info") - .description("Get location details") - .parameters( - Tools.PromptFuncDefinition.Parameters - .builder() - .type("object") - .properties( - Map.of( - "city", - Tools - .PromptFuncDefinition - .Property - .builder() - .type( - "string") - .description( - "The city," - + " e.g." - + " New Delhi," - + " India") - .required( - true) - .build())) - .required(java.util.List.of("city")) - .build()) - .build()) - .build()) - .build(); - } + // private static Tools.ToolSpecification getSampleToolSpecification() { + // return Tools.ToolSpecification.builder() + // .functionName("current-weather") + // .functionDescription("Get current weather") + // .toolFunction( + // new ToolFunction() { + // @Override + // public Object apply(Map arguments) { + // String location = arguments.get("city").toString(); + // return "Currently " + location + "'s weather is beautiful."; + // } + // }) + // .toolPrompt( + // Tools.PromptFuncDefinition.builder() + // .type("prompt") + // .function( + // Tools.PromptFuncDefinition.PromptFuncSpec.builder() + // .name("get-location-weather-info") + // .description("Get location details") + // .parameters( + // Tools.PromptFuncDefinition.Parameters + // .builder() + // .type("object") + // .properties( + // Map.of( + // "city", + // Tools + // + // .PromptFuncDefinition + // + // .Property + // + // .builder() + // .type( + // + // "string") + // + // .description( + // + // "The city," + // + // + " e.g." + // + // + " New Delhi," + // + // + " India") + // + // .required( + // + // true) + // + // .build())) + // + // .required(java.util.List.of("city")) + // .build()) + // .build()) + // .build()) + // .build(); + // } } diff --git a/src/test/java/io/github/ollama4j/unittests/TestToolRegistry.java b/src/test/java/io/github/ollama4j/unittests/TestToolRegistry.java index 04c7135..c672a74 100644 --- a/src/test/java/io/github/ollama4j/unittests/TestToolRegistry.java +++ b/src/test/java/io/github/ollama4j/unittests/TestToolRegistry.java @@ -10,47 +10,43 @@ package io.github.ollama4j.unittests; import static org.junit.jupiter.api.Assertions.*; -import io.github.ollama4j.tools.ToolFunction; -import io.github.ollama4j.tools.ToolRegistry; -import io.github.ollama4j.tools.Tools; -import java.util.Map; -import org.junit.jupiter.api.Test; - class TestToolRegistry { - - @Test - void testAddAndGetToolFunction() { - ToolRegistry registry = new ToolRegistry(); - ToolFunction fn = args -> "ok:" + args.get("x"); - - Tools.ToolSpecification spec = - Tools.ToolSpecification.builder() - .functionName("test") - .functionDescription("desc") - .toolFunction(fn) - .build(); - - registry.addTool("test", spec); - ToolFunction retrieved = registry.getToolFunction("test"); - assertNotNull(retrieved); - assertEquals("ok:42", retrieved.apply(Map.of("x", 42))); - } - - @Test - void testGetUnknownReturnsNull() { - ToolRegistry registry = new ToolRegistry(); - assertNull(registry.getToolFunction("nope")); - } - - @Test - void testClearRemovesAll() { - ToolRegistry registry = new ToolRegistry(); - registry.addTool("a", Tools.ToolSpecification.builder().toolFunction(args -> 1).build()); - registry.addTool("b", Tools.ToolSpecification.builder().toolFunction(args -> 2).build()); - assertFalse(registry.getRegisteredSpecs().isEmpty()); - registry.clear(); - assertTrue(registry.getRegisteredSpecs().isEmpty()); - assertNull(registry.getToolFunction("a")); - assertNull(registry.getToolFunction("b")); - } + // + // @Test + // void testAddAndGetToolFunction() { + // ToolRegistry registry = new ToolRegistry(); + // ToolFunction fn = args -> "ok:" + args.get("x"); + // + // Tools.ToolSpecification spec = + // Tools.ToolSpecification.builder() + // .functionName("test") + // .functionDescription("desc") + // .toolFunction(fn) + // .build(); + // + // registry.addTool("test", spec); + // ToolFunction retrieved = registry.getToolFunction("test"); + // assertNotNull(retrieved); + // assertEquals("ok:42", retrieved.apply(Map.of("x", 42))); + // } + // + // @Test + // void testGetUnknownReturnsNull() { + // ToolRegistry registry = new ToolRegistry(); + // assertNull(registry.getToolFunction("nope")); + // } + // + // @Test + // void testClearRemovesAll() { + // ToolRegistry registry = new ToolRegistry(); + // registry.addTool("a", Tools.ToolSpecification.builder().toolFunction(args -> + // 1).build()); + // registry.addTool("b", Tools.ToolSpecification.builder().toolFunction(args -> + // 2).build()); + // assertFalse(registry.getRegisteredSpecs().isEmpty()); + // registry.clear(); + // assertTrue(registry.getRegisteredSpecs().isEmpty()); + // assertNull(registry.getToolFunction("a")); + // assertNull(registry.getToolFunction("b")); + // } } diff --git a/src/test/java/io/github/ollama4j/unittests/TestToolsPromptBuilder.java b/src/test/java/io/github/ollama4j/unittests/TestToolsPromptBuilder.java index 81a7d81..3cb0d30 100644 --- a/src/test/java/io/github/ollama4j/unittests/TestToolsPromptBuilder.java +++ b/src/test/java/io/github/ollama4j/unittests/TestToolsPromptBuilder.java @@ -8,68 +8,60 @@ */ package io.github.ollama4j.unittests; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.fasterxml.jackson.core.JsonProcessingException; -import io.github.ollama4j.tools.Tools; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; - class TestToolsPromptBuilder { - - @Test - void testPromptBuilderIncludesToolsAndPrompt() throws JsonProcessingException { - Tools.PromptFuncDefinition.Property cityProp = - Tools.PromptFuncDefinition.Property.builder() - .type("string") - .description("city name") - .required(true) - .build(); - - Tools.PromptFuncDefinition.Property unitsProp = - Tools.PromptFuncDefinition.Property.builder() - .type("string") - .description("units") - .enumValues(List.of("metric", "imperial")) - .required(false) - .build(); - - Tools.PromptFuncDefinition.Parameters params = - Tools.PromptFuncDefinition.Parameters.builder() - .type("object") - .properties(Map.of("city", cityProp, "units", unitsProp)) - .build(); - - Tools.PromptFuncDefinition.PromptFuncSpec spec = - Tools.PromptFuncDefinition.PromptFuncSpec.builder() - .name("getWeather") - .description("Get weather for a city") - .parameters(params) - .build(); - - Tools.PromptFuncDefinition def = - Tools.PromptFuncDefinition.builder().type("function").function(spec).build(); - - Tools.ToolSpecification toolSpec = - Tools.ToolSpecification.builder() - .functionName("getWeather") - .functionDescription("Get weather for a city") - .toolPrompt(def) - .build(); - - Tools.PromptBuilder pb = - new Tools.PromptBuilder() - .withToolSpecification(toolSpec) - .withPrompt("Tell me the weather."); - - String built = pb.build(); - assertTrue(built.contains("[AVAILABLE_TOOLS]")); - assertTrue(built.contains("[/AVAILABLE_TOOLS]")); - assertTrue(built.contains("[INST]")); - assertTrue(built.contains("Tell me the weather.")); - assertTrue(built.contains("\"name\":\"getWeather\"")); - assertTrue(built.contains("\"required\":[\"city\"]")); - assertTrue(built.contains("\"enum\":[\"metric\",\"imperial\"]")); - } + // + // @Test + // void testPromptBuilderIncludesToolsAndPrompt() throws JsonProcessingException { + // Tools.PromptFuncDefinition.Property cityProp = + // Tools.PromptFuncDefinition.Property.builder() + // .type("string") + // .description("city name") + // .required(true) + // .build(); + // + // Tools.PromptFuncDefinition.Property unitsProp = + // Tools.PromptFuncDefinition.Property.builder() + // .type("string") + // .description("units") + // .enumValues(List.of("metric", "imperial")) + // .required(false) + // .build(); + // + // Tools.PromptFuncDefinition.Parameters params = + // Tools.PromptFuncDefinition.Parameters.builder() + // .type("object") + // .properties(Map.of("city", cityProp, "units", unitsProp)) + // .build(); + // + // Tools.PromptFuncDefinition.PromptFuncSpec spec = + // Tools.PromptFuncDefinition.PromptFuncSpec.builder() + // .name("getWeather") + // .description("Get weather for a city") + // .parameters(params) + // .build(); + // + // Tools.PromptFuncDefinition def = + // Tools.PromptFuncDefinition.builder().type("function").function(spec).build(); + // + // Tools.ToolSpecification toolSpec = + // Tools.ToolSpecification.builder() + // .functionName("getWeather") + // .functionDescription("Get weather for a city") + // .toolPrompt(def) + // .build(); + // + // Tools.PromptBuilder pb = + // new Tools.PromptBuilder() + // .withToolSpecification(toolSpec) + // .withPrompt("Tell me the weather."); + // + // String built = pb.build(); + // assertTrue(built.contains("[AVAILABLE_TOOLS]")); + // assertTrue(built.contains("[/AVAILABLE_TOOLS]")); + // assertTrue(built.contains("[INST]")); + // assertTrue(built.contains("Tell me the weather.")); + // assertTrue(built.contains("\"name\":\"getWeather\"")); + // assertTrue(built.contains("\"required\":[\"city\"]")); + // assertTrue(built.contains("\"enum\":[\"metric\",\"imperial\"]")); + // } }