Knowledge base
The knowledge base holds documents your bots can search by meaning: price lists, FAQs, opening hours, policies. LiteBot splits each document into chunks, turns every chunk into a vector with an embeddings provider, and stores the vectors in a vector database. A bot reaches the knowledge base through a search tool. This chapter covers the vector and embeddings settings, collections, uploading and searching documents, how a bot uses the results, and what to do when you change the embeddings model. Read it before you load your first documents, because the embeddings choice is hard to change later.
How it fits together
1. Upload a file or post text -> POST /api/documents/upload or /api/documents
2. Split into chunks -> 1000 characters, 200 overlapping (files only)
3. Embed each chunk -> gemini, openai, huggingface or local
4. Store -> vectors in Vectra or Milvus; text and metadata in Mongo knowledge_documents
5. A bot calls a search tool -> the question is embedded and the closest chunks come back
Mongo keeps the original text of every chunk, so the vectors can always be rebuilt from it (Changing the embeddings model).
Vector database
| Variable | Default | Meaning |
|---|---|---|
VECTOR_DB_PROVIDER |
milvus when unset; .env.example sets vectra |
vectra (files on disk, no extra service) or milvus (Milvus or Zilliz Cloud) |
VECTRA_DATA_PATH |
./data/vectra |
Folder for Vectra indexes, one subfolder per collection. A relative path is resolved from the working directory |
MILVUS_ADDRESS |
http://localhost:19530 |
Milvus server |
MILVUS_USERNAME, MILVUS_PASSWORD |
empty | Milvus credentials |
MILVUS_DATABASE |
default |
Milvus database |
Set VECTOR_DB_PROVIDER explicitly. The code falls back to Milvus when it is unset, and without a Milvus server the knowledge base fails to start while the rest of LiteBot keeps running.
Vectra suits a single server: there is nothing else to run, and backing up means copying the folder. Run the binary from its own folder so ./data/vectra lands beside it, or set an absolute path. Milvus suits several LiteBot nodes sharing one knowledge base, or very large collections.
Search caching:
| Variable | Default | Meaning |
|---|---|---|
VECTOR_CACHE_ENABLED |
off | Must be exactly true to cache search results. Legacy name MILVUS_CACHE_ENABLED |
VECTOR_CACHE_TTL |
3600 | Seconds a cached search result lives. Legacy name MILVUS_CACHE_TTL |
VECTOR_SEARCH_TIMEOUT |
3000 | Milliseconds allowed for a search. Legacy name MILVUS_SEARCH_TIMEOUT |
VECTOR_CACHE_MAX_SIZE |
1000 for the search-result cache, 2000 for the query-embedding cache | Entries per cache; one variable sets both. Legacy name MILVUS_CACHE_MAX_SIZE (search-result cache only) |
VECTOR_CACHE_TTL_MS |
300000 | Lifetime of the vector layer's query-embedding cache, in milliseconds |
Embeddings
The embeddings provider turns text into vectors, both when you add documents and on every search.
| Provider | Registers when | Model | Dimension |
|---|---|---|---|
gemini |
GEMINI_API_KEY is set, or Vertex is configured |
GEMINI_EMBED_MODEL, default gemini-embedding-001 |
GEMINI_EMBED_DIMENSION, default 768 |
openai |
OPENAI_API_KEY is set |
text-embedding-ada-002 |
1536 |
huggingface |
HUGGINGFACE_API_KEY is set |
sentence-transformers/all-MiniLM-L6-v2 |
384 |
local |
always | built-in, works offline | 1536 |
local produces vectors from the text itself without a model. It needs no key, but its matches are much weaker than a real embeddings model; use it only for testing.
| Variable | Default | Meaning |
|---|---|---|
EMBEDDINGS_PROVIDER |
unset | gemini, openai, huggingface or local. Unset, or a provider that is not registered, picks the first available of gemini, openai, huggingface, local |
GEMINI_EMBED_MODEL |
gemini-embedding-001 |
Gemini embeddings model |
GEMINI_EMBED_DIMENSION |
768 | Vector size requested from Gemini |
GEMINI_EMBED_TRANSPORT |
Vertex when VERTEX_* is configured |
aistudio forces the API-key route |
GEMINI_EMBED_BATCH |
100 | Texts per Vertex request when indexing |
GEMINI_EMBED_TIMEOUT |
15000 | Vertex request timeout in milliseconds |
EMBED_BATCH_SIZE |
64 | Documents per embedding request when indexing, all providers |
EMBED_CACHE_ENABLED |
true | Cache query embeddings in memory (exact text only) |
EMBED_CACHE_MAX_SIZE |
5000 | Entries in that cache |
Gemini embeddings follow the Vertex settings from LLM providers automatically: when VERTEX_PROJECT_ID and a credentials file are set, embeddings go to the same region. AI Studio and Vertex return identical vectors for the same model and dimension, so switching between them needs no re-index. From India, a Vertex embedding takes about 400 ms against about 800 ms through AI Studio, and every search pays it once. A bad credentials file logs a warning and falls back to AI Studio.
The provider is chosen at startup and the log shows it ("Embeddings providers initialized", with defaultProvider). Adding a key later can change the auto-selected provider on the next restart, which breaks search until you re-index. Set EMBEDDINGS_PROVIDER explicitly.
Collections
A collection is a named group of documents searched together, for example faqs or price_list. Names use letters, digits and _, up to 50 characters.
The registry lives in Mongo kb_collections. Four global collections exist from the start: knowledge_base (the default when a request names none), hospital_info, faqs and policies. At startup, any vector collection that is not yet in the registry is added as a global collection.
Tenant scoping
- A tenant sees the global (
*) collections plus its own. Global collections are read-only for tenant users. - A tenant collection is stored under a physical name
<tenant>__<name>(hyphens in the tenant id become_), so two tenants can both havefaqs. The API always shows the plain name. - When a name exists both globally and in the tenant, the tenant's own collection wins.
- A global admin can act on a tenant's collection by adding
tenantIdto the request.
curl -s -X POST http://127.0.0.1:3345/api/collections \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"name":"price_list","description":"Current prices"}'
{ "success": true, "message": "Collection created successfully",
"data": { "name": "price_list", "description": "Current prices", "documentCount": 0 } }
| Method and path | What it does |
|---|---|
GET /api/collections |
Collections you can see, with document and chunk counts |
POST /api/collections |
Create { name, description } (admin may add tenantId) |
GET /api/collections/:name/documents |
Documents in a collection |
GET /api/collections/:name/stats |
Counts for one collection |
DELETE /api/collections/:name?confirm=true |
Drop the vectors, the registry row and the stored text. Cannot be undone |
GET /api/collections/reindex |
Rebuild all vectors from stored text (admin, streams progress) |
Adding documents
Upload a file
POST /api/documents/upload takes one file as multipart form data.
- Formats: PDF, DOCX, TXT and Markdown (
.pdf,.docx,.txt,.md). - Size: up to 10 MB per file.
- Text is extracted, split into chunks of 1000 characters with 200 characters of overlap, and each chunk is stored and embedded. Chunks share a
parentDocumentIdand are titled<filename> - Part N. - Optional form fields:
collection(defaultknowledge_base) andmetadata, a JSON string.chunkSizeandoverlapinsidemetadatachange the split for that file.
curl -s -X POST http://127.0.0.1:3345/api/documents/upload \
-H "Authorization: Bearer <token>" \
-F "file=@price-list.pdf" -F "collection=price_list" \
-F 'metadata={"chunkSize":800,"overlap":150,"category":"pricing"}'
{ "success": true, "data": { "filename": "price-list.pdf", "chunks": 14, "collection": "price_list",
"parentDocumentId": "6f1c...", "documentIds": ["6f1c...-chunk-000", "..."] } }
A scanned PDF with no text layer returns "No content could be extracted from the file". Smaller chunks give more precise matches but less context per result; one topic per chunk works best, so short FAQ-style source files beat long manuals.
To replace a file, delete its chunks with DELETE /api/documents/parent/:parentDocumentId and upload it again.
Add text
POST /api/documents stores one document from JSON. It is not split: the whole content (up to 50,000 characters) becomes a single vector. Keep each document to one topic, or upload a file to get chunking.
curl -s -X POST http://127.0.0.1:3345/api/documents \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"id":"hours","title":"Opening hours","content":"Open Monday to Saturday, 9 am to 6 pm. Closed on Sundays.","collection":"faqs","tags":["hours"]}'
| Field | Default | Meaning |
|---|---|---|
title |
required | Up to 200 characters |
content |
required | Up to 50,000 characters |
id |
generated | Posting an existing id again updates that document |
collection |
knowledge_base |
Target collection |
category |
general |
Free label |
tags |
[] |
Labels, up to 30 characters each |
source |
api |
Where the text came from |
metadata |
{} |
Anything else you want back in search results |
POST /api/documents/batch adds several at once. GET, PUT and DELETE /api/documents/:id read, change and remove one document.
The dashboard's documents page does the same: pick a collection, upload or type a document, and search it.
Searching
curl -s -X POST http://127.0.0.1:3345/api/documents/search \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"query":"are you open on sunday","collection":"faqs","limit":3}'
{ "success": true, "query": "are you open on sunday", "resultsCount": 3,
"data": [ { "id": "hours", "title": "Opening hours",
"content": "Open Monday to Saturday, 9 am to 6 pm. Closed on Sundays.", "score": "0.188", "collection": "faqs" } ] }
| Field | Default | Meaning |
|---|---|---|
query |
required | Up to 500 characters |
collection |
knowledge_base |
Collection to search |
limit |
4 | 1 to 20 results |
threshold |
none | Drop weak matches; between -2 and 2 (scoreThreshold is the older name) |
Use this endpoint to check what a bot will find before you blame the prompt. If the right chunk is not in the top three here, the bot will not see it either.
Using the knowledge base from a bot
A bot has no knowledge-base setting of its own. It searches through a tool that calls context.modules.knowledgeBase.searchDocuments, and you add that tool to the bot's tools (Tools).
// config/tools/searchFaq.js
module.exports = {
name: 'searchFaq',
description: 'Look up opening hours, prices, location and policies. Call it before answering any factual question about the business.',
parameters: {
type: 'object',
properties: { query: { type: 'string', description: 'The question, in the caller\'s words' } },
required: ['query']
},
metadata: { category: 'knowledge', cacheable: true, cacheTTL: 600 },
async execute(params, context) {
const kb = context.modules.knowledgeBase;
try {
if (kb.initialized === false) await kb.initialize();
const results = await kb.searchDocuments(params.query, { collection: 'faqs', limit: 3 });
if (!results.length) {
return { success: true, found: false, message: 'Nothing in the knowledge base matches. Say you will check and offer a callback.' };
}
return {
success: true,
found: true,
results: results.map(r => ({ title: (r.metadata && r.metadata.title) || 'Untitled', text: r.text }))
};
} catch (error) {
context.modules.logger.error('searchFaq failed', { error: error.message });
return { success: false, message: 'The knowledge base is unavailable right now.' };
}
}
};
- Fix the collection in the tool rather than letting the model choose it, unless the bot truly needs several.
- When the bot belongs to a tenant,
knowledgeBaseonly resolves that tenant's collections and the global ones; any other name fails with "Collection not found". The plain name (faqs) is enough; never use the physical<tenant>__faqsform. - Return two or three short chunks. Every result goes back to the model as input tokens on that turn.
- Search is cacheable because it only reads; edits to the knowledge base show up once the cache entry expires.
Startup warm-up
At startup LiteBot connects to the vector database in the background, without delaying the server, then registers collections and loads the default ones. It then pre-embeds frequent questions, so the first searches after a restart do not each wait for the embeddings provider.
| Variable | Default | Meaning |
|---|---|---|
VECTOR_WARMUP_DISABLED |
false | true skips the warm-up |
VECTOR_WARMUP_QUERIES |
empty | Comma-separated questions to pre-embed. Empty uses the most frequent user messages of the last 7 days from request_analytics |
VECTOR_WARMUP_LIMIT |
50 | Most questions to pre-embed |
The log shows "Vector database pre-initialization completed" and "Embedding cache warm-up complete" with counts. If the vector database is down, the server still starts and knowledge-base calls fail until it is back.
Changing the embeddings model
Vectors from different models, or different dimensions, cannot be compared. After you change EMBEDDINGS_PROVIDER, GEMINI_EMBED_MODEL or GEMINI_EMBED_DIMENSION, every stored vector must be rebuilt. Until you do, searches return poor or no matches.
- Back up the vector data (the Vectra folder, or a Milvus backup) and the Mongo
knowledge_documentscollection. - Clear the old vectors. With Vectra, stop LiteBot and move the
VECTRA_DATA_PATHfolder aside. With Milvus, drop the collections, because an existing Milvus collection keeps its original dimension. - Change the variables and restart.
- Re-index from the stored text: the re-index button on the dashboard's documents page, or the admin endpoint, which streams progress as server-sent events.
curl -N http://127.0.0.1:3345/api/collections/reindex -H "Authorization: Bearer <token>"
event: progress
data: {"collection":"faqs","batch":2,"totalBatches":3,"collectionProgress":"20/26","percent":41}
event: complete
data: {"summary":{"faqs":26,"knowledge_base":112},"totalIndexed":138}
Only one re-index runs at a time; a second request gets 409. Closing the connection stops the re-index part-way, so let it finish. Switching Gemini between AI Studio and Vertex with the same model and dimension needs none of this.
Where to go next
- Write the search tool: Tools
- Which tenants see which collections: Users, roles and tenants
- Gemini on Vertex, which embeddings follow: LLM providers
- Every endpoint in one place: HTTP API
- Back up
data/vectraand Mongo: Operations