RAG Implementation
Technical implementation of local document RAG in Clapilot.
RAG (retrieval-augmented generation) lets chat answers cite the user's own documents: instead of hoping the model remembers a file, Clapilot embeds document chunks up front and injects the best-matching chunks into the prompt at question time. Clapilot implements this local-first on PostgreSQL with pgvector (the Postgres extension for vector-similarity search), a queue-driven background indexer, and chat-time retrieval injection. No external vector database is involved.
Components
- Retrieval path:
src/app/api/chat/route.ts+src/lib/rag/retrieve.ts - Background indexer:
scripts/document-rag-indexer.mjs - Runtime bootstrap:
entrypoint.sh - Database schema:
db/migrations/012_rag_pgvector.sql - Admin control endpoints:
GET /api/admin/rag/statusPOST /api/admin/rag/reindex
Data model
The RAG schema is implemented in Postgres:
document_index_jobs- Queue for indexing work (
pending,processing,processed,failed) - Tracks attempts, errors, timestamps
- Queue for indexing work (
document_chunks- Chunk text, metadata, and embedding vector
embedding vector(1536)for OpenAItext-embedding-3-small- HNSW index for vector similarity search
Indexing is enqueued automatically from dokumente changes by DB trigger.
Indexing flow
- A document insert/update in
dokumenteenqueues an index job. document-rag-indexer.mjspollsdocument_index_jobs.- The worker resolves the document file from
dokumente.file_path. - Text is extracted/chunked and embeddings are generated.
- Existing chunks for that document are replaced atomically.
- Job status is updated to
processedorfailed.
Notes:
- Missing files produce
failedjobs withENOENTdetails. - The worker is non-blocking for app startup and runs continuously.
Chat retrieval flow
/api/chat attempts retrieval before sending the prompt to the gateway:
- Build the query embedding through the embedding provider/model selected in
ClapilotAICore -> Provider & Modelle(src/lib/rag/provider-embeddings.ts); if no embedding provider is configured, retrieval is skipped - Resolve the active Mandant scope from the explicit request context or the current
/mandanten/:idroute - Search
document_chunksby cosine distance inside that Mandant scope only - Apply thresholds (
top_k,min_score, per-document cap) - Inject
[Clapilot RAG Context]as system message with source labels ([Q1],[Q2]) - Continue normal streaming response path
If no active Mandant is selected or no relevant chunks match, chat proceeds without document context (RAG remains enabled, but cross-Mandant retrieval stays blocked).
Runtime configuration
Main env toggles:
RAG_ENABLED(chat retrieval injection)RAG_INDEXER_ENABLED(background indexing worker)RAG_INDEXER_POLL_SECONDSRAG_INDEXER_BATCHDOC_VISION_FALLBACK_ENABLED(optional legacy/env override for document vision fallback; default is disabled unless explicitly enabled)DOC_VISION_MODEL(optional legacy/env override for the selected vision model ref)RAG_CHUNK_SIZERAG_CHUNK_OVERLAPRAG_RETRIEVAL_TOP_KRAG_RETRIEVAL_MIN_SCORERAG_RETRIEVAL_MAX_PER_DOC
Current docker defaults are RAG_ENABLED=true and RAG_INDEXER_ENABLED=true.
Embedding provider + model selection now primarily comes from ClapilotAICore -> Provider & Modelle, not from a fixed OpenAI env key. The legacy RAG_EMBEDDING_MODEL env is only used as a final fallback when no native provider selection exists yet.
Document vision fallback is local-first. In ClapilotAICore -> RAG Index, the OCR & Vision block configures the normal RAG vision fallback/model and a dedicated OCR model (receipts/scans). The dedicated app_settings.document_ocr_model is optional: empty inherits rag_document_vision_model; a selected OCR model takes precedence and enables vision fallback for receipt/scan extraction even when the general RAG vision toggle is off. The effective model remains part of the OCR cache configuration hash.
MM-Bridge
files_visualize is the shared multimodal bridge for Chat, Dokumente workflows, and repository-backed Clapilot Code sessions (clapilot-cli files visualize --file-path <path>). It accepts a workspace file path or dokumente_id; runtime-state paths, dotfiles, and dot-directories are rejected after canonical path resolution. Provider/model resolution happens only on the server through the existing OCR/Vision model setting; no provider credential is returned in tool output or placed in the calling model's context.
The bridge is deliberately text-first. Source code, TXT/Markdown, CSV/TSV, JSON/XML/YAML, HTML, SRT/VTT, LaTeX, SVG/DrawIO XML, XLS/XLSX/ODS, DOCX, and Markdown/code cells from IPYNB are extracted locally and do not call a vision model. Images and PDFs use local text/OCR plus the vision sidecar where visual understanding is required. Presentations and legacy Office files are converted with headless LibreOffice before bounded vision analysis. Videos are sampled with ffmpeg.
Default budgets are 40,000 returned characters, at most 2 PDF pages, and at most 6 video frames (one frame per ten seconds). Operators can tune them with MM_BRIDGE_MAX_CHARS, DOC_VISION_MAX_PAGES, and MM_BRIDGE_MAX_FRAMES; all values are hard-capped (120,000 characters, 6 pages, 12 frames). Tool responses include the effective limits. Missing libreoffice, ffmpeg, mammoth, or xlsx support returns a named MM_BRIDGE_DEPENDENCY_MISSING error. A missing sidecar returns MM_BRIDGE_VISION_NOT_CONFIGURED instead of an empty result.
Operations and validation
Check runtime
docker compose exec -T clapilot env | rg '^RAG_|^OPENAI_API_KEY|^CLAPILOT_AGENT_'
docker compose logs -f clapilot | rg -n 'rag-indexer|chat-rag'
Check indexing state
docker compose exec -T postgres psql -U "${POSTGRES_USER:-clapilot}" -d "${POSTGRES_DB:-clapilot}" -c \
"SELECT status, count(*) FROM document_index_jobs GROUP BY status ORDER BY status;
SELECT count(*) AS chunks FROM document_chunks;"
Admin runtime controls
/settings/clapilotaicore/ragshows RAG Index Health- Manual reindex options are available from the ClapilotAICore admin UI
- The same panel exposes the OCR & Vision settings. Both model selects use vision-capable entries from
Provider & Modelle; the receipt/scan OCR select includes an inherit option for the RAG vision model
Failure modes
- Missing embedding provider/model:
- Indexer logs a configuration error and does not process new embeddings until a provider is selected in
ClapilotAICore -> Provider & Modelle
- Indexer logs a configuration error and does not process new embeddings until a provider is selected in
- Missing document file:
- Job marked
failedwithENOENT; fixfile_pathor restore file, then reindex
- Job marked
- No context on a specific question:
- Usually threshold/filter behavior, not a RAG disable state
- Repeated document OCR / repeated image uploads:
- Check for stale
_inbox/.process-now.failed.jsonartifacts and failed queue items; document OCR results are cached by file hash + extraction config, and trigger files are claimed atomically to avoid blind retries across replicas
- Check for stale
