Local AI
Campbooks asks for an AI role, never for a vendor. You choose what serves each role. The shipped defaults use open-weight models only, so every model can run on your own hardware.
The roles
| Role | What it is for |
|---|---|
decide | Typed decisions about an item: which option, what score, is this true. Each answer comes with a probability. |
complete | Longer generation, such as drafts and summaries. |
embed | Search vectors. |
vision | Optional. Images and scanned documents. |
A role left unset is simply unavailable. Features that need it do not run.
Start the local services
The ai profile adds three services, one per role it serves.
docker compose --profile ai up -d
| Service | Role | Model | Notes |
|---|---|---|---|
laya | decide | convaiinnovations/laya-multilingual (Apache-2.0, 322M) | Built from deploy/laya. About 680 MB download on first start. About 2 GB of RAM. |
ollama | complete (and vision) | qwen3:8b | Pull models with docker compose exec ollama ollama pull … |
embeddings | embed | BAAI/bge-m3 | linux/amd64 only. On arm64 use Ollama's bge-m3. |
1. Set the decide API key
The laya service refuses to start without a key, because laya-serve answers anyone when it has none. The server sends the same value. The service has no published port, so only containers on the compose network reach it.
openssl rand -hex 32
Put the result in .env:
CAMPBOOKS_AI__DECIDE__API_KEY=<the value you generated>
2. Pull the complete model once
Laya and the embeddings server download their models themselves. Ollama needs one pull.
docker compose exec ollama ollama pull qwen3:8b
CPU or GPU
decide(Laya) andembedrun fine on CPU. Laya takes a few hundred milliseconds per item on a recent laptop.completeworks on CPU but is slow. Use a GPU host, or a hosted provider that serves open-weight models, and set its URL in.env.- To use an NVIDIA GPU with Ollama, uncomment the
deployblock under theollamaservice incompose.yamland install the NVIDIA Container Toolkit. - Laya uses every CPU core by default. Set
LAYA_THREADSon thelayaservice to cap it.
Use another endpoint
Every role is configured by three settings: CAMPBOOKS_AI__<ROLE>__BASE_URL, CAMPBOOKS_AI__<ROLE>__MODEL and CAMPBOOKS_AI__<ROLE>__API_KEY. Point a role at any OpenAI-compatible endpoint (Ollama, vLLM, a hosted provider serving open-weight models):
CAMPBOOKS_AI__COMPLETE__BASE_URL=https://llm.example.com/v1
CAMPBOOKS_AI__COMPLETE__MODEL=your-model
CAMPBOOKS_AI__COMPLETE__API_KEY=...
For decide, set CAMPBOOKS_AI__DECIDE__PROTOCOL=openai to use an instruct model instead of Laya. It answers the same questions with JSON-schema output. Probabilities come from token log-probabilities when the server returns them. Without them, answers carry no confidence, so nothing that needs one acts on its own. Set CAMPBOOKS_AI__DECIDE__LOGPROBS=false for servers that reject the logprobs parameter.
CAMPBOOKS_AI__DECIDE__PROTOCOL=openai
CAMPBOOKS_AI__DECIDE__BASE_URL=http://ollama:11434/v1
CAMPBOOKS_AI__DECIDE__MODEL=qwen3:8b
On arm64, drop the embeddings service and point embed at Ollama:
CAMPBOOKS_AI__EMBED__BASE_URL=http://ollama:11434/v1
CAMPBOOKS_AI__EMBED__MODEL=bge-m3
About Laya
- There is no official image, so compose builds one from
deploy/laya/Dockerfile. After an upgrade, rebuild it withdocker compose --profile ai build laya. - On first start it downloads the multilingual checkpoint into the
laya-modelsvolume. Later starts load it from there. CAMPBOOKS_AI__DECIDE__MODELalso acceptsconvaiinnovations/laya(English only, 512 tokens) andconvaiinnovations/laya-typed-decisions. The image preloads only the multilingual one, so setLAYA_MODELSon the service too.- Out of the box the models are over-confident. The
CAMPBOOKS_AI__DECIDE__CALIBRATION__*settings soften the probabilities. They default to 1.0.
Open-weight models only
Only open-weight models appear in the shipped catalog. They can be self-hosted (vLLM, llama.cpp, Ollama, Text Embeddings Inference) or used through any hosted endpoint that serves open weights. All roles speak the OpenAI-compatible HTTP API, except Laya's own typed protocol for decide.
If you change the embedding model, stored vectors are tied to the old model. Embeddings record their model id and dimensions, and a re-embed job is part of the design.