RAG pipeline¶
About this page
This documents a stack generated from soliplex-template. A generated
project ships its own copy of this page without this note.
rag/db/ is the single source of truth for vector data. It is mounted by
two services:
- haiku-ingester is the writer (
/data); its job queue is a Postgres database (soliplex_ingester), not a file underrag/db/. - backend reads it via the
haiku.rag.skills.ragskill.
Only one ingester may write a given LanceDB (the single-writer constraint).
Adding documents¶
Drop files into rag/docs/. The filesystem source in
haiku.rag/haiku.rag.yaml picks them up on its next poll, and docling-serve
converts and chunks them.
To ingest only part of what lands there, set ignore_patterns /
include_patterns (gitignore-style) on that source. Note that a non-empty
include_patterns is exclusive — anything it does not match is skipped.
Adding other sources¶
To pull from S3, HTTP, WebDAV, and the like, append entries under
the ingester.databases entry keyed haiku.rag, in
haiku.rag/haiku.rag.yaml, and
restart the ingester:
A separate, static database¶
For a separate, mostly-static corpus, build a standalone database with the
haiku-rag CLI through the
haiku-rag service — the stack's one-shot CLI runner, which shares the
ingester's image, its rag/db and rag/docs mounts, its OLLAMA_BASE_URL and
its haiku.rag.yaml. Point it at a different --db so it never collides
with haiku.rag.lancedb:
docker compose run --rm --no-TTY haiku-rag init --db /data/handbook.lancedb
docker compose run --rm --no-TTY haiku-rag add-src /docs/handbook/ \
--db /data/handbook.lancedb
Everything after the service name goes to haiku-rag. Do not run the CLI on
the haiku-ingester service instead: docker compose run replaces that
service's command, which is the only thing that exports the queue password
haiku.rag.yaml interpolates, and the config then fails to load before the
subcommand runs.
add-src also takes a URL or s3:// URI; for a local path outside
rag/docs/, add -v /abs/path:/src:ro and point add-src at /src. Use
rebuild to re-index and delete <id> to remove a document. The database
lands at rag/db/handbook.lancedb, which the backend already reads through its
rag/db mount. Wire a room to it with rag_lancedb_stem: "handbook" in that
room's room_config.yaml.
Ingesting multiple databases¶
One ingester can maintain several databases with the same queue and control
plane. In haiku.rag/haiku.rag.yaml, list their locations under
lancedb.databases and their sources under ingester.databases. Keep the
default database in both maps when adding another:
lancedb:
databases:
haiku.rag: /data/haiku.rag.lancedb
handbook: /data/handbook.lancedb
ingester:
# Keep the existing queue and api sections here.
databases:
haiku.rag:
sources:
- type: fs
id: local-docs
root: /docs
ignore_patterns: ["handbook/**"]
delete_orphans: true
handbook:
sources:
- type: fs
id: handbook-docs
root: /docs/handbook
delete_orphans: true
Source ids must be unique across databases. Each database must have only
one writer. Point a room at handbook with rag_lancedb_stem: "handbook".
Moving a source does not remove documents already stored in another database.
Apply database and source changes without restarting the other databases:
POST /reload is the HTTP equivalent. GET /databases/{name} reports a
database's details. Changes outside lancedb.databases and
ingester.databases, such as embeddings or queue settings, require a restart.
Migrating a store¶
Most haiku.rag-slim bumps need nothing, but a release that changed the store
layout leaves every LanceDB written before it needing a one-off upgrade before
haiku.rag will open it. See Database migrations for how to
ask which case a bump is.
Changing the embedding model¶
The embedding model is set in two files, and they must agree:
haiku.rag/haiku.rag.yaml configures the ingester (and the one-shot
haiku-rag service), and backend/environment/haiku.rag.yaml configures the
backend. Both open the same LanceDB, and a store records the vector dimension
it was written with, so it refuses to open under a different one:
Database configuration is incompatible with current settings:
- Stored (db) embedding vector dimension 2560 -> Environment
(current) embedding vector dimension 1024
When that check fails, the ingester crash-loops. Stacks generated before
soliplex-template#218 was fixed are affected even if the model was never
changed. In those stacks the ingester's file has no embeddings: section, so
it fell back to haiku.rag's default (qwen3-embedding:4b, 2560 dimensions),
whatever rag_embed_model said.
To switch models, or to repair one of those stacks:
-
Set the same
embeddings:block in both files: -
Stop the writer:
docker compose stop haiku-ingester. -
Bring the store in line with the new model. Check whether it holds any documents (
inforeports the row count of each table):An empty store can just be moved aside, and the ingester creates a fresh one on start:
mv rag/db/haiku.rag.lancedb rag/db/haiku.rag.lancedb.old.A store holding documents needs its existing chunks re-embedded with the new model. This drops and recreates the chunks table at the new dimension, and does not re-convert the source documents:
Do the same for every other store under
rag/db/that a room reads: one query is embedded once, with one model. -
Restart:
docker compose start haiku-ingester, thendocker compose restart backend. - Jobs that failed under the old configuration, for example with a 404 for a
model the Ollama server doesn't have, sit in the dead-letter queue. List
them with
GET /dlq, then retry each one withPOST /dlq/{id}/retry, or let the next source sweep queue them again. Both calls need the bearer token; see Ingester control plane.
Searching several databases at once¶
A room's RAG skill or search_documents tool can name more than one database
under rag_databases, and a search then covers them as one set:
Each entry carries exactly one of rag_lancedb_stem or
rag_lancedb_override_path, plus an optional name — the identity reported in
search results, documents and citations, defaulting to the stem that placed it.
Names must be unique, and every database in the list must have been written
with the same embedding model, since one query is embedded once for all of
them. A bare rag_lancedb_stem on the skill is shorthand for a one-entry list.
Operating the pipeline¶
The ingester exposes a control plane on host port 8765 — /health,
/jobs, /sources, /dlq, /stats, and a dashboard at /. It requires a
bearer token; see Ingester control plane.