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 you can search it 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, chunk_text,
similarity_score, and a signed_url to the source).
For the underlying idea, see the RAG concept. 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 on the document's
rag_processing_status(not_started→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 (ops sets it through env vars, and it
applies to all queries) and per-query config (you pass it 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 defaults differ between services (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.
To avoid depending on which default applies, always pass an explicit value when 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 the search misses relevant documents — especially for multi-lingual content. Turn onuse_rerankingbefore raising the threshold; reranking usually fixes "right docs, wrong order" without dropping recall. - Citations: every answer includes
sources[]withdocument_title,chunk_text,similarity_score,page_number, and a 1-hoursigned_url. Show 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 | failed
# (the endpoint returns the document's rag_processing_status as processing_status)2. 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. If you omit session_id, the
service creates a new session automatically. List, inspect, or delete sessions through
/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 (coming in v3.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
Run this end-to-end happy path:
- Upload a machine-readable PDF (DOCX/TXT/MD also work).
- 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 until v3.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.