Technical reference for SmartSupport AI.
- Architecture
- RAG Engine
- Index Storage
- API Server
- Widget Script
- Configuration
- Build Index Script
- Troubleshooting
- Performance Tuning
- Upgrading Storage for Scale
SmartSupport AI has three parts:
Backend (backend/)
A FastAPI server that receives customer questions, searches the index, generates answers, and serves the widget script.
Widget (widget/)
A self-contained JavaScript file that injects a chat button and panel into any webpage. It reads its configuration from a global object injected by the server.
Index (index/)
The processed knowledge built from your documents. Created by running build_index.py. Contains the FAISS vector index, metadata, and embedding cache.
Customer types a question in the chat widget
|
| HTTP POST /api/chat
v
FastAPI receives the question
|
v
RAGEngine.query(question)
|
|-- Embeds the question into a vector (384 numbers)
|-- Searches FAISS for the top-K most similar document chunks
|-- Builds a prompt: system instruction + retrieved chunks + question
|-- SmolLM2 reads the prompt and generates an answer
|
v
FastAPI returns JSON
|
v
Widget renders the answer in the chat panel
The RAG engine (backend/rag_engine.py) handles everything related to documents and answer generation.
Documents are split into overlapping chunks before indexing.
CHUNK_SIZE = 400 characters (~4 to 5 sentences)
CHUNK_OVERLAP = 80 characters (repeated at start of next chunk)
Overlap preserves context that would otherwise be lost at chunk boundaries. For example, if a question and its answer are split across a boundary, the overlap ensures both appear in at least one chunk.
Each chunk is converted to a 384-dimensional float vector using the all-MiniLM-L6-v2 sentence-transformers model. Vectors that represent similar meaning are close together in vector space.
The same model is used at query time to embed the customer's question. The question vector is then compared to all stored chunk vectors.
Computing embeddings is the slowest part of the indexing process. The engine caches every embedding keyed by the MD5 hash of the chunk text.
cache key = md5(chunk_text)
cache value = [0.12, -0.34, 0.88, ...] (384 floats)
If you run build_index.py again after editing only one file, chunks from the unchanged file are loaded from cache instantly. Only new or changed content needs to be re-embedded.
The cache is stored at index/embed_cache.json.
FAISS (Facebook AI Similarity Search) stores all chunk vectors and performs fast nearest-neighbor search.
Index type: IndexFlatIP (inner product on L2-normalized vectors = cosine similarity).
Cosine similarity scores range from 0.0 to 1.0. Scores above 0.7 indicate a strong match. Scores below 0.3 indicate that no relevant content was found.
Retrieved chunks are assembled into a prompt for SmolLM2-135M-Instruct:
[system]
You are a professional customer support assistant.
Answer the user's question using ONLY the context provided below.
Be concise, accurate, and helpful.
If the answer is not in the context, say so clearly.
Context:
[Source: faq.txt]
We accept returns within 7 days...
[Source: products.txt]
Product: HP Pavilion 15, Price: 350,000 NGN...
[user]
What is your return policy?
[assistant]
The model completes the assistant turn, generating a natural language answer grounded in the retrieved context.
SmartSupport AI does not use a database. All data is stored as files in the index/ directory.
index/
|-- faiss.index Binary FAISS index file. Not human-readable.
|-- metadata.json JSON array of all text chunks with their source filenames.
`-- embed_cache.json JSON object mapping md5(chunk) to its float vector.
[
{
"text": "We accept returns within 7 days for faulty or incorrectly sent items...",
"source": "faq.txt"
},
{
"text": "Product: HP Pavilion 15. Price: 350,000 NGN. Processor: Intel Core i5...",
"source": "products.txt"
}
]The original document files are read during indexing but not stored in the index/ directory. The original files remain in documents/. The index only contains the processed, vectorized knowledge.
The index/ directory is written to disk and persists across server restarts. When the server starts, it loads the index from disk into memory.
If you delete the index/ directory, run python scripts/build_index.py again to rebuild it.
backend/main.py is a FastAPI application.
GET /health
Returns the server status and index statistics.
POST /api/chat
Accepts a question and returns an answer.
POST /api/chat/stream
Same as /api/chat but returns a Server-Sent Events stream. Each event contains one token.
GET /widget.js
Returns the chat widget JavaScript file with the business configuration injected at the top. The widget is served with Cache-Control: no-cache so configuration changes take effect immediately after a server restart.
Development mode (with auto-reload on file changes):
ENV=development python backend/main.pyProduction mode:
python backend/main.pyWith uvicorn directly:
uvicorn backend.main:app --host 0.0.0.0 --port 8000widget/widget.js is a self-contained JavaScript file with no external dependencies.
- On load, it reads
window.__SS_CONFIG__for configuration (injected by the server) - It injects a
<style>block with all widget CSS into<head> - It creates a launcher button and a chat panel and appends them to
<body> - When the customer clicks the button, the panel opens
- When the customer sends a message, it posts to
/api/chatand displays the response
The widget uses ES5-compatible syntax. It works in all modern browsers and Internet Explorer 11.
All widget element IDs are prefixed with ss- to avoid clashing with the host website's own styles and scripts.
The widget CSS is scoped to #ss-panel, #ss-launcher, and .ss-* selectors.
On screens narrower than 420px, the panel expands to fill the available width with 10px margins on each side.
All configuration is managed through environment variables. In production, set these in your server environment or in a .env file.
These values are injected into the widget script by the server.
| Variable | Description | Default |
|---|---|---|
BOT_NAME |
Name shown in the chat header | Support |
PRIMARY_COLOR |
Main color button, user bubbles, accents | #2563eb |
SECONDARY_COLOR |
Gradient secondary color | #1d4ed8 |
WELCOME_MESSAGE |
First message the bot sends when the widget opens | See .env.example |
FALLBACK_MESSAGE |
Message shown when confidence is below threshold | See .env.example |
WIDGET_POSITION |
Button placement: bottom-right or bottom-left |
bottom-right |
BUTTON_SIZE |
Launcher button diameter (CSS size value) | 56px |
API_KEYS |
Set API key (Optional) | ss-abc123 |
RATE_LIMIT_PER_MINUTE |
Rate limit | 20 |
| Variable | Description | Default |
|---|---|---|
API_BASE_URL |
Public URL of this server. Must be set for widget.js to work. | http://localhost:8000 |
PORT |
Port the server listens on | 8000 |
| Variable | Description | Default |
|---|---|---|
EMBED_MODEL |
Hugging Face model for text embedding | all-MiniLM-L6-v2 |
LLM_MODEL |
Hugging Face model for answer generation | HuggingFaceTB/SmolLM2-135M-Instruct |
CHUNK_SIZE |
Characters per document chunk | 250 |
CHUNK_OVERLAP |
Overlap characters between chunks | 40 |
TOP_K |
Number of chunks retrieved per question | 2 |
INDEX_DIR |
Directory where the FAISS index is stored | ./index |
scripts/build_index.py reads all supported files from the documents/ directory, processes them, and writes the index to index/.
| Extension | Notes |
|---|---|
.txt |
Plain text. Best format for FAQs. |
.md |
Markdown. Parsed as plain text. |
.pdf |
Text-based PDFs only. Scanned PDFs (images) are not supported. |
.csv |
Each row is treated as a text entry. |
Run build_index.py again whenever you:
- Add a new document to
documents/ - Edit existing content
- Delete a document
After re-running, restart the server so it loads the updated index.
The widget does not appear on my website
Check the browser console (F12 > Console) for errors. Common causes:
- Your website is served over HTTPS but your API server uses HTTP. Browsers block mixed content. Your API server must also use HTTPS.
- The URL in the
<script src="...">tag is wrong.
The bot answers with the fallback message for every question
Possible causes:
- The documents do not cover the topic being asked about.
- The documents are too vague or too brief.
- The index was not rebuilt after the documents were edited.
The server starts but says "Index not found"
You have not run build_index.py yet, or you ran it but the index was saved to a different directory. Check that index/faiss.index exists.
build_index.py finishes but shows 0 chunks
The files in documents/ contain no readable text. Check:
- The files are not empty.
- PDF files are text-based, not scanned images.
- The file encoding is UTF-8.
First response takes 20-30 seconds
This is the model cold-start. On first query, the language model loads from disk into memory. Subsequent responses are much faster (1-3 seconds). On Hugging Face Spaces free tier, the Space may also sleep after inactivity and take 30 seconds to wake.
| Issue | Adjustment |
|---|---|
| Answers too brief | Increase TOP_K to 4 |
| Answers include unrelated content | Decrease TOP_K to 2 (default) |
| Index builds slowly | Normal on first run. Cache speeds up subsequent runs. |
| Answers feel generic | Improve document quality, be more specific in your FAQ |
| Response time too slow | Switch to a smaller LLM (if available) |
| Better answer quality | Switch to a larger LLM (Phi-3, Mistral-7B on GPU) |
| Model | RAM required | Speed | Quality |
|---|---|---|---|
HuggingFaceTB/SmolLM2-360M-Instruct |
1GB | Fast | Basic |
HuggingFaceTB/SmolLM2-1.7B-Instruct (default) |
3GB | Moderate | Good |
microsoft/Phi-3-mini-4k-instruct |
8GB | Slower | Better |
mistralai/Mistral-7B-Instruct-v0.3 |
16GB (GPU recommended) | Slower | Best |
| Model | Size | Speed | Quality |
|---|---|---|---|
all-MiniLM-L6-v2 (default) |
80MB | Fast | Good |
all-mpnet-base-v2 |
420MB | Moderate | Better |
BAAI/bge-large-en-v1.5 |
1.3GB | Slow | Best |
The default file-based storage is sufficient for most deployments. If you need to scale:
For platforms where the disk resets on restart (Heroku, Railway free tier), push the index/ directory to a Hugging Face Dataset repository after building:
from huggingface_hub import HfApi
api = HfApi()
api.upload_folder(
folder_path="./index",
repo_id="YOUR_USERNAME/smartsupport-index",
repo_type="dataset",
)On startup, download it before loading:
from huggingface_hub import snapshot_download
snapshot_download(
repo_id="YOUR_USERNAME/smartsupport-index",
repo_type="dataset",
local_dir="./index",
)For production deployments with frequent updates or large document sets, replace the FAISS index with Pinecone, Qdrant, or pgvector. The RAGEngine class is designed so only the index_documents, save, load, and retrieve methods need to change.
Documentation version: 2.0.0