Models — configuring the LLMs
Three separately-chosen chat models plus one embedding model, the exact env var for each, and the one number — the embedding dimension — that turns a model swap into a migration instead of a restart.
lumnik uses one model each for conversational chat, text-to-SQL, offline evaluation, and embeddings — every one of them swappable through an environment variable. This page is for the operator: which model does what, the exact knobs, and how to swap a model on Ollama. The provider internals (SPIs, the exact Quarkus property each variable feeds, switching to OpenAI) live in Advanced provider configuration.
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 —
a general conversational model is not one. It is ~4.7 GB (ollama pull qwen2.5-coder:7b).
Override with ASK_SQL_MODEL.
It runs at temperature 0: at the extension's inherited default (0.8), the same answerable
question abstained (NO_ANSWER) 11 times out of 40 draws; at 0, 0 times out of 30. Temperature 0
buys greedy, low-variance decoding — not a guarantee of byte-identical SQL on every provider and
run, but it is what keeps this path answering the same way twice in practice.
Judge — faithfulness eval (gemma4)
The judge scores whether generated SQL faithfully answers the question — offline/eval only, never in the request path.
| Default model | gemma4 |
| Independent from | the analytic generator — kept distinct so the judge has different blind spots |
| Why gemma4 | of four models probed (gemma2/gemma4/qwen3.6/llama3.2), the lightest that reliably catches the JOIN-type failure mode (INNER where the question's "each X" implies LEFT) without over-flagging — measured, not guessed. Full comparison and verbatim reasoning: Advanced → Why gemma4; study: docs/studies/2026-06-27-judge-join-type-blind-spot.md (PR #77) |
| Production use | optional — pull it only if you run the eval harness |
| Override | 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, and there is currently no supported, tested migration command — this is the shape of what has to happen, not a runbook:
- The existing
VECTOR(768)data cannot be cast to another dimension — there's no shrinking or widening a stored vector, so the old rows must be dropped, not converted. - The
chunk_embedding_idxHNSW index is built on that column and must be dropped beforeALTER TABLE rag.chunk ALTER COLUMN embedding TYPE VECTOR(<n>)can run, then rebuilt after. - Set
RAG_EMBEDDING_MODELandRAG_EMBEDDING_DIMto match, and restart the hub. - Rebuild the corpus:
POST /api/platform/rag/reindex(status and reindex) for the platform corpus, and re-run every connector so ingested-row chunks are re-embedded.
Back up first (./deploy/selfhost/backup.sh) — this touches every stored embedding and no
automated test covers the sequence above. Plan it as a migration, not a restart.
The knobs
All model choices are runtime environment variables. The self-host compose forwards them from
the root .env (commented template lines sit in .env.example), and the Helm chart renders all
six from the llm: values block (deploy/helm/up.sh derives those from LUMNIK_-prefixed
variables). Unset means the application defaults below apply.
| Env var | Default | What it drives |
|---|---|---|
OLLAMA_BASE_URL |
http://localhost:11434 |
Where all three Ollama clients connect |
RAG_CHAT_MODEL |
llama3.2 |
Conversational RAG chat |
ASK_SQL_MODEL |
qwen2.5-coder:7b |
Text-to-SQL ask + view generation |
JUDGE_MODEL |
gemma4 |
Offline faithfulness eval |
RAG_EMBEDDING_MODEL |
nomic-embed-text |
Chunk + query embeddings |
RAG_EMBEDDING_DIM |
768 |
Must match the model and the VECTOR(768) column — see above |
The self-host compose's own default for OLLAMA_BASE_URL is http://host.docker.internal:11434,
not localhost — the containerized hub reaches out to the host machine unless you set it; see
Point the hub at an Ollama that is not on this machine.
Timeouts default to 120s for all three Ollama clients; the analytic and judge temperatures are
fixed at 0, the conversational chat model keeps the provider's default. Verifying an
embedding-model swap: GET /api/platform/rag/status
reads the live provider/model from the active port, so it reflects a swap immediately —
unlike chat or analytic, which you verify by checking the container's environment (below). The
exact Quarkus property each variable feeds, and how to override a timeout, live in
Advanced provider configuration → Property mapping.
Swapping a model on Ollama
Three steps, no rebuild:
- Pull the model on the Ollama host:
ollama pull mistral. - Add or update
RAG_CHAT_MODEL=mistralin the root.env— anecho >>risks a duplicate definition if the key is already there. - Recreate the hub:
docker compose -f docker-compose.selfhost.yml up -d hub.
Verify the swap took — a wrong variable name fails silently (the old model keeps answering):
docker compose -f docker-compose.selfhost.yml exec hub env | grep RAG_CHAT_MODEL
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 are documented once, in
Prerequisites → The AI layer — this page
doesn't duplicate them.
Pointing at OpenAI
The OpenAI extension is already on the hub's classpath — the seam is wired, but Ollama is the
shipped, exercised default. Switching means editing the module's config and rebuilding the hub
image (up.sh --build), not flipping an env var, and it makes an embedding-provider swap a real
migration (see above). Full procedure — including the one selector the shipped config leaves
implicit for the plain conversational chat model — lives in
Advanced provider configuration → Pointing at OpenAI.
Traps
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
- Prerequisites — the AI layer — the three
ollama pulls. - Point the hub at a remote Ollama —
OLLAMA_BASE_URL,.envhandling, the open-edition exception. - Ask honesty — the five guards — what wraps the analytic model's output.
- Scopes — the boundary every model answers within.
- Advanced provider configuration — SPIs, full Quarkus property mapping, OpenAI end-to-end, named-model traps.