Files
spring-ai/ollama-local
Claude c1e36c6c09 Fix ollama-local: correct a wrong claim about ChatClient.options() and keepAlive
Self-correction pass caught this before publishing: the previous commit's test
comment and companion post draft claimed a keepAlive set through
ChatClient.prompt().options(...) never reaches the request Ollama receives,
and worked around it by calling ChatModel.call(Prompt) directly instead. That
claim was never actually verified against /api/ps for the ChatClient path --
only inferred from a failed timing assertion that, it turned out, would have
failed the same way even with a genuinely confirmed unload (see below).

Checked directly: unloading via ChatClient.prompt().options(OllamaChatOptions
.builder()...keepAlive("0")).call() and immediately querying /api/ps shows an
empty model registry, same as the ChatModel path. The options merge works
correctly. Simplified the test back to ChatClient throughout, consistent with
the rest of this series, and removed the incorrect comment.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtpJvZfg4nvLvtzgJTDWpB
2026-09-23 17:54:43 +00:00
..

ollama-local

Companion code for Run LLMs Locally with Spring AI and Ollama, part of the Spring AI series on ankurm.com.

spring-ai-starter-model-ollama autoconfigures a ChatModel and an EmbeddingModel from spring.ai.ollama.* properties alone -- no API key anywhere in this module. Every test in LocalChatAndEmbeddingTest.java drives a real, local Ollama server started by Testcontainers, answering with a real small model (qwen2.5:0.5b for chat, all-minilm for embeddings) -- unlike every other module in this series, nothing here is scripted.

Versions

Component Version
Spring Boot 4.1.1
Spring AI 2.0.1
Testcontainers 2.0.5
Java 25 (LTS)

org.testcontainers:ollama was renamed to org.testcontainers:testcontainers-ollama in the Testcontainers 2.x line, and org.testcontainers:junit-jupiter to org.testcontainers:testcontainers-junit-jupiter -- both confirmed by reading the real testcontainers-bom-2.0.5.pom that Spring Boot 4.1.1 imports. If you're copying an older Testcontainers-Ollama tutorial, the old artifact IDs still exist on Maven Central but are stuck on the 1.x line and are not what this BOM resolves.

Quickstart

./scripts/bake-image.sh   # once: pulls qwen2.5:0.5b + all-minilm into a local image
./scripts/run-all.sh      # every time: runs the suite against that baked image

bake-image.sh is the real Testcontainers-recommended pattern for CI: pull the models into a container once, docker commit the result, and every subsequent test run starts a container that already has them on disk -- no registry pull, no network dependency, no per-run latency for the pull itself.

What's here

File What it shows
OllamaLocalApplication.java The whole application: no manual OllamaApi/OllamaChatModel wiring, just the starter's autoconfiguration
config/ChatClientConfig.java Wraps the autoconfigured ChatModel in a ChatClient, same as every other module in this series
application.yml The five spring.ai.ollama.* properties this module uses, with pull-model-strategy: never so the app fails fast instead of silently pulling gigabytes at startup
LocalChatAndEmbeddingTest.java A Testcontainers-managed OllamaContainer, a confirmed model unload via keep_alive: 0 + /api/ps, real chat calls, and a deterministic embedding-dimension assertion

Output files

File Captured from
output/01-chat-reload-after-unload.txt reloadAfterAConfirmedUnload -- unloads the model, confirms via /api/ps, then reloads it
output/02-chat-back-to-back-call.txt backToBackCallReusesTheAlreadyLoadedModel
output/03-embedding-dimensions.txt embeddingsAreDeterministicallySized

Requirements

JDK 25, Maven, Docker. Run scripts/bake-image.sh before the test suite -- without a baked image present, scripts/run-all.sh refuses to start rather than silently pulling ~450MB of models over the network on every test run.