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
- spring-ai-starter-model-ollama autoconfigures ChatModel/EmbeddingModel from
spring.ai.ollama.* properties alone; no API key anywhere in this module.
- org.testcontainers:ollama and org.testcontainers:junit-jupiter were both
renamed in the Testcontainers 2.x line -- to org.testcontainers:testcontainers-ollama
and org.testcontainers:testcontainers-junit-jupiter respectively -- confirmed by
reading the real testcontainers-bom-2.0.5.pom that Spring Boot 4.1.1 imports
(spring-boot-dependencies -> testcontainers.version=2.0.5). The pre-rename
artifact IDs still exist on Maven Central but are stuck on the 1.x line.
- Unlike every other module in this series, tests drive a real local model
(qwen2.5:0.5b chat, all-minilm embeddings) via a Testcontainers-managed
OllamaContainer started from a baked image (scripts/bake-image.sh), not a
ScriptedChatModel -- the whole point of this post is a real model answering
a real prompt.
- LocalChatAndEmbeddingTest forces a genuine cold state with Ollama's
keep_alive: 0 option (set via ChatModel.call(Prompt) -- ChatClient.options()
does not carry a keepAlive override through to the request in this version)
and confirms the unload actually happened via /api/ps before measuring a
reload, rather than trusting whichever call happens to run first.
- On this quiet sandbox host, even a confirmed-cold reload of the 500MB model
came back in single-digit milliseconds once the underlying image layers were
cached -- eval (generation) time dominates total latency here, not loading.
Captured, not asserted as universal: readers get scripts/bake-image.sh to
get their own numbers.
- Embedding dimension (384, all-minilm) asserted deterministically.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtpJvZfg4nvLvtzgJTDWpB