From c78e5f964a7dc27db4e9a1fde87edd0f21e5d5ec Mon Sep 17 00:00:00 2001 From: Can Date: Fri, 1 May 2026 20:33:02 +0200 Subject: [PATCH] Update ollamaClient to use LiteLLM endpoints instead of ollama --- .../meitrex/common/ollama/OllamaClient.java | 25 ++- .../meitrex/common/ollama/OllamaRequest.java | 15 +- .../meitrex/common/ollama/OllamaResponse.java | 145 +++--------------- .../common/ollama/OllamaClientTest.java | 31 +++- 4 files changed, 73 insertions(+), 143 deletions(-) diff --git a/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaClient.java b/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaClient.java index cb3b0fa..0741142 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaClient.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaClient.java @@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; @@ -151,13 +152,22 @@ public ResponseType startQuery( ? modelOverride : this.config.getModel(); + Map responseFormat = Map.of( + "type", "json_schema", + "json_schema", Map.of( + "name", "structured_output", + "schema", schemaObject, + "strict", true + ) + ); + log.info("Starting LLM query. Model: {}", modelToUse); OllamaRequest request = new OllamaRequest( - modelToUse, - filledPrompt, - false, - schemaObject + modelToUse, + List.of(new OllamaRequest.Message("user", filledPrompt)), + 0.0, + responseFormat ); final OllamaResponse response = queryLLM(request); @@ -215,12 +225,13 @@ private OllamaResponse queryLLM(final OllamaRequest request) throws IOException, final HttpResponse response = client.send(reqBuilder.build(), HttpResponse.BodyHandlers.ofString()); - log.info("RAW OLLAMA HTTP RESPONSE BODY: {}", response.body()); + log.debug("RAW OLLAMA HTTP RESPONSE BODY: {}", response.body()); final OllamaResponse result = jsonMapper.readValue(response.body(), OllamaResponse.class); - if (result.getError() != null) { - throw new RuntimeException("Ollama returned error: " + result.getError()); + if (response.statusCode() >= 400 || result.getErrorMessage() != null) { + throw new RuntimeException("LLM returned error: " + + (result.getErrorMessage() != null ? result.getErrorMessage() : response.body())); } return result; diff --git a/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaRequest.java b/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaRequest.java index 9e0c74b..3fc6ec1 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaRequest.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaRequest.java @@ -1,12 +1,19 @@ package de.unistuttgart.iste.meitrex.common.ollama; import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; import java.util.Map; public record OllamaRequest( @JsonProperty("model") String model, - @JsonProperty("prompt") String prompt, - @JsonProperty("stream") boolean stream, - @JsonProperty("format") Map format -) {} + @JsonProperty("messages") List messages, + @JsonProperty("temperature") double temperature, + @JsonProperty("response_format") Map responseFormat +) { + public record Message( + @JsonProperty("role") String role, + @JsonProperty("content") String content + ) {} +} diff --git a/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaResponse.java b/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaResponse.java index caf26d8..d1c44ef 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaResponse.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaResponse.java @@ -1,139 +1,34 @@ package de.unistuttgart.iste.meitrex.common.ollama; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.Setter; +import java.util.List; -@Setter -@Getter -public class OllamaResponse { +@JsonIgnoreProperties(ignoreUnknown = true) +public record OllamaResponse( + @JsonProperty("choices") List choices, + @JsonProperty("error") OpenAiError error +) { + @JsonIgnoreProperties(ignoreUnknown = true) + public record Choice(@JsonProperty("message") Message message) {} - @JsonProperty("total_duration") - private long totalDuration; - @JsonProperty("load_duration") - private long loadDuration; - @JsonProperty("prompt_eval_count") - private long promptEvalCount; - @JsonProperty("prompt_eval_duration") - private long promptEvalDuration; - @JsonProperty("eval_count") - private long evalCount; - @JsonProperty("eval_duration") - private long evalDuration; - @JsonProperty("model") - private String model; - @JsonProperty("created_at") - private String createdAt; - @JsonProperty("response") - private String response; - @JsonProperty("done") - private boolean done; - @JsonProperty("done_reason") - private String doneReason; - @JsonProperty("context") - private long[] context; - @JsonProperty("error") - private String error; + @JsonIgnoreProperties(ignoreUnknown = true) + public record Message(@JsonProperty("content") String content) {} - /** - * @return The total duration of the request in milliseconds. - */ - @JsonIgnore - public long getTotalDuration() { - return totalDuration; - } - - /** - * @return The duration of the model loading in milliseconds. - */ - @JsonIgnore - public long getLoadDuration() { - return loadDuration; - } - - /** - * @return The number of prompt evaluations. - */ - @JsonIgnore - public long getPromptEvalCount() { - return promptEvalCount; - } - - /** - * @return The duration of the prompt evaluation in milliseconds. - */ - @JsonIgnore - public long getPromptEvalDuration() { - return promptEvalDuration; - } - - /** - * @return The number of evaluations. - */ - @JsonIgnore - public long getEvalCount() { - return evalCount; - } - - /** - * @return The duration of the evaluation in milliseconds. - */ - @JsonIgnore - public long getEvalDuration() { - return evalDuration; - } - - /** - * @return The model used for the request. - */ - @JsonIgnore - public String getModel() { - return model; - } + @JsonIgnoreProperties(ignoreUnknown = true) + public record OpenAiError(@JsonProperty("message") String message) {} - /** - * @return The creation time of the request in ISO 8601 format. - */ - @JsonIgnore - public String getCreatedAt() { - return createdAt; - } - - /** - * @return The response from the model. - */ @JsonIgnore public String getResponse() { - return response; - } - - /** - * @return Whether the request is done or not. - */ - @JsonIgnore - public boolean isDone() { - return done; - } - - /** - * @return The reason why the request is done. - */ - @JsonIgnore - public String getDoneReason() { - return doneReason; - } - - /** - * @return The context of the request. - */ - @JsonIgnore - public long[] getContext() { - return context; + if (choices != null && !choices.isEmpty() && choices.get(0).message() != null) { + return choices.get(0).message().content(); + } + return null; } @JsonIgnore - public String getError() { - return error; + public String getErrorMessage() { + return error != null ? error.message() : null; } -} +} \ No newline at end of file diff --git a/src/test/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaClientTest.java b/src/test/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaClientTest.java index d7b5a53..8d5864c 100644 --- a/src/test/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaClientTest.java +++ b/src/test/java/de/unistuttgart/iste/meitrex/common/ollama/OllamaClientTest.java @@ -78,9 +78,14 @@ void testStartQuerySuccess() throws Exception { String ollamaJsonResponse = """ { "model": "mixtral:8x22b", - "response": "{\\"result\\": 2}", - "done": true, - "done_reason": "stop" + "choices": [ + { + "message": { + "role": "assistant", + "content": "{\\"result\\": 2}" + } + } + ] } """; @@ -159,7 +164,14 @@ void testStartQueryHandlesOllamaError() throws Exception { when(jsonSchemaService.getJsonSchema(any())).thenReturn("{\"properties\":{}}"); - String errorJson = "{\"error\": \"Authentication Error\"}"; + String errorJson = """ + { + "error": { + "message": "Authentication Error", + "type": "invalid_request_error" + } + } + """; @SuppressWarnings("unchecked") HttpResponse mockHttpResponse = mock(HttpResponse.class); @@ -194,9 +206,14 @@ void testStartQueryHandlesInvalidContentJson() throws Exception { String brokenContentJsonResponse = """ { "model": "mixtral:8x22b", - "response": "This is not valid JSON text", - "done": true, - "done_reason": "stop" + "choices": [ + { + "message": { + "role": "assistant", + "content": "This is not valid JSON text" + } + } + ] } """;