Model providers
The pluggable embeddings and wiki backends: auto, built-in CPU, Ollama, OpenAI, Anthropic, and agent-CLI, with data-sharing posture and setup.
Both the embeddings and wiki tiers are pluggable and take a provider, defaulting to "auto". Pick
by what may leave your machine and what hardware you have.
flowchart LR
E["kb embed"] --> P{"which provider?"}
W["kb wiki"] --> P
P -->|"builtin"| BI["a small model,
in-process on CPU"]
P -->|"ollama"| OL["a local Ollama daemon"]
P -->|"openai, plus anthropic
for the wiki"| API["a hosted API, key
read from an env var"]
P -->|"cli, for the wiki"| C["an agent CLI you
already pay for"]
BI --> IN(["nothing leaves your machine"])
OL --> IN
API --> OUT(["your prompts leave your machine"])
C --> OUT
auto, the default, is the one value that picks for you: it resolves to a local Ollama that already has
the model pulled, and otherwise to the built-in CPU model.
auto(default), resolves to a reachable local Ollama that already has the target model pulled, else the built-in CPU model if its extra is installed, else it skips that tier. Reachability alone isn't enough to pick Ollama, a daemon running for other models (e.g. just a chat model, with no embedding model ever pulled) falls straight through to the built-in tier instead of failing on first real use. So the semantic/wiki tiers Just Work the moment you setenabled = true, with no daemon and no API key.builtin, a small model that runs in-process on CPU, auto-downloaded once tocache_dir(default~/.contextlake/models). Zero daemon, zero API key.- Embeddings,
engine = "model2vec"(default): staticpotion-base-8M(~30MB, MIT), numpy inference, very fast at scale,pip install "contextlake[kb-local]". Orengine = "fastembed": ONNXbge-small(~90MB, MIT, higher quality),pip install "contextlake[kb-fastembed]". - Wiki LLM, a
Qwen2.5-Coder-0.5B-Instructint4 OpenVINO IR model (Apache-2.0, 349 MB) viaopenvino-genai,contextlake doctor --fix llm-local(an ordinary wheel: no compiler, no custom index). Fast to set up, but 0.5B is a modest writer (good for coverage, not polished prose) and CPU generation is slow (~4 calls/repo). Prefer Ollama at any real scale. See Installing the built-in LLM and How much does the model matter?. ollama, a local Ollama daemon (base_url, defaulthttp://127.0.0.1:11434), the recommended wiki backend: a 3B-8B model writes markedly better pages than the built-in 0.5B. See Using Ollama for the wiki.openai, any OpenAI-compatible chat API (a hosted key, or a local server like LM Studio, Jan, llama.cpp, vLLM). Best prose, per-token cost. The key is read from the env var named byapi_key_env(defaultOPENAI_API_KEY), never stored in config.anthropic, the Anthropic Messages API (a hosted key). Best-in-class wiki prose and reliable structured council reviews. The key is read from the env var named byapi_key_env(defaultANTHROPIC_API_KEY), never stored in config.modelselects the tier: defaultclaude-opus-4-8; setmodel = "claude-haiku-4-5"or"claude-sonnet-5"for a much cheaper high-volume fleet run (the council makes many calls).max_tokens(default 4096) caps each response.cli, a locally-installed agent CLI you already pay for:claude,gemini, orcodex. contextlake shells out to it (command, defaultclaude;argsoverrides the per-CLI preset) and feeds the prompt on stdin. No API key touches contextlake; data goes to whatever provider that CLI uses. Reuses your subscription, offline-adjacent (still a network call by that tool), and mirrors how contextlake already shells out togitandglab. For the three recognised commands, contextlake strips that CLI's own API-key env var(s) (ANTHROPIC_API_KEYforclaude,OPENAI_API_KEYforcodex,GEMINI_API_KEY/GOOGLE_API_KEYforgemini) from the child process only -- otherwise a key set anywhere in your shell for an unrelated reason (e.g. testing theanthropicprovider) can silently override the CLI's subscription login and bill a pay-per-token account you never meant to use here. Confirmed by live repro forclaudeand by its own docs forgemini;codex's docs describe API-key auth as a separate, explicitly-opted-into mode rather than an environment-variable override, so the strip is a defensive precaution there rather than a confirmed-necessary fix.provider = "cli",command, andargsare read only from~/.contextlake/kb.tomlor a--configpath, never from a.contextlake.kb.tomlfound by walking up from your current directory, since that file may have arrived inside a repository you cloned and these keys are a command line contextlake runs. Setting them locally logs a warning and is ignored; every other provider works from a local file as usual. See Workspace trust.
Data-sharing posture per backend. Pick by what may leave your machine:
| Backend | Data leaves the machine? | Auth |
|---|---|---|
builtin / auto→builtin |
No: fully local CPU model | none |
ollama |
No: local daemon | none |
cli |
Yes: to whatever provider that CLI uses | reuses the CLI's own login |
anthropic / openai |
Yes: to the API endpoint | env-var key (never stored) |
Configuring the wiki LLM#
Two lines is enough (passing --llm on the CLI implies enabled = true), or set both in
~/.contextlake/kb.toml:
[llm]
enabled = true
provider = "ollama" # auto | builtin | ollama | openai | anthropic | cli
model = "qwen2.5:3b" # provider-specific model id (table below)
# base_url = "http://127.0.0.1:11434" # optional: defaults per provider (table below)
# api_key_env = "OPENAI_API_KEY" # openai: env var holding the key (never the key)
# timeout = 300 # seconds per model call; raise it for a slow CPU (ollama/openai)
council_size = 3 # review lenses that run (1-3); fewer = fewer calls per page
accept_score = 0.7 # mean council score a page must clear to be written
# review_provider = "anthropic" # optional: have the COUNCIL review with a different
# review_model = "claude-haiku-4-5" # (usually stronger) backend than the one generating
Unset, review_provider means the council reviews with the same client that generated the page,
which is the historical behavior. Setting it lets a cheap local generator be gated by a real model; it costs
pages × council_size extra calls against that provider, so it is opt-in and never inferred from an
API key that happens to be in your environment. See
Generate the wiki for the full rationale.
| provider | example model |
notes |
|---|---|---|
builtin |
OpenVINO/Qwen2.5-Coder-0.5B-Instruct-int4-ov |
a HF repo id holding a pre-converted OpenVINO IR model |
ollama |
qwen2.5:3b, llama3.1, llama3.2:3b |
must be ollama pulled first |
openai |
gpt-4o-mini, or your server's model id |
base_url defaults to https://api.openai.com/v1; point it at a local openai-compatible server to override |
anthropic |
claude-haiku-4-5, claude-opus-4-8 |
base_url defaults to https://api.anthropic.com; key from ANTHROPIC_API_KEY |
cli |
n/a, the CLI picks its own model | command = the agent CLI to invoke (default claude); reuses that CLI's login |
base_url is resolved per provider, so you only set it to reach a proxy or a local
openai-compatible server, never just to make anthropic/openai work.
CLI flags override the toml and now work on bootstrap too:
contextlake bootstrap --llm ollama --llm-model qwen2.5:3b.
Installing the built-in LLM#
contextlake doctor --fix llm-local # or: pip install "contextlake[llm-local]"
An ordinary wheel install. No compiler, no custom index, no --only-binary pin.
Install and upgrade covers each channel,
including why the standalone binary and the full Docker image need nothing at all.
What it runs, and what it costs you#
The builtin wiki model is a small int4 OpenVINO IR model driven by
openvino-genai. The default is
OpenVINO/Qwen2.5-Coder-0.5B-Instruct-int4-ov: Apache-2.0, published pre-converted by the
OpenVINO project, 349 MB, downloaded on first wiki run into ~/.contextlake/models.
The runtime pulls openvino and openvino-tokenizers and nothing else -- in particular
neither torch nor transformers, which is why a local generative tier does not cost you a
multi-gigabyte install. "Pre-converted" is the load-bearing part: converting a stock
checkpoint to OpenVINO IR yourself needs optimum-intel, which does pull torch.
Why not llama.cpp#
Earlier releases ran a GGUF through llama-cpp-python, and it needed a per-accelerator wheel
index because upstream publishes no wheels to PyPI at all -- so a plain pip install compiled
C++ from source and wanted cmake and a compiler. It also had no wheel for CPython 3.14 on
any x86_64 platform, which pinned the whole project's container base image to an older
Python.
openvino-genai ships ordinary manylinux wheels for CPython 3.10 through 3.14, so the index,
the --only-binary pin and the compiler requirement are all simply gone. If you want GPU
inference, use Ollama (below) rather than a different index.
Using Ollama for the wiki#
Ollama is a standalone local model server. It sidesteps the native Python build, and a 3B-8B model writes much better wiki pages than the 0.5B built-in.
A) Ollama inside WSL / Linux (simplest, localhost just works):
curl -fsSL https://ollama.com/install.sh | sh # installs + starts the daemon
ollama pull qwen2.5:3b # ~1.9GB, one-time
contextlake bootstrap --llm ollama --llm-model qwen2.5:3b # whole layer in one command
# or per repo: contextlake kb wiki <repo> --llm ollama --llm-model qwen2.5:3b
contextlake defaults base_url to http://127.0.0.1:11434, so usually nothing else to set.
B) Ollama on Windows, contextlake in WSL (the cross-boundary case). WSL2 is a separate network
namespace, so a Windows Ollama bound to 127.0.0.1 is not reachable from WSL (localhost:11434 →
connection refused). Two fixes:
- Easiest, mirrored networking. In
%UserProfile%\.wslconfigadd:then[wsl2] networkingMode=mirroredwsl --shutdownand reopen. Nowlocalhostis shared, so the defaultbase_url = "http://127.0.0.1:11434"works from WSL unchanged. - Or expose Ollama and use the host IP. On Windows set
OLLAMA_HOST=0.0.0.0(System Environment Variables) and restart Ollama so it listens on all interfaces; allow it through the Windows firewall. From WSL, the Windows host is your default-route gateway, not thenameserverin/etc/resolv.conf(that is a DNS stub):Setip route show default | awk '{print $3}' # e.g. 172.24.224.1 (NOT 10.255.255.254) curl http://172.24.224.1:11434/api/tags # confirm reachabilitybase_url = "http://172.24.224.1:11434"in[llm](your IP will differ).
Pull the model on whichever side runs Ollama: ollama pull qwen2.5:3b.
How much does the model matter?#
The wiki's quality is bounded by the model behind it. The graph facts fed in are identical; the difference is how well the model turns them into prose (and the verification council rejects weak pages regardless, so a smaller model mostly means more rejections and blander accepted pages).
The constraint that decides this for most people is hardware, not model quality. Generating one wiki
page is not one model call: it is the page, plus one review from each council lens. A rejected page is
reported and skipped, not rewritten, so a rejection costs you the page rather than another round of calls.
So the question is not whether a 1.5B-3B model writes better prose than the built-in
0.5B (it does) but whether your host can finish that many calls: if a single call takes minutes, which is
the ordinary case for a 1.5B-3B model on a CPU with no GPU, the run reaches the default 300-second
timeout below before a page is done. The built-in 0.5B stays under it, and pays for that with thin,
generic prose the council often rejects. The same 1.5B-3B models answer in seconds once Ollama has a
GPU (including Ollama on a Windows host with a discrete GPU, reached from WSL). So:
- CPU-only, offline, quick: the built-in 0.5B, fast to set up, basic prose. Or a hosted API if quality matters and you accept per-token cost.
- GPU available (incl. Ollama on your Windows host): Ollama 3B-8B, the sweet spot for readable pages at fleet scale.
- Best prose regardless of local hardware: an API model (
openaiprovider).
If your local model is slow, raise the per-call timeout instead of letting every page fail silently:
timeout in [llm] (seconds, default 300):
[llm]
provider = "ollama"
model = "qwen2.5:3b"
timeout = 1200 # give a slow CPU room; default is 300s (5 min)
If the endpoint stops answering rather than being slow, raising timeout makes things worse: a wiki run
calls the model once per page. After three consecutive endpoint failures (a timeout, a refused
connection, a 5xx) the provider is skipped for 60 seconds and then probed once, so a dead Ollama daemon
or an unreachable API costs the run a few timeouts instead of one per page, and says so in the log. A
rejected request is exempt and always reported as itself, so model "..." not found, try pulling it
first keeps telling you to run ollama pull however many pages are left.
Notes: behind a TLS-inspecting corporate proxy the first built-in download needs your OS CA bundle
(export REQUESTS_CA_BUNDLE / SSL_CERT_FILE; see docs/releasing.md). Don't switch the embedder
model/dimension against an existing vector store without re-embedding from scratch, a guard refuses the
mismatch. The prebuilt Docker image (ghcr.io/sayak-sarkar/contextlake) bundles these models so nothing
downloads at runtime. See examples/kb.toml.example.
