Skip to content

Knowledge Bases & RAG

A knowledge base collection is a set of documents that Brutor chunks, embeds and stores in a per-tenant vector store (Qdrant, collection brutor_kb_{tenant_id}). Once a collection is bound to a resource group, every LLM call made by that group’s users is automatically grounded: the gateway retrieves the most relevant chunks and injects them as context before the request reaches the provider.

The developer-relevant consequence up front:

Admin API base http://localhost:5050/v1/admin/knowledge-base (note the hyphen)
Portal API base http://localhost:8100/v1/portal/knowledgebases (plural, one word)
Auth Admin JWT (admin API) · portal JWT (portal API)

When an LLM call arrives at the proxy for a group with KB bindings:

  1. The last user message becomes the retrieval query.
  2. The gateway runs hybrid search — dense vectors plus sparse BM25, fused with Reciprocal Rank Fusion — over the group’s collections, applying the collection’s similarity_threshold and top_k. (An optional cross-encoder reranker can be enabled via BRUTOR_RERANKER_URL.)
  3. Chunks are accumulated until the max_context_tokens budget is hit, formatted as a numbered context block, and injected as a system message (after your system messages, before user messages) — using the collection’s system_prompt_template if set ({context} placeholder).
  4. The proxy log records rag_chunks_retrieved for the call.

When multiple collections are bound to a group, settings merge generously: union of collections, max top_k, min similarity_threshold, max max_context_tokens.

  1. Create the collection:

    Terminal window
    curl -s -X POST http://localhost:5050/v1/admin/knowledge-base/collections \
    -H "Authorization: Bearer $ADMIN_JWT" -H "Content-Type: application/json" \
    -d '{"name": "sales-playbooks", "description": "EMEA sales enablement",
    "top_k": 5, "similarity_threshold": 0.75, "max_context_tokens": 2000,
    "group_ids": ["group-01ABC..."]}'
    {
    "id": "kbc-01K1...",
    "name": "sales-playbooks",
    "enabled": true,
    "top_k": 5,
    "similarity_threshold": 0.75,
    "max_context_tokens": 2000,
    "document_count": 0
    }

    Tunables: top_k (1–50), similarity_threshold (0.0–1.0 cosine cutoff), max_context_tokens (100–32000), system_prompt_template (≤4096 chars, {context} placeholder). Rebind groups later with POST .../collections/{id}/groups.

  2. Upload documents (multipart/form-data; optional form fields name, version, author):

    Terminal window
    curl -s -X POST \
    http://localhost:5050/v1/admin/knowledge-base/collections/kbc-01K1.../documents \
    -H "Authorization: Bearer $ADMIN_JWT" \
    -F "file=@q3-playbook.pdf" -F "name=Q3 Playbook" -F "version=2026.3"

    Supported types: PDF, DOCX/XLSX/PPTX (and legacy DOC/XLS/PPT), plain text, Markdown, HTML, CSV. Unsupported types → 400.

  3. Poll until ready. Upload is asynchronous — the 201 response has status: "pending". The KB uploader service (a separate worker) extracts text, chunks (500 tokens, 50 overlap, sentence-aware), embeds through the gateway, and upserts vectors. Poll the document until status is ready:

    Terminal window
    curl -s http://localhost:5050/v1/admin/knowledge-base/documents/kbd-01K1... \
    -H "Authorization: Bearer $ADMIN_JWT"
    {
    "id": "kbd-01K1...",
    "name": "Q3 Playbook",
    "status": "ready",
    "chunk_count": 42,
    "embedding_model": "text-embedding-3-small"
    }

    Lifecycle: pending → processing → ready | error (error_message carries the failure). POST .../documents/{id}/reembed re-queues a document after content or model changes.

  4. Preview retrieval with the admin search endpoint:

    Terminal window
    curl -s -X POST \
    http://localhost:5050/v1/admin/knowledge-base/collections/kbc-01K1.../search \
    -H "Authorization: Bearer $ADMIN_JWT" -H "Content-Type: application/json" \
    -d '{"query": "Q3 EMEA renewal terms", "top_k": 8}'
    {
    "query": "Q3 EMEA renewal terms",
    "total": 3,
    "results": [
    {
    "chunk": {"document_id": "kbd-01K1...", "chunk_index": 7,
    "content": "Renewal terms for EMEA in Q3 ..."},
    "score": 0.87,
    "document_name": "Q3 Playbook"
    }
    ]
    }
  5. Chat, grounded. Any LLM call or portal chat made by a member of the bound group is now grounded automatically — no request changes.

Building your own chat client on the Portal API? List what the signed-in user can reach (access is enforced by the resource-group join):

Terminal window
curl -s "http://localhost:8100/v1/portal/knowledgebases/?enabled_only=true" \
-H "Authorization: Bearer $PORTAL_JWT"
[
{
"id": "kbc-01K1...",
"name": "sales-playbooks",
"enabled": true,
"top_k": 5,
"document_count": 12,
"ready_count": 12
}
]

GET /v1/portal/knowledgebases/{collection_id} returns one collection, access-checked. There is deliberately no portal search endpoint — retrieval only happens inside governed LLM calls.

The embedding model is tenant-wide (default_embedding_model_id on the tenant), not per-collection — guaranteeing a single consistent vector space. Any embedding-capable model in your catalog works (OpenAI, Ollama, vLLM, …), and all embedding traffic — ingest and query — routes through the gateway itself, so it’s metered, quota’d and policy-checked like everything else.

Changing the tenant’s embedding model triggers a tenant-wide re-embed (all documents flip back to pending). A dimension mismatch between existing vectors and the current model raises a kb_vector_dim_mismatch alert; the remedy is that re-embed.

  • Search scope = resource-group binding. A collection is retrievable only by members of its bound groups — enforced identically in the portal listing and the proxy’s retrieval path.
  • Tenant isolation is cryptographic. Every Qdrant operation mints a fresh 300-second tenant-scoped JWT naming exactly brutor_kb_{tenant_id} — cross-tenant access is rejected at the vector-store layer, per call.
  • Connector documents carry source ACLs. Content synced from external tools keeps its origin permissions (e.g. Slack channel membership, Drive sharing): at retrieval time a connector chunk is only eligible if the requesting user’s mapped principals intersect the document’s ACL. Direct uploads are governed by the group binding alone.

The KB connector service pulls content on a per-connector cron schedule and feeds it through the same ingestion pipeline (source: "connector"). Eight connector types ship: Confluence, Notion, Google Drive, Slack, GitHub, Jira, SharePoint, and a web crawler. Configure them in the Admin UI (Knowledge Base → Connectors) or via /v1/admin/kb-connectors — catalog at GET /v1/admin/kb-connectors/catalog, per-connector POST .../{id}/sync for an immediate run, GET .../{id}/runs for history. Credentials are encrypted at rest and never returned by the API.

  • The admin base path is hyphenated (knowledge-base); the portal path is one plural word (knowledgebases).
  • A 201 upload response means queued, not searchable — the KB uploader worker must be running (it is part of the standard compose stack) and the document must reach ready.
  • No request-level KB selection and no per-collection embedding model — both are deliberate design decisions (group-driven grounding; single tenant vector space).