Knowledge Base / RAG
Index documents for semantic search and grounded Q&A.
What it is
The Knowledge Base is TurfAI's RAG layer: enable indexing on a document and it becomes searchable by meaning, not just keywords. Natural-language queries return top-K passages with similarity scores and an AI-synthesized answer carrying source citations (document title, snippet, score, and a signed URL to the source). For the concept, see RAG; this page covers the module.
When to use it
Use it whenever AI needs to answer from your documents — policy Q&A, support knowledge
bases, contract lookups — or when an agent needs a search_documents
tool. It also powers the Chatbot.
How it works
Process flow — index then query:
Call flow — a RAG query:
Sub-features
- Document indexing — async with status (
queued→processing→completed, orfailed),force_reprocess, chunk count (rag_chunk_count), embedding-model metadata. - Semantic search — top-K with similarity scoring, optional reranking, configurable similarity threshold.
- Conversation sessions — persistent multi-turn chat with session ids, history, deletion, titles.
- Collections — group documents into named collections and scope queries to them.
- Two backends — Google File Search (default) and pgvector (legacy), selected per deployment.
Configuration parameters
RAG config has two layers: deployment/indexing config (set by ops via env vars, fixed for
all queries) and per-query config (passed on each /api/rag/query call).
Indexing & backend (deployment-level)
| Parameter | Env / source | Default | Notes |
|---|---|---|---|
| Backend | RAG_BACKEND | google_file_search | google_file_search (managed, Gemini, ~90% cheaper) or pgvector (PostgreSQL, on-prem control) |
| Embedding model | EMBEDDING_CONFIG.model | text-embedding-004 (Vertex, 768-dim) | pgvector path; OpenAI text-embedding-3-small (1536-dim) is the fallback |
| Generation model | GOOGLE_FILE_SEARCH_MODEL / LLM_CONFIG.model | gemini-2.5-flash | LLM that synthesizes the answer |
| Chunk size | GOOGLE_FILE_SEARCH_CHUNK_SIZE / CHUNK_CONFIG | 1000 tokens | |
| Chunk overlap | GOOGLE_FILE_SEARCH_CHUNK_OVERLAP / CHUNK_CONFIG | 200 tokens (20%) | |
| Store scope | STORE_SCOPE | user | user (one store per user) or enterprise (shared) — File Search only |
| Fallback | RAG_FALLBACK_ENABLED / RAG_FALLBACK_BACKEND | true / pgvector | Old docs stay queryable after a backend switch |
Per-query parameters (POST /api/rag/query)
| Parameter | Type | Default | Range | Notes |
|---|---|---|---|---|
query | string | — (required) | min length 1 | The natural-language question |
top_k | int | 5 | 1–20 (max_top_k=20) | Passages to retrieve |
similarity_threshold | float | 0.3 (DMS) | 0.0–1.0 | Minimum score to include a passage — see note below |
use_reranking | bool | false | — | Re-rank retrieved chunks by relevance |
llm_model | string | unset → gemini-2.5-flash | — | Override the synthesis model per query |
session_id | string | unset → auto-created | — | Carry multi-turn conversation context |
filters | object | none | — | collection_ids, document_ids, tenant_id |
Threshold default mismatch (reconciled against source): the DMS query endpoint defaults
similarity_threshold to 0.3 (controllers/rag.js), while the RAG query service's own model
defaults to 0.5 (rag_query_service/models.py). Because every call flows through DMS, the
effective default is 0.3. Both were deliberately lowered (from 0.5 / 0.7) for multi-lingual
documents. Always pass an explicit value if recall/precision matters to you.
Tips & best practices
- Use a collection when you have a distinct knowledge domain (one product, one customer, one policy set). Scope queries with
filters.collection_idsso unrelated documents can't leak into answers. - Document hygiene: prefer machine-readable PDFs, DOCX, TXT, or Markdown; keep one topic per document; re-run with
force_reprocess=trueafter meaningful edits so the chunk index matches the live file. - Choosing a backend: start with Google File Search (API key only, embeddings handled by Gemini, ~90% cheaper, auto-scaling). Move to pgvector only when you need on-prem/full control of the vector store.
- Tuning
similarity_threshold: raise it (e.g. 0.5–0.6) when answers pull in loosely related passages; lower it (e.g. 0.2–0.3) when relevant documents are being missed — especially for multi-lingual content. Turn onuse_rerankingbefore raising the threshold; reranking usually fixes "right docs, wrong order" without dropping recall. - Citations: every answer ships
sources[]withdocument_title,chunk_text,similarity_score,page_number, and a 1-hoursigned_url. Surface these in the UI so users can verify the answer; ifsourcesis empty, treat the answer as ungrounded.
Concrete examples
1. Enable RAG, then poll status
# Enable indexing on document 42
POST /api/documents/42/enable-rag
{ "force_reprocess": false }
# → { "status": "queued", "job_id": "rag-embed-42-1718800000000", "document_id": "42" }
# Poll until completed
GET /api/documents/42/rag-status
# → { "processing_status": "queued", "chunk_count": 0 } # then...
# → { "processing_status": "processing", "chunk_count": 0 } # then...
# → { "processing_status": "completed", "chunk_count": 37 }
# status values: not_started | queued | processing | completed | failed2. Run a RAG query
POST /api/rag/query
{
"query": "What is the notice period for terminating the MSA?",
"top_k": 5,
"similarity_threshold": 0.3,
"use_reranking": true,
"filters": { "collection_ids": [7] }
}Response (shape from QueryResponse / Source):
{
"answer": "The MSA requires 60 days' written notice to terminate for convenience [1].",
"sources": [
{
"document_id": 42,
"document_title": "Master Services Agreement.pdf",
"chunk_text": "...either party may terminate this Agreement for convenience upon sixty (60) days' prior written notice...",
"chunk_index": 12,
"similarity_score": 0.71,
"page_number": 8,
"file_url": "gs://turfai-docs/42/msa.pdf",
"signed_url": "https://storage.googleapis.com/turfai-docs/42/msa.pdf?X-Goog-Expires=3600..."
}
],
"confidence": 0.82,
"session_id": "sess_a1b2c3",
"processing_time_ms": 940
}3. Multi-turn session — reuse the returned session_id on the next call to keep context:
POST /api/rag/query
{ "query": "And what about termination for cause?", "session_id": "sess_a1b2c3" }The service resolves "what about..." against the prior turn. Omit session_id and a new
session is auto-created; list/inspect/delete them via /api/rag/sessions.
Limitations
- Standalone images (JPEG/PNG) may not be supported by Google File Search — convert to PDF first.
- Google File Search auto-OCR of scanned PDFs is not explicitly documented by Google. Gemini's native vision usually reads them, but test before relying on it; in-boundary OCR for scanned PDFs is coming soon (today's fallback is TurfAI OCR → pgvector).
- Per-document file limits (File Search): up to 100 MB / 1000 pages; pages scaled to 3072×3072.
- Data Shield does not yet tokenise the RAG chat path (v0.5) — do not send untokenised PII through RAG chat expecting masking.
- Persistent / cross-call session scope beyond the current store is coming soon.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Query returns no results (empty sources) | Threshold too high, or document never finished indexing | Confirm rag-status is completed with chunk_count > 0; lower similarity_threshold (try 0.2–0.3); widen top_k |
| Irrelevant results | Right docs retrieved in wrong order, or threshold too loose | Set use_reranking: true; then raise similarity_threshold slightly; scope with filters.collection_ids |
Document stuck in processing | Worker overloaded or job timed out (job_timeout 300s) | Wait, then re-enable-rag with force_reprocess: true; check the embeddings worker is running |
Status failed | Unsupported file, fetch error, or embedding-service error | Read rag_error; verify MIME type (PDF/DOCX/TXT/MD); for pgvector confirm TurfAI LLM service health (/health on 9090) |
| Scanned PDF returns nothing | File Search auto-OCR is not guaranteed | Test the scanned PDF first; if it fails, run TurfAI OCR then index, or fall back to pgvector (in-boundary OCR is coming soon) |
409 RAG already enabled | Document already completed | Re-index with force_reprocess: true |
How to test
End-to-end happy path:
- Upload a machine-readable PDF (DOCX/TXT/MD also fine).
- Enable RAG:
POST /api/documents/:id/enable-rag; expectstatus: queued. - Wait for
completed: pollGET /api/documents/:id/rag-statusuntilprocessing_statusiscompletedandchunk_count > 0. If it lands onfailed, readrag_error. - Query:
POST /api/rag/querywith a question whose answer is in the document. - Confirm a cited answer: the response has a non-empty
answerandsources[]with the rightdocument_title, apage_number, asimilarity_scoreabove your threshold, and a workingsigned_url. - Multi-turn: send a follow-up question with the returned
session_idand confirm the answer respects the earlier turn's context. - Collection scope (optional): add the document to a collection and confirm
filters.collection_idsreturns it while excluding others.
Cross-links & roadmap
- Chatbot — public/embedded chat surface built on this layer.
- RAG concept — chunking, embeddings, and retrieval explained.
- Data Shield — note: RAG chat is not tokenised by Data Shield in v0.5.
- Coming soon: in-boundary OCR for scanned PDFs; persistent (cross-call) session scope.
Dependencies
- RAG query service (Python) — embeddings, vector search, conversation memory, citations.
- RAG embeddings worker — chunks + embeds documents off a Redis queue.
- Vector store — Google File Search or PostgreSQL + pgvector.
- LLM / embedding service — Gemini via Vertex AI (File Search) or TurfAI LLM service (pgvector).
- Document management + GCS — source documents and 1-hour signed URLs.
APIs used
| Method | Path | Purpose |
|---|---|---|
POST | /api/documents/:id/enable-rag | Queue a document for indexing (force_reprocess optional) |
GET | /api/documents/:id/rag-status | Poll processing_status + chunk_count |
POST | /api/documents/:id/disable-rag | Remove a document from the index |
POST | /api/rag/query | Authenticated RAG query (returns answer + sources) |
POST GET DELETE | /api/rag/sessions[/:id] | Create / list / inspect / delete conversation sessions |
| (node) | RAG Enable / RAG Query task | Index / query inside a workflow |
GET | /api/documents?rag_enabled=true | List indexed sources (KB Assembly) |
See the synced feature reference for the full RAG surface.