diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java index 9ecca5a2930..84d72346304 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ */ public class OllamaChatAutoConfigurationIT extends BaseOllamaIT { - private static final String MODEL_NAME = OllamaModel.LLAMA3_2.getName(); + private static final String MODEL_NAME = OllamaModel.QWEN_2_5_3B.getName(); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( // @formatter:off diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaImage.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaImage.java index 5bb7547d0f7..0d17057a516 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaImage.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaImage.java @@ -18,7 +18,7 @@ public final class OllamaImage { - public static final String DEFAULT_IMAGE = "ollama/ollama:0.5.7"; + public static final String DEFAULT_IMAGE = "ollama/ollama:0.10.1"; private OllamaImage() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java index 8098f0b1866..bb8c06d8bb0 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ai.ollama.api.OllamaModel; import reactor.core.publisher.Flux; import org.springframework.ai.chat.messages.AssistantMessage; @@ -44,7 +45,7 @@ public class FunctionCallbackInPromptIT extends BaseOllamaIT { private static final Logger logger = LoggerFactory.getLogger(FunctionCallbackInPromptIT.class); - private static final String MODEL_NAME = "qwen2.5:3b"; + private static final String MODEL_NAME = OllamaModel.QWEN_2_5_3B.getName(); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( // @formatter:off diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java index 837129a4868..e0698688dba 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ai.ollama.api.OllamaModel; import reactor.core.publisher.Flux; import org.springframework.ai.chat.messages.AssistantMessage; @@ -55,7 +56,7 @@ public class OllamaFunctionToolBeanIT extends BaseOllamaIT { private static final Logger logger = LoggerFactory.getLogger(OllamaFunctionToolBeanIT.class); - private static final String MODEL_NAME = "qwen2.5:3b"; + private static final String MODEL_NAME = OllamaModel.QWEN_2_5_3B.getName(); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( // @formatter:off diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java index b481386a479..48f2e6b9ad6 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java @@ -65,9 +65,6 @@ public static Builder builder() { private static final Log logger = LogFactory.getLog(OllamaApi.class); - - private static final String DEFAULT_BASE_URL = "http://localhost:11434"; - private final RestClient restClient; private final WebClient webClient; @@ -80,14 +77,11 @@ public static Builder builder() { * @param responseErrorHandler Response error handler. */ private OllamaApi(String baseUrl, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) { - - Consumer defaultHeaders = headers -> { headers.setContentType(MediaType.APPLICATION_JSON); headers.setAccept(List.of(MediaType.APPLICATION_JSON)); }; - this.restClient = restClientBuilder .clone() .baseUrl(baseUrl) @@ -95,7 +89,6 @@ private OllamaApi(String baseUrl, RestClient.Builder restClientBuilder, WebClien .defaultStatusHandler(responseErrorHandler) .build(); - this.webClient = webClientBuilder .clone() .baseUrl(baseUrl) @@ -260,7 +253,9 @@ public Flux pullModel(PullModelRequest pullModelRequest) { * @param content The content of the message. * @param images The list of base64-encoded images to send with the message. * Requires multimodal models such as llava or bakllava. - * @param toolCalls The relevant tool call. + * @param toolCalls The list of tools that the model wants to use. + * @param toolName The name of the tool that was executed to inform the model of the result. + * @param thinking The model's thinking process. Requires thinking models such as qwen3. */ @JsonInclude(Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) @@ -268,7 +263,10 @@ public record Message( @JsonProperty("role") Role role, @JsonProperty("content") String content, @JsonProperty("images") List images, - @JsonProperty("tool_calls") List toolCalls) { + @JsonProperty("tool_calls") List toolCalls, + @JsonProperty("tool_name") String toolName, + @JsonProperty("thinking") String thinking + ) { public static Builder builder(Role role) { return new Builder(role); @@ -317,11 +315,19 @@ public record ToolCall( * * @param name The name of the function. * @param arguments The arguments that the model expects you to pass to the function. + * @param index The index of the function call in the list of tool calls. */ @JsonInclude(Include.NON_NULL) public record ToolCallFunction( @JsonProperty("name") String name, - @JsonProperty("arguments") Map arguments) { + @JsonProperty("arguments") Map arguments, + @JsonProperty("index") Integer index + ) { + + public ToolCallFunction(String name, Map arguments) { + this(name, arguments, null); + } + } public static class Builder { @@ -330,6 +336,8 @@ public static class Builder { private String content; private List images; private List toolCalls; + private String toolName; + private String thinking; public Builder(Role role) { this.role = role; @@ -350,8 +358,18 @@ public Builder toolCalls(List toolCalls) { return this; } + public Builder toolName(String toolName) { + this.toolName = toolName; + return this; + } + + public Builder thinking(String thinking) { + this.thinking = thinking; + return this; + } + public Message build() { - return new Message(this.role, this.content, this.images, this.toolCalls); + return new Message(this.role, this.content, this.images, this.toolCalls, this.toolName, this.thinking); } } } @@ -367,6 +385,7 @@ public Message build() { * @param tools List of tools the model has access to. * @param options Model-specific options. For example, "temperature" can be set through this field, if the model supports it. * You can use the {@link OllamaOptions} builder to create the options then {@link OllamaOptions#toMap()} to convert the options into a map. + * @param think Think controls whether thinking/reasoning models will think before responding. * * @see Chat @@ -382,7 +401,8 @@ public record ChatRequest( @JsonProperty("format") Object format, @JsonProperty("keep_alive") String keepAlive, @JsonProperty("tools") List tools, - @JsonProperty("options") Map options + @JsonProperty("options") Map options, + @JsonProperty("think") Boolean think ) { public static Builder builder(String model) { @@ -455,6 +475,7 @@ public static class Builder { private String keepAlive; private List tools = List.of(); private Map options = Map.of(); + private Boolean think; public Builder(String model) { Assert.notNull(model, "The model can not be null."); @@ -499,8 +520,13 @@ public Builder options(OllamaOptions options) { return this; } + public Builder think(Boolean think) { + this.think = think; + return this; + } + public ChatRequest build() { - return new ChatRequest(this.model, this.messages, this.stream, this.format, this.keepAlive, this.tools, this.options); + return new ChatRequest(this.model, this.messages, this.stream, this.format, this.keepAlive, this.tools, this.options, this.think); } } } diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaModel.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaModel.java index 7602eca2584..4679b6e2539 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaModel.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaModel.java @@ -27,11 +27,30 @@ */ public enum OllamaModel implements ChatModelDescription { + QWEN_2_5_3B("qwen2.5:3b"), + /** * Qwen 2.5 */ QWEN_2_5_7B("qwen2.5"), + /** + * Flagship vision-language model of Qwen and also a significant leap from the + * previous Qwen2-VL. + */ + QWEN2_5_VL("qwen2.5vl"), + + /** + * Qwen3 is the latest generation of large language models in Qwen series, offering a + * comprehensive suite of dense and mixture-of-experts (MoE) models. + */ + QWEN3_7B("qwen3:7b"), + + /** + * Qwen3 4B + */ + QWEN3_4B("qwen3:4b"), + /** * QwQ is the reasoning model of the Qwen series. */ @@ -139,6 +158,11 @@ public enum OllamaModel implements ChatModelDescription { */ GEMMA("gemma"), + /** + * The current, most capable model that runs on a single GPU. + */ + GEMMA3("gemma3"), + /** * Uncensored Llama 2 model */ diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java index a71be1ce2b2..64da524c653 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java @@ -63,8 +63,11 @@ public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions { /** * Whether to use NUMA. (Default: false) + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("numa") + @Deprecated private Boolean useNUMA; /** @@ -99,27 +102,39 @@ public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions { /** * (Default: false) + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("low_vram") + @Deprecated private Boolean lowVRAM; /** * (Default: true) + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("f16_kv") + @Deprecated private Boolean f16KV; /** * Return logits for all the tokens, not just the last one. * To enable completions to return logprobs, this must be true. + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("logits_all") + @Deprecated private Boolean logitsAll; /** * Load only the vocabulary, not the weights. + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("vocab_only") + @Deprecated private Boolean vocabOnly; /** @@ -139,8 +154,11 @@ public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions { * This can improve performance but trades away some of the advantages of memory-mapping * by requiring more RAM to run and potentially slowing down load times as the model loads into RAM. * (Default: false) + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("use_mlock") + @Deprecated private Boolean useMLock; /** @@ -205,8 +223,11 @@ public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions { * Tail free sampling is used to reduce the impact of less probable tokens * from the output. A higher value (e.g., 2.0) will reduce the impact more, while a * value of 1.0 disables this setting. (default: 1) + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("tfs_z") + @Deprecated private Float tfsZ; /** @@ -252,29 +273,41 @@ public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions { /** * Enable Mirostat sampling for controlling perplexity. (default: 0, 0 * = disabled, 1 = Mirostat, 2 = Mirostat 2.0) + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("mirostat") + @Deprecated private Integer mirostat; /** * Controls the balance between coherence and diversity of the output. * A lower value will result in more focused and coherent text. (Default: 5.0) + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("mirostat_tau") + @Deprecated private Float mirostatTau; /** * Influences how quickly the algorithm responds to feedback from the generated text. * A lower learning rate will result in slower adjustments, while a higher learning rate * will make the algorithm more responsive. (Default: 0.1) + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("mirostat_eta") + @Deprecated private Float mirostatEta; /** * (Default: true) + * + * @deprecated Not supported in Ollama anymore. */ @JsonProperty("penalize_newline") + @Deprecated private Boolean penalizeNewline; /** @@ -429,10 +462,18 @@ public void setKeepAlive(String keepAlive) { this.keepAlive = keepAlive; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Boolean getUseNUMA() { return this.useNUMA; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setUseNUMA(Boolean useNUMA) { this.useNUMA = useNUMA; } @@ -469,34 +510,66 @@ public void setMainGPU(Integer mainGPU) { this.mainGPU = mainGPU; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Boolean getLowVRAM() { return this.lowVRAM; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setLowVRAM(Boolean lowVRAM) { this.lowVRAM = lowVRAM; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Boolean getF16KV() { return this.f16KV; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setF16KV(Boolean f16kv) { this.f16KV = f16kv; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Boolean getLogitsAll() { return this.logitsAll; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setLogitsAll(Boolean logitsAll) { this.logitsAll = logitsAll; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Boolean getVocabOnly() { return this.vocabOnly; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setVocabOnly(Boolean vocabOnly) { this.vocabOnly = vocabOnly; } @@ -509,10 +582,18 @@ public void setUseMMap(Boolean useMMap) { this.useMMap = useMMap; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Boolean getUseMLock() { return this.useMLock; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setUseMLock(Boolean useMLock) { this.useMLock = useMLock; } @@ -586,10 +667,18 @@ public void setMinP(Double minP) { this.minP = minP; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Float getTfsZ() { return this.tfsZ; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setTfsZ(Float tfsZ) { this.tfsZ = tfsZ; } @@ -645,34 +734,66 @@ public void setFrequencyPenalty(Double frequencyPenalty) { this.frequencyPenalty = frequencyPenalty; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Integer getMirostat() { return this.mirostat; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setMirostat(Integer mirostat) { this.mirostat = mirostat; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Float getMirostatTau() { return this.mirostatTau; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setMirostatTau(Float mirostatTau) { this.mirostatTau = mirostatTau; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Float getMirostatEta() { return this.mirostatEta; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setMirostatEta(Float mirostatEta) { this.mirostatEta = mirostatEta; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Boolean getPenalizeNewline() { return this.penalizeNewline; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public void setPenalizeNewline(Boolean penalizeNewline) { this.penalizeNewline = penalizeNewline; } @@ -852,6 +973,10 @@ public Builder truncate(Boolean truncate) { return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder useNUMA(Boolean useNUMA) { this.options.useNUMA = useNUMA; return this; @@ -877,21 +1002,37 @@ public Builder mainGPU(Integer mainGPU) { return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder lowVRAM(Boolean lowVRAM) { this.options.lowVRAM = lowVRAM; return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder f16KV(Boolean f16KV) { this.options.f16KV = f16KV; return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder logitsAll(Boolean logitsAll) { this.options.logitsAll = logitsAll; return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder vocabOnly(Boolean vocabOnly) { this.options.vocabOnly = vocabOnly; return this; @@ -902,6 +1043,10 @@ public Builder useMMap(Boolean useMMap) { return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder useMLock(Boolean useMLock) { this.options.useMLock = useMLock; return this; @@ -942,6 +1087,10 @@ public Builder minP(Double minP) { return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder tfsZ(Float tfsZ) { this.options.tfsZ = tfsZ; return this; @@ -977,21 +1126,37 @@ public Builder frequencyPenalty(Double frequencyPenalty) { return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder mirostat(Integer mirostat) { this.options.mirostat = mirostat; return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder mirostatTau(Float mirostatTau) { this.options.mirostatTau = mirostatTau; return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder mirostatEta(Float mirostatEta) { this.options.mirostatEta = mirostatEta; return this; } + /** + * @deprecated Not supported in Ollama anymore. + */ + @Deprecated public Builder penalizeNewline(Boolean penalizeNewline) { this.options.penalizeNewline = penalizeNewline; return this; diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/BaseOllamaIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/BaseOllamaIT.java index d5601cd78ca..6ed9f2a11a8 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/BaseOllamaIT.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/BaseOllamaIT.java @@ -49,19 +49,19 @@ public abstract class BaseOllamaIT { /** * Initialize the Ollama container and API with the specified model. This method * should be called from @BeforeAll in subclasses. - * @param model the Ollama model to initialize (must not be null or empty) + * @param models the Ollama models to initialize (must not be null or empty) * @return configured OllamaApi instance * @throws IllegalArgumentException if model is null or empty */ - protected static OllamaApi initializeOllama(final String model) { - Assert.hasText(model, "Model name must be provided"); + protected static OllamaApi initializeOllama(String... models) { + Assert.notEmpty(models, "at least one model name must be provided"); if (!SKIP_CONTAINER_CREATION) { ollamaContainer = new OllamaContainer(OllamaImage.DEFAULT_IMAGE).withReuse(true); ollamaContainer.start(); } - final OllamaApi api = buildOllamaApiWithModel(model); + final OllamaApi api = buildOllamaApiWithModel(models); ollamaApi.set(api); return api; } @@ -84,20 +84,22 @@ public static void tearDown() { } } - private static OllamaApi buildOllamaApiWithModel(final String model) { + private static OllamaApi buildOllamaApiWithModel(String... models) { final String baseUrl = SKIP_CONTAINER_CREATION ? OLLAMA_LOCAL_URL : ollamaContainer.getEndpoint(); final OllamaApi api = OllamaApi.builder().baseUrl(baseUrl).build(); - ensureModelIsPresent(api, model); + ensureModelIsPresent(api, models); return api; } - private static void ensureModelIsPresent(final OllamaApi ollamaApi, final String model) { + private static void ensureModelIsPresent(final OllamaApi ollamaApi, String... models) { final var modelManagementOptions = ModelManagementOptions.builder() .maxRetries(DEFAULT_MAX_RETRIES) .timeout(DEFAULT_TIMEOUT) .build(); final var ollamaModelManager = new OllamaModelManager(ollamaApi, modelManagementOptions); - ollamaModelManager.pullModel(model, PullModelStrategy.WHEN_MISSING); + for (String model : models) { + ollamaModelManager.pullModel(model, PullModelStrategy.WHEN_MISSING); + } } } diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelFunctionCallingIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelFunctionCallingIT.java index f8ec3091c3f..c1b19d466c1 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelFunctionCallingIT.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelFunctionCallingIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ai.ollama.api.OllamaModel; import reactor.core.publisher.Flux; import org.springframework.ai.chat.messages.AssistantMessage; @@ -49,7 +50,7 @@ class OllamaChatModelFunctionCallingIT extends BaseOllamaIT { private static final Logger logger = LoggerFactory.getLogger(OllamaChatModelFunctionCallingIT.class); - private static final String MODEL = "qwen2.5:3b"; + private static final String MODEL = OllamaModel.QWEN_2_5_3B.getName(); @Autowired ChatModel chatModel; diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelIT.java index ae79f67350e..6ff152404fe 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelIT.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelIT.java @@ -22,7 +22,6 @@ import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonProperty; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.ai.chat.client.ChatClient; @@ -69,7 +68,7 @@ @SpringBootTest class OllamaChatModelIT extends BaseOllamaIT { - private static final String MODEL = OllamaModel.LLAMA3_2.getName(); + private static final String MODEL = OllamaModel.QWEN_2_5_3B.getName(); private static final String ADDITIONAL_MODEL = "tinyllama"; @@ -257,7 +256,6 @@ void beanStreamOutputConverterRecords() { // Example inspired by https://ollama.com/blog/structured-outputs @Test - @Disabled("Pending review") void jsonSchemaFormatStructuredOutput() { var outputConverter = new BeanOutputConverter<>(CountryInfo.class); var userPromptTemplate = new PromptTemplate(""" @@ -265,10 +263,7 @@ void jsonSchemaFormatStructuredOutput() { """); Map model = Map.of("country", "denmark"); var prompt = userPromptTemplate.create(model, - OllamaOptions.builder() - .model(OllamaModel.LLAMA3_2.getName()) - .format(outputConverter.getJsonSchemaMap()) - .build()); + OllamaOptions.builder().model(MODEL).format(outputConverter.getJsonSchemaMap()).build()); var chatResponse = this.chatModel.call(prompt); diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java index 3174c459afb..cd8addca1e5 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,15 @@ package org.springframework.ai.ollama; -import java.time.Duration; -import java.util.List; - import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.content.Media; import org.springframework.ai.ollama.api.OllamaApi; +import org.springframework.ai.ollama.api.OllamaModel; import org.springframework.ai.ollama.api.OllamaOptions; -import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.TransientAiException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; @@ -41,6 +37,9 @@ import org.springframework.retry.support.RetryTemplate; import org.springframework.util.MimeTypeUtils; +import java.time.Duration; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -49,14 +48,14 @@ class OllamaChatModelMultimodalIT extends BaseOllamaIT { private static final Logger logger = LoggerFactory.getLogger(OllamaChatModelMultimodalIT.class); - private static final String MODEL = "llava-phi3"; + private static final String MODEL = OllamaModel.GEMMA3.getName(); @Autowired private OllamaChatModel chatModel; @Test void unsupportedMediaType() { - var imageData = new ClassPathResource("/norway.webp"); + var imageData = new ClassPathResource("/something.adoc"); var userMessage = UserMessage.builder() .text("Explain what do you see in this picture?") diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelObservationIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelObservationIT.java index 0d8b6a0b714..b6d9948dd4f 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelObservationIT.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelObservationIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,7 +53,7 @@ @SpringBootTest(classes = OllamaChatModelObservationIT.Config.class) public class OllamaChatModelObservationIT extends BaseOllamaIT { - private static final String MODEL = OllamaModel.LLAMA3_2.getName(); + private static final String MODEL = OllamaModel.QWEN_2_5_3B.getName(); @Autowired TestObservationRegistry observationRegistry; diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaImage.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaImage.java index e82ecb9ab67..e027789ff5a 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaImage.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaImage.java @@ -23,7 +23,7 @@ */ public final class OllamaImage { - public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("ollama/ollama:0.6.7"); + public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("ollama/ollama:0.10.1"); private OllamaImage() { diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/api/OllamaApiIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/api/OllamaApiIT.java index 98af032efbd..fbbefdcaf17 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/api/OllamaApiIT.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/api/OllamaApiIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,16 @@ package org.springframework.ai.ollama.api; -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - import org.springframework.ai.ollama.BaseOllamaIT; -import org.springframework.ai.ollama.api.OllamaApi.ChatRequest; -import org.springframework.ai.ollama.api.OllamaApi.ChatResponse; -import org.springframework.ai.ollama.api.OllamaApi.EmbeddingsRequest; -import org.springframework.ai.ollama.api.OllamaApi.EmbeddingsResponse; -import org.springframework.ai.ollama.api.OllamaApi.Message; +import org.springframework.ai.ollama.api.OllamaApi.*; import org.springframework.ai.ollama.api.OllamaApi.Message.Role; +import reactor.core.publisher.Flux; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; @@ -40,16 +35,20 @@ */ public class OllamaApiIT extends BaseOllamaIT { - private static final String MODEL = OllamaModel.LLAMA3_2.getName(); + private static final String CHAT_MODEL = OllamaModel.QWEN_2_5_3B.getName(); + + private static final String EMBEDDING_MODEL = OllamaModel.NOMIC_EMBED_TEXT.getName(); + + private static final String THINKING_MODEL = OllamaModel.QWEN3_4B.getName(); @BeforeAll public static void beforeAll() throws IOException, InterruptedException { - initializeOllama(MODEL); + initializeOllama(CHAT_MODEL, EMBEDDING_MODEL, THINKING_MODEL); } @Test public void chat() { - var request = ChatRequest.builder(MODEL) + var request = ChatRequest.builder(CHAT_MODEL) .stream(false) .messages(List.of( Message.builder(Role.SYSTEM) @@ -67,7 +66,7 @@ public void chat() { System.out.println(response); assertThat(response).isNotNull(); - assertThat(response.model()).contains(MODEL); + assertThat(response.model()).contains(CHAT_MODEL); assertThat(response.done()).isTrue(); assertThat(response.message().role()).isEqualTo(Role.ASSISTANT); assertThat(response.message().content()).contains("Sofia"); @@ -75,7 +74,7 @@ public void chat() { @Test public void streamingChat() { - var request = ChatRequest.builder(MODEL) + var request = ChatRequest.builder(CHAT_MODEL) .stream(true) .messages(List.of(Message.builder(Role.USER) .content("What is the capital of Bulgaria and what is the size? " + "What it the national anthem?") @@ -101,17 +100,45 @@ public void streamingChat() { @Test public void embedText() { - EmbeddingsRequest request = new EmbeddingsRequest(MODEL, "I like to eat apples"); + EmbeddingsRequest request = new EmbeddingsRequest(EMBEDDING_MODEL, "I like to eat apples"); EmbeddingsResponse response = getOllamaApi().embed(request); assertThat(response).isNotNull(); assertThat(response.embeddings()).hasSize(1); - assertThat(response.embeddings().get(0)).hasSize(3072); - assertThat(response.model()).isEqualTo(MODEL); + assertThat(response.embeddings().get(0)).hasSize(768); + assertThat(response.model()).isEqualTo(EMBEDDING_MODEL); assertThat(response.promptEvalCount()).isEqualTo(5); assertThat(response.loadDuration()).isGreaterThan(1); assertThat(response.totalDuration()).isGreaterThan(1); } + @Test + public void think() { + var request = ChatRequest.builder(THINKING_MODEL) + .stream(false) + .messages(List.of( + Message.builder(Role.SYSTEM) + .content("You are geography teacher. You are talking to a student.") + .build(), + Message.builder(Role.USER) + .content("What is the capital of Bulgaria and what is the size? " + + "What it the national anthem?") + .build())) + .options(OllamaOptions.builder().temperature(0.9).build()) + .think(true) + .build(); + + ChatResponse response = getOllamaApi().chat(request); + + System.out.println(response); + + assertThat(response).isNotNull(); + assertThat(response.model()).contains(THINKING_MODEL); + assertThat(response.done()).isTrue(); + assertThat(response.message().role()).isEqualTo(Role.ASSISTANT); + assertThat(response.message().content()).contains("Sofia"); + assertThat(response.message().thinking()).isNotEmpty(); + } + } diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/api/tool/OllamaApiToolFunctionCallIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/api/tool/OllamaApiToolFunctionCallIT.java index 104cd91ce08..9e3aa8e35fb 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/api/tool/OllamaApiToolFunctionCallIT.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/api/tool/OllamaApiToolFunctionCallIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.springframework.ai.ollama.api.OllamaApi.Message; import org.springframework.ai.ollama.api.OllamaApi.Message.Role; import org.springframework.ai.ollama.api.OllamaApi.Message.ToolCall; +import org.springframework.ai.ollama.api.OllamaModel; import static org.assertj.core.api.Assertions.assertThat; @@ -42,7 +43,7 @@ */ public class OllamaApiToolFunctionCallIT extends BaseOllamaIT { - private static final String MODEL = "qwen2.5:3b"; + private static final String MODEL = OllamaModel.QWEN_2_5_3B.getName(); private static final Logger logger = LoggerFactory.getLogger(OllamaApiToolFunctionCallIT.class); diff --git a/models/spring-ai-ollama/src/test/resources/something.adoc b/models/spring-ai-ollama/src/test/resources/something.adoc new file mode 100644 index 00000000000..5ab2f8a4323 --- /dev/null +++ b/models/spring-ai-ollama/src/test/resources/something.adoc @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/proxy/OllamaWithOpenAiChatModelIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/proxy/OllamaWithOpenAiChatModelIT.java index 1b0204dfb29..375cb389ab0 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/proxy/OllamaWithOpenAiChatModelIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/proxy/OllamaWithOpenAiChatModelIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.junit.jupiter.Container; @@ -76,10 +74,12 @@ class OllamaWithOpenAiChatModelIT { private static final Logger logger = LoggerFactory.getLogger(OllamaWithOpenAiChatModelIT.class); - private static final String DEFAULT_OLLAMA_MODEL = "mistral"; + private static final String DEFAULT_OLLAMA_MODEL = "qwen2.5:3b"; + + private static final String MULTIMODAL_MODEL = "gemma3:4b"; @Container - static OllamaContainer ollamaContainer = new OllamaContainer("ollama/ollama:0.5.7"); + static OllamaContainer ollamaContainer = new OllamaContainer("ollama/ollama:0.10.1"); static String baseUrl = "http://localhost:11434"; @@ -93,8 +93,7 @@ class OllamaWithOpenAiChatModelIT { public static void beforeAll() throws IOException, InterruptedException { logger.info("Start pulling the '" + DEFAULT_OLLAMA_MODEL + " ' generative ... would take several minutes ..."); ollamaContainer.execInContainer("ollama", "pull", DEFAULT_OLLAMA_MODEL); - ollamaContainer.execInContainer("ollama", "pull", "llava"); - ollamaContainer.execInContainer("ollama", "pull", "llama3.2:1b"); + ollamaContainer.execInContainer("ollama", "pull", MULTIMODAL_MODEL); logger.info(DEFAULT_OLLAMA_MODEL + " pulling competed!"); baseUrl = "http://" + ollamaContainer.getHost() + ":" + ollamaContainer.getMappedPort(11434); @@ -102,20 +101,18 @@ public static void beforeAll() throws IOException, InterruptedException { @Test void roleTest() { - UserMessage userMessage = new UserMessage( - "Tell me about 3 famous pirates from the Golden Age of Piracy and what they did."); + UserMessage userMessage = new UserMessage("What's the capital of Denmark?"); SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(this.systemResource); Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate")); Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); ChatResponse response = this.chatModel.call(prompt); assertThat(response.getResults()).hasSize(1); - assertThat(response.getResults().get(0).getOutput().getText()).contains("Blackbeard"); + assertThat(response.getResults().get(0).getOutput().getText()).contains("Copenhagen"); } @Test void streamRoleTest() { - UserMessage userMessage = new UserMessage( - "Tell me about 3 famous pirates from the Golden Age of Piracy and what they did."); + UserMessage userMessage = new UserMessage("What's the capital of Denmark?"); SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(this.systemResource); Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate")); Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); @@ -131,11 +128,10 @@ void streamRoleTest() { .map(AssistantMessage::getText) .collect(Collectors.joining()); - assertThat(stitchedResponseContent).contains("Blackbeard"); + assertThat(stitchedResponseContent).contains("Copenhagen"); } @Test - @Disabled("Not supported by the current Ollama API") void streamingWithTokenUsage() { var promptOptions = OpenAiChatOptions.builder().streamUsage(true).seed(1).build(); @@ -173,7 +169,6 @@ void listOutputConverter() { List list = outputConverter.convert(generation.getOutput().getText()); assertThat(list).hasSize(5); - } @Test @@ -199,7 +194,6 @@ void mapOutputConverter() { @Test void beanOutputConverter() { - BeanOutputConverter outputConverter = new BeanOutputConverter<>(ActorsFilms.class); String format = outputConverter.getFormat(); @@ -220,7 +214,6 @@ void beanOutputConverter() { @Test void beanOutputConverterRecords() { - BeanOutputConverter outputConverter = new BeanOutputConverter<>(ActorsFilmsRecord.class); String format = outputConverter.getFormat(); @@ -243,7 +236,6 @@ void beanOutputConverterRecords() { @Test void beanStreamOutputConverterRecords() { - BeanOutputConverter outputConverter = new BeanOutputConverter<>(ActorsFilmsRecord.class); String format = outputConverter.getFormat(); @@ -273,17 +265,15 @@ void beanStreamOutputConverterRecords() { assertThat(actorsFilms.movies()).hasSize(5); } - @ParameterizedTest(name = "{0} : {displayName} ") - @ValueSource(strings = { "llama3.1:latest", "llama3.2:latest" }) - void functionCallTest(String modelName) { - + @Test + void functionCallTest() { UserMessage userMessage = new UserMessage( "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); List messages = new ArrayList<>(List.of(userMessage)); var promptOptions = OpenAiChatOptions.builder() - .model(modelName) + .model(DEFAULT_OLLAMA_MODEL) // Note for Ollama you must set the tool choice to explicitly. Unlike OpenAI // (which defaults to "auto") Ollama defaults to "nono" .toolChoice("auto") @@ -300,17 +290,15 @@ void functionCallTest(String modelName) { assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); } - @ParameterizedTest(name = "{0} : {displayName} ") - @ValueSource(strings = { "llama3.1:latest", "llama3.2:latest" }) - void streamFunctionCallTest(String modelName) { - + @Test + void streamFunctionCallTest() { UserMessage userMessage = new UserMessage( "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); List messages = new ArrayList<>(List.of(userMessage)); var promptOptions = OpenAiChatOptions.builder() - .model(modelName) + .model(DEFAULT_OLLAMA_MODEL) // Note for Ollama you must set the tool choice to explicitly. Unlike OpenAI // (which defaults to "auto") Ollama defaults to "nono" .toolChoice("auto") @@ -335,10 +323,8 @@ void streamFunctionCallTest(String modelName) { assertThat(content).contains("30", "10", "15"); } - @ParameterizedTest(name = "{0} : {displayName} ") - @ValueSource(strings = { "llava" }) - void multiModalityEmbeddedImage(String modelName) throws IOException { - + @Test + void multiModalityEmbeddedImage() { var imageData = new ClassPathResource("/test.png"); var userMessage = UserMessage.builder() @@ -347,7 +333,7 @@ void multiModalityEmbeddedImage(String modelName) throws IOException { .build(); var response = this.chatModel - .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().model(modelName).build())); + .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().model(MULTIMODAL_MODEL).build())); logger.info(response.getResult().getOutput().getText()); assertThat(response.getResult().getOutput().getText()).containsAnyOf("bananas", "apple", "bowl", "basket", @@ -355,9 +341,8 @@ void multiModalityEmbeddedImage(String modelName) throws IOException { } @Disabled("Not supported by the current Ollama API") - @ParameterizedTest(name = "{0} : {displayName} ") - @ValueSource(strings = { "llava" }) - void multiModalityImageUrl(String modelName) throws IOException { + @Test + void multiModalityImageUrl() { var userMessage = UserMessage.builder() .text("Explain what do you see on this picture?") @@ -368,7 +353,7 @@ void multiModalityImageUrl(String modelName) throws IOException { .build(); ChatResponse response = this.chatModel - .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().model(modelName).build())); + .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().model(MULTIMODAL_MODEL).build())); logger.info(response.getResult().getOutput().getText()); assertThat(response.getResult().getOutput().getText()).containsAnyOf("bananas", "apple", "bowl", "basket", @@ -376,9 +361,8 @@ void multiModalityImageUrl(String modelName) throws IOException { } @Disabled("Not supported by the current Ollama API") - @ParameterizedTest(name = "{0} : {displayName} ") - @ValueSource(strings = { "llava" }) - void streamingMultiModalityImageUrl(String modelName) throws IOException { + @Test + void streamingMultiModalityImageUrl() { var userMessage = UserMessage.builder() .text("Explain what do you see on this picture?") @@ -389,7 +373,7 @@ void streamingMultiModalityImageUrl(String modelName) throws IOException { .build(); Flux response = this.chatModel - .stream(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().model(modelName).build())); + .stream(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().model(MULTIMODAL_MODEL).build())); String content = response.collectList() .block() @@ -403,12 +387,11 @@ void streamingMultiModalityImageUrl(String modelName) throws IOException { assertThat(content).containsAnyOf("bananas", "apple", "bowl", "basket", "fruit stand"); } - @ParameterizedTest(name = "{0} : {displayName} ") - @ValueSource(strings = { "mistral" }) - void validateCallResponseMetadata(String model) { + @Test + void validateCallResponseMetadata() { // @formatter:off ChatResponse response = ChatClient.create(this.chatModel).prompt() - .options(OpenAiChatOptions.builder().model(model).build()) + .options(OpenAiChatOptions.builder().model(DEFAULT_OLLAMA_MODEL).build()) .user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did") .call() .chatResponse(); @@ -416,7 +399,7 @@ void validateCallResponseMetadata(String model) { logger.info(response.toString()); assertThat(response.getMetadata().getId()).isNotEmpty(); - assertThat(response.getMetadata().getModel()).containsIgnoringCase(model); + assertThat(response.getMetadata().getModel()).containsIgnoringCase(DEFAULT_OLLAMA_MODEL); assertThat(response.getMetadata().getUsage().getPromptTokens()).isPositive(); assertThat(response.getMetadata().getUsage().getCompletionTokens()).isPositive(); assertThat(response.getMetadata().getUsage().getTotalTokens()).isPositive(); diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaImage.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaImage.java index 0c2703069a8..17dc6e840df 100644 --- a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaImage.java +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaImage.java @@ -23,7 +23,7 @@ */ public final class OllamaImage { - public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("ollama/ollama:0.5.7"); + public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("ollama/ollama:0.10.1"); private OllamaImage() {