Models — configuring the LLMs¶
Every natural-language feature in lumnik sits behind two small SPIs (ChatPort / EmbeddingPort
in lumnik-llm-spi); the langchain4j wiring is confined to one module, lumnik-llm-langchain.
This page is for the operator: which model does what, the exact knobs, and how to swap a model —
on Ollama or toward OpenAI.
The default provider is Ollama, local. Nothing leaves your machine unless you point it elsewhere.
Three chat models, on purpose¶
lumnik deliberately runs three separately-chosen chat models, because the three jobs have different shapes — and because the judge must not share the generator's blind spots.
Chat — conversational RAG (llama3.2)¶
The default chat model answers semantic questions over indexed chunks (lm chat,
POST /api/rag/chat). It is a general conversational model, chosen lighter/faster — it phrases answers from
retrieved context, it does not write SQL. Override with RAG_CHAT_MODEL.
Analytic — text-to-SQL ask (qwen2.5-coder:7b)¶
The analytic model translates a question into one read-only SQL query
(the five guards veto anything unsafe), and also writes
kind: View manifests on POST /api/views/generate. It needs a code/SQL-capable model:
qwen2.5-coder is far better than llama3.2 at SQL over many columns. It is ~4.7 GB
(ollama pull qwen2.5-coder:7b). Override with ASK_SQL_MODEL.
Judge — faithfulness eval (gemma4)¶
The judge scores whether generated SQL faithfully answers the question — offline/eval only,
never in the request path. It is an independent reviewer, kept distinct from the analytic
generator so it has different blind spots. Default gemma4: a controlled probe across
gemma2/gemma4/qwen3.6/llama3.2 settled it as the lightest judge that reliably catches the
JOIN-type failure mode (INNER where the question's "each X" implies LEFT) without over-flagging.
Optional in production — pull it only if you run the eval harness. Override with JUDGE_MODEL.
Embeddings — model and dimension travel together¶
Semantic indexing and search embed text through one embedding model — default
nomic-embed-text, which produces 768-dimensional vectors. That number is load-bearing:
RAG_EMBEDDING_DIM(default768) feedsquarkus.langchain4j.pgvector.dimension;- the
rag.chunktable's column is created by Flyway asVECTOR(768)(lumnik-rag/.../db/migration/rag/V1__rag_schema.sql) — the app never auto-creates it (create-table=false, deliberately, to avoid an init race).
Changing the embedding model to one with a different dimension is not an env-var flip.
The vector column stays VECTOR(768) until you migrate it yourself, and every existing
embedding becomes incompatible — you must alter the column and re-index all chunks
(re-run the connectors so content is re-embedded). Plan it as a migration, not a restart.
The knobs¶
All model choices are runtime environment variables; set them in the self-host .env (or your
deployment's env) and restart the hub.
| Env var | Default | Property it feeds | What it drives |
|---|---|---|---|
OLLAMA_BASE_URL |
http://localhost:11434 |
quarkus.langchain4j.ollama.base-url + …ollama.analytic.base-url + …ollama.judge.base-url |
Where all three Ollama clients connect |
RAG_CHAT_MODEL |
llama3.2 |
quarkus.langchain4j.ollama.chat-model.model-id |
Conversational RAG chat |
ASK_SQL_MODEL |
qwen2.5-coder:7b |
quarkus.langchain4j.ollama.analytic.chat-model.model-id |
Text-to-SQL ask + view generation |
JUDGE_MODEL |
gemma4 |
quarkus.langchain4j.ollama.judge.chat-model.model-id |
Offline faithfulness eval |
RAG_EMBEDDING_MODEL |
nomic-embed-text |
quarkus.langchain4j.ollama.embedding-model.model-id |
Chunk + query embeddings |
RAG_EMBEDDING_DIM |
768 |
quarkus.langchain4j.pgvector.dimension |
Must match the model and the VECTOR(768) column |
Timeouts are fixed at 120s for all three clients in the module config
(lumnik-llm-langchain/src/main/resources/application.properties); like any Quarkus runtime
property they can be overridden per deployment if a slow model needs more.
Three further properties — lumnik.llm.chat.provider (default ollama),
lumnik.llm.embedding.provider (default ollama-nomic), lumnik.llm.embedding.dimension
(default 768) — are labels, not selectors: they are what the ports report through the
SPI (name() / dimension()), they do not choose a model. If you rewire providers, update them
so the system describes itself truthfully.
Swapping a model on Ollama¶
Three steps, no rebuild:
ollama pull mistral # 1. pull the model on the Ollama host
echo 'RAG_CHAT_MODEL=mistral' >> deploy/selfhost/.env # 2. set the env var
docker compose -f docker-compose.selfhost.yml up -d hub # 3. restart the hub
The same pattern applies to ASK_SQL_MODEL, JUDGE_MODEL and RAG_EMBEDDING_MODEL — with the
dimension caveat above for the embedding model. The three default pulls (and the
host.docker.internal wiring for a containerized hub) are documented once, in the
self-host guide — this page doesn't duplicate them.
Pointing at OpenAI¶
The OpenAI extension is already on the hub's classpath (quarkus-langchain4j-openai in
lumnik-llm-langchain/pom.xml) — the seam is wired, but Ollama is the shipped, exercised
default. The procedure, from the module's own config:
- Switch the provider selectors — these are build-time properties (the langchain4j
build step must disambiguate when both extensions are on the classpath), so this step means
editing
lumnik-llm-langchain/src/main/resources/application.propertiesand rebuilding the hub image (up.sh --build), not flipping an env var:
quarkus.langchain4j.embedding-model.provider=openai
quarkus.langchain4j.analytic.chat-model.provider=openai
quarkus.langchain4j.judge.chat-model.provider=openai
The last two are per-named-model — you may also leave them on ollama and mix providers
(e.g. OpenAI for chat, local qwen for SQL); that is exactly what named models are for.
-
Configure the OpenAI client —
quarkus.langchain4j.openai.api-keyand thequarkus.langchain4j.openai.*-model.model-nameproperties for chat/embedding (andquarkus.langchain4j.openai.analytic.*/…judge.*for the named models you switched). -
Match the embedding dimension — set
RAG_EMBEDDING_DIMto the OpenAI model's dimension (e.g. 1536 fortext-embedding-3-small) and migrate theVECTOR(768)column + re-index, as described above. This is the step that makes an embedding-provider swap a real migration. -
Update the
lumnik.llm.*.providerlabels so the ports report the truth.
Traps¶
Named models do not inherit base-url or timeout
In quarkus-langchain4j, a named model (analytic, judge) inherits nothing from the
default client — neither base-url nor timeout. lumnik's shipped config compensates
explicitly (…ollama.analytic.base-url and …ollama.judge.base-url both follow
OLLAMA_BASE_URL, each with its own timeout=120s). If you ever add another named model,
repeat that wiring — otherwise it silently falls back to localhost:11434 and breaks
wherever the hub runs in a container while Ollama is on the host.
Changing the embedding dimension = migrate + re-index
RAG_EMBEDDING_DIM only tells pgvector what to expect; the rag.chunk column is fixed at
VECTOR(768) by Flyway. A different-dimension embedding model requires altering the column
and re-embedding every chunk (re-run your connectors). There is no in-place swap.
See also¶
- Self-host guide — the three
ollama pulls,OLLAMA_BASE_URLfor a containerized hub,.envhandling. - Ask honesty — the five guards — what wraps the analytic model's output.
- Scopes — the boundary every model answers within.