diff --git a/docs/docs/apis-generate/chat-with-tools.md b/docs/docs/apis-generate/chat-with-tools.md index b121410..eca5e15 100644 --- a/docs/docs/apis-generate/chat-with-tools.md +++ b/docs/docs/apis-generate/chat-with-tools.md @@ -29,17 +29,17 @@ session. The tool invocation and response handling are all managed internally by This tool calling can also be done using the streaming API. -### Client-managed tool calls (clientHandlesTools) +### Client-managed tool calls (useTools) By default, ollama4j automatically executes tool calls returned by the model during chat, runs the corresponding registered Java methods, and appends the tool results back into the conversation. For some applications, you may want to intercept tool calls and decide yourself when and how to execute them (for example, to queue them, to show a confirmation UI to the user, to run them in a sandbox, or to perform multi‑step orchestration). -To enable this behavior, set the clientHandlesTools flag to true on your OllamaAPI instance. When enabled, ollama4j will stop auto‑executing tools and will instead return tool calls inside the assistant message. You can then inspect the tool calls and execute them manually. +To enable this behavior, set the useTools flag to true on your OllamaAPI instance. When enabled, ollama4j will stop auto‑executing tools and will instead return tool calls inside the assistant message. You can then inspect the tool calls and execute them manually. Notes: -- Default value: clientHandlesTools is false for backward compatibility. -- When clientHandlesTools is false, ollama4j auto‑executes tools and loops internally until tools are resolved or max retries is reached. -- When clientHandlesTools is true, ollama4j will not execute tools; you are responsible for invoking tools and passing results back as TOOL messages, then re‑calling chat() to continue. +- Default value: useTools is true. +- When useTools is false, ollama4j auto‑executes tools and loops internally until tools are resolved or max retries is reached. +- When useTools is true, ollama4j will not execute tools; you are responsible for invoking tools and passing results back as TOOL messages, then re‑calling chat() to continue. ### Annotation-Based Tool Registration diff --git a/src/main/java/io/github/ollama4j/OllamaAPI.java b/src/main/java/io/github/ollama4j/OllamaAPI.java index 34c7257..110e3b2 100644 --- a/src/main/java/io/github/ollama4j/OllamaAPI.java +++ b/src/main/java/io/github/ollama4j/OllamaAPI.java @@ -5,7 +5,7 @@ * Licensed under the MIT License (the "License"); * you may not use this file except in compliance with the License. * -*/ + */ package io.github.ollama4j; import com.fasterxml.jackson.core.JsonParseException; @@ -92,13 +92,9 @@ public class OllamaAPI { @SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) private int numberOfRetriesForModelPull = 0; - /** - * When set to true, tools will not be automatically executed by the library. Instead, tool - * calls will be returned to the client for manual handling. - * - *
Default is false for backward compatibility.
- */
- @Setter private boolean clientHandlesTools = false;
+ @Setter
+ @SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
+ private int modelKeepAliveTime = 0;
/**
* Instantiates the Ollama API with default Ollama host: jsonMap = new java.util.HashMap<>();
+ jsonMap.put("model", modelName);
+ jsonMap.put("keep_alive", 0);
+ String jsonData = objectMapper.writeValueAsString(jsonMap);
+ HttpRequest request =
+ getRequestBuilderDefault(new URI(url))
+ .method(
+ "POST",
+ HttpRequest.BodyPublishers.ofString(
+ jsonData, StandardCharsets.UTF_8))
+ .header(
+ Constants.HttpConstants.HEADER_KEY_ACCEPT,
+ Constants.HttpConstants.APPLICATION_JSON)
+ .header(
+ Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
+ Constants.HttpConstants.APPLICATION_JSON)
+ .build();
+ HttpClient client = HttpClient.newHttpClient();
+ HttpResponse
+ * If set to {@code true} (the default), tools will be automatically used/applied by the library. + * If set to {@code false}, tool calls will be returned to the client for manual handling. + *
+ * Disabling this should be an explicit operation.
+ */
+ private boolean useTools = true;
+
public OllamaChatRequest() {}
public OllamaChatRequest(String model, boolean think, List
- * 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.
+ *
+ * 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;
+ int modelKeepAliveTime = 0;
try {
String useExternalOllamaHostEnv = System.getenv("USE_EXTERNAL_OLLAMA_HOST");
@@ -97,6 +101,7 @@ class OllamaAPIIntegrationTest {
Integer.parseInt(props.getProperty("REQUEST_TIMEOUT_SECONDS"));
numberOfRetriesForModelPull =
Integer.parseInt(props.getProperty("NUMBER_RETRIES_FOR_MODEL_PULL"));
+ modelKeepAliveTime = Integer.parseInt(props.getProperty("MODEL_KEEP_ALIVE_TIME"));
} else {
useExternalOllamaHost = Boolean.parseBoolean(useExternalOllamaHostEnv);
ollamaHost = ollamaHostEnv;
@@ -108,10 +113,10 @@ class OllamaAPIIntegrationTest {
} 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.");
+ + " 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";
@@ -133,12 +138,14 @@ class OllamaAPIIntegrationTest {
}
api.setRequestTimeoutSeconds(requestTimeoutSeconds);
api.setNumberOfRetriesForModelPull(numberOfRetriesForModelPull);
+ api.setModelKeepAliveTime(modelKeepAliveTime);
}
/**
- * 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.
+ * 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)
@@ -149,8 +156,9 @@ class OllamaAPIIntegrationTest {
/**
* Tests retrieval of the Ollama server version.
- *
- * Scenario: Calls the /api/version endpoint and asserts a non-null version string is returned.
+ *
+ * Scenario: Calls the /api/version endpoint and asserts a non-null version string is
+ * returned.
*/
@Test
@Order(1)
@@ -162,8 +170,8 @@ class OllamaAPIIntegrationTest {
/**
* Tests the /api/ping endpoint for server liveness.
- *
- * Scenario: Ensures the Ollama server responds to ping requests.
+ *
+ * Scenario: Ensures the Ollama server responds to ping requests.
*/
@Test
@Order(1)
@@ -174,8 +182,8 @@ class OllamaAPIIntegrationTest {
/**
* Tests listing all available models from the Ollama server.
- *
- * Scenario: Calls /api/tags and verifies the returned list is not null (may be empty).
+ *
+ * Scenario: Calls /api/tags and verifies the returned list is not null (may be empty).
*/
@Test
@Order(2)
@@ -186,10 +194,21 @@ class OllamaAPIIntegrationTest {
assertTrue(models.size() >= 0, "Models list can be empty or contain elements");
}
+ @Test
+ @Order(2)
+ void shouldUnloadModel()
+ throws URISyntaxException, IOException, OllamaBaseException, InterruptedException {
+ final String model = "all-minilm:latest";
+ api.unloadModel(model);
+ boolean isUnloaded =
+ api.ps().getModels().stream().noneMatch(mp -> model.equals(mp.getName()));
+ assertTrue(isUnloaded, "Model should be unloaded but is still present in process list");
+ }
+
/**
* 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.
+ *
+ * Scenario: Pulls an embedding model, then checks that it is present in the list of models.
*/
@Test
@Order(3)
@@ -203,8 +222,9 @@ class OllamaAPIIntegrationTest {
/**
* Tests fetching detailed information for a specific model.
- *
- * Scenario: Pulls a model and retrieves its details, asserting the model file contains the model name.
+ *
+ * Scenario: Pulls a model and retrieves its details, asserting the model file contains the
+ * model name.
*/
@Test
@Order(4)
@@ -218,8 +238,8 @@ class OllamaAPIIntegrationTest {
/**
* Tests generating embeddings for a batch of input texts.
- *
- * Scenario: Uses the embedding model to generate vector embeddings for two input sentences.
+ *
+ * Scenario: Uses the embedding model to generate vector embeddings for two input sentences.
*/
@Test
@Order(5)
@@ -235,9 +255,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -276,9 +296,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -303,8 +323,8 @@ class OllamaAPIIntegrationTest {
/**
* Tests text generation with streaming enabled.
- *
- * Scenario: Calls generate with a general-purpose model, streaming the response tokens.
+ *
+ * Scenario: Calls generate with a general-purpose model, streaming the response tokens.
* Usage: generate, raw=false, think=false, streaming enabled.
*/
@Test
@@ -331,9 +351,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -367,9 +387,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -390,8 +410,8 @@ class OllamaAPIIntegrationTest {
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]",
+ + " 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())
@@ -413,9 +433,10 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -463,9 +484,10 @@ class OllamaAPIIntegrationTest {
/**
* 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, clientHandlesTools=false, no thinking, no streaming.
+ *
+ * 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)
@@ -475,7 +497,6 @@ class OllamaAPIIntegrationTest {
URISyntaxException,
InterruptedException,
ToolInvocationException {
- api.setClientHandlesTools(false);
String theToolModel = TOOLS_MODEL;
api.pullModel(theToolModel);
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(theToolModel);
@@ -488,7 +509,7 @@ class OllamaAPIIntegrationTest {
"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");
@@ -520,14 +541,14 @@ class OllamaAPIIntegrationTest {
}
/**
- * Tests chat API with explicit tool invocation and clientHandlesTools=true.
- *
- * Scenario: Registers a tool, enables clientHandlesTools, sends a user message, and verifies the assistant's tool call.
- * Usage: chat, explicit tool, clientHandlesTools=true, no thinking, no streaming.
+ * 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 shouldChatWithExplicitToolAndClientHandlesTools()
+ void shouldChatWithExplicitToolAndUseTools()
throws OllamaBaseException,
IOException,
URISyntaxException,
@@ -539,60 +560,39 @@ class OllamaAPIIntegrationTest {
api.registerTool(employeeFinderTool());
- try {
- api.setClientHandlesTools(true);
+ 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);
- 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());
+ 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");
- 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
- * Scenario: Registers a tool, sends a user message, and streams the assistant's response (with tool call).
- * Usage: chat, explicit tool, clientHandlesTools=false, 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)
@@ -602,7 +602,6 @@ class OllamaAPIIntegrationTest {
URISyntaxException,
InterruptedException,
ToolInvocationException {
- api.setClientHandlesTools(false);
String theToolModel = TOOLS_MODEL;
api.pullModel(theToolModel);
@@ -617,7 +616,7 @@ class OllamaAPIIntegrationTest {
.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");
@@ -640,9 +639,6 @@ class OllamaAPIIntegrationTest {
"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");
@@ -653,9 +649,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -700,11 +696,13 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -738,9 +736,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * Scenario: Sends a user message and streams the assistant's response. Usage: chat, no
+ * tools, no thinking, streaming enabled.
*/
@Test
@Order(15)
@@ -771,8 +769,8 @@ class OllamaAPIIntegrationTest {
/**
* Tests chat API with thinking and streaming enabled.
- *
- * Scenario: Sends a user message with thinking enabled and streams the assistant's response.
+ *
+ * Scenario: Sends a user message with thinking enabled and streams the assistant's response.
* Usage: chat, no tools, thinking enabled, streaming enabled.
*/
@Test
@@ -805,8 +803,8 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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
@@ -835,9 +833,10 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -874,9 +873,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -900,9 +899,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -929,9 +928,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -956,9 +955,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * Scenario: Calls generate with think=true, expecting both response and thinking fields to
+ * be populated. Usage: generate, think=true, no streaming.
*/
@Test
@Order(20)
@@ -984,9 +983,9 @@ class OllamaAPIIntegrationTest {
/**
* 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.
+ *
+ * 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)
@@ -1013,9 +1012,606 @@ class OllamaAPIIntegrationTest {
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, IOException, URISyntaxException, InterruptedException {
+ api.pullModel(GENERAL_PURPOSE_MODEL);
+ boolean raw = true;
+ boolean thinking = false;
+ OllamaResult result =
+ api.generate(
+ GENERAL_PURPOSE_MODEL,
+ "What is 2+2?",
+ raw,
+ thinking,
+ new OptionsBuilder().build(),
+ new OllamaGenerateStreamObserver(null, null));
+ 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, IOException, URISyntaxException, InterruptedException {
+ api.pullModel(GENERAL_PURPOSE_MODEL);
+ boolean raw = true;
+ OllamaResult result =
+ api.generate(
+ GENERAL_PURPOSE_MODEL,
+ "What is the largest planet in our solar system?",
+ raw,
+ false,
+ new OptionsBuilder().build(),
+ new OllamaGenerateStreamObserver(
+ null, new ConsoleOutputGenerateTokenHandler()));
+
+ 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, IOException, URISyntaxException, InterruptedException {
+ api.pullModel(THINKING_TOOL_MODEL);
+ 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,
+ "What is a catalyst?",
+ 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, IOException, URISyntaxException, InterruptedException {
+ 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;
+ OllamaResult result =
+ api.generate(
+ THINKING_TOOL_MODEL,
+ "Count 1 to 5. Just give me the numbers and do not give any other details or information.",
+ raw,
+ true,
+ new OptionsBuilder().setTemperature(0.1f).build(),
+ new OllamaGenerateStreamObserver(
+ thinkingToken -> LOG.info("THINKING: {}", thinkingToken),
+ responseToken -> LOG.info("RESPONSE: {}", responseToken)));
+ 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, IOException, InterruptedException, URISyntaxException {
+ api.pullModel(TOOLS_MODEL);
+
+ String prompt =
+ "Generate information about three major cities: their names, populations, and top attractions.";
+
+ Map Scenario: Enables thinking in chat mode without streaming. Usage: chat, thinking enabled,
+ * no streaming, no tools.
+ */
+ @Test
+ @Order(26)
+ void shouldChatWithThinkingNoStream()
+ throws OllamaBaseException,
+ IOException,
+ URISyntaxException,
+ InterruptedException,
+ ToolInvocationException {
+ api.pullModel(THINKING_TOOL_MODEL);
+ OllamaChatRequestBuilder builder =
+ OllamaChatRequestBuilder.getInstance(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,
+ IOException,
+ URISyntaxException,
+ InterruptedException,
+ ToolInvocationException {
+ api.pullModel(GENERAL_PURPOSE_MODEL);
+
+ OllamaChatRequestBuilder builder =
+ OllamaChatRequestBuilder.getInstance(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,
+ IOException,
+ URISyntaxException,
+ InterruptedException,
+ ToolInvocationException {
+ api.pullModel(THINKING_TOOL_MODEL_2);
+
+ api.registerTool(employeeFinderTool());
+
+ OllamaChatRequestBuilder builder =
+ OllamaChatRequestBuilder.getInstance(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, IOException, URISyntaxException, InterruptedException {
+ api.pullModel(VISION_MODEL);
+
+ List