Checkout ✅✅ => https://ragchatbot.sharadsnaik.in/
A production-grade multimodal chatbot with RAG (Retrieval-Augmented Generation), vision, audio transcription, and real-time voice-to-voice conversation — all built on free-tier APIs.
| Layer | Technology |
|---|---|
| API Framework | FastAPI (async) |
| Task Queue | Celery + Redis |
| Vector Store | Pinecone (cloud serverless) |
| Embeddings | all-MiniLM-L6-v2 (sentence-transformers, local) |
| LLM | Groq — llama3-8b-8192 (free tier) |
| STT (batch) | HuggingFace — openai/whisper-large-v3-turbo |
| STT (real-time) | Groq — whisper-large-v3-turbo |
| Vision | HuggingFace — moonshotai/Kimi-K2.5 |
| TTS | edge-tts (free, no key) or Groq TTS |
| Frontend | React + CSS Modules |
| Backend Deploy | Render |
| Frontend Deploy | Netlify |
project/
│
├── router/
│ ├── main.py # FastAPI app entry point
│ └── routes.py # All HTTP endpoints
models
│ └── voice_to_voice.py #realtime voice-to-voice
│
├── pipelines/
│ ├── text_pipeline.py # RAG: embed → Pinecone → Groq LLM
│ ├── image_pipeline.py # Vision: image → Kimi-K2.5 → answer
│ └── audio_pipeline.py # Audio: Whisper → optional Groq
│
├── workers/
│ ├── text_worker.py # Celery worker: ingest + RAG query
│ ├── image_worker.py # Celery worker: vision
│ └── audio_worker.py # Celery worker: audio transcription
│
├── voice_ws/
│ └── main.py # Real-time WebSocket voice pipeline
│
├── queues/
│ └── celery_config.py # Celery + Redis config
│
├── utils/
│ ├── mime_detector.py # MIME type detection (magic bytes)
│ ├── pdf_extractor.py # PDF → text
│ ├── text_chunker.py # Chunk documents for ingestion
│ ├── embedding_manager.py # Sentence-transformers wrapper
│ ├── vector_store.py # Pinecone upsert / query
│ ├── bm25_retriever.py # Keyword search (hybrid RAG)
│ ├── llm_client.py # Groq LLM client
│ └── config.py # All env var loading
│
└── frontend/ # React app
├── src/
│ ├── hooks/
│ │ └── useVoiceSocket.js
│ ├── components/
│ │ ├── AudioVisualiser.jsx / .module.css
│ │ ├── MessageList.jsx / .module.css
│ │ ├── MicButton.jsx / .module.css
│ │ └── StatusBar.jsx / .module.css
│ ├── VoiceApp.jsx
│ └── VoiceApp.module.css
└── public/
└── index.html
Create a .env file in the project root. Never commit this file.
# ── Required ────────────────────────────────────────────────
GROQ_API_KEY=gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
PINECONE_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
# ── Pinecone ─────────────────────────────────────────────────
PINECONE_INDEX_NAME=rag-index
PINECONE_CLOUD=aws
PINECONE_REGION=us-east-1
# ── RAG / LLM ────────────────────────────────────────────────
GROQ_MODEL=llama3-8b-8192
EMBED_MODEL=all-MiniLM-L6-v2
RAG_TOP_K=5
RAG_MAX_TOKENS=1024
# ── Vision ───────────────────────────────────────────────────
IMAGE_MAX_TOKENS=1024
IMAGE_MAX_PX=1024
# ── Real-time Voice ──────────────────────────────────────────
GROQ_LLM_MODEL=llama3-8b-8192
TTS_PROVIDER=edge # "edge" = free | "groq" = Groq TTS
VAD_SILENCE_MS=800
# ── Celery / Redis ───────────────────────────────────────────
CELERY_BROKER_URL=redis://localhost:6379/0
CELERY_RESULT_BACKEND=redis://localhost:6379/0
# ── CORS (comma-separated) ───────────────────────────────────
ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000| Key | URL |
|---|---|
GROQ_API_KEY |
console.groq.com → API Keys |
PINECONE_API_KEY |
app.pinecone.io → API Keys |
HF_TOKEN |
huggingface.co/settings/tokens → New token (read scope) |
- Python 3.11+
- Node.js 18+
- Redis (local or cloud)
- ffmpeg (for audio conversion)
# Ubuntu / WSL
sudo apt-get install -y ffmpeg redis-server
# macOS
brew install ffmpeg redisgit clone <your-repo>
cd project
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install fastapi uvicorn httpx groq \
pinecone-client sentence-transformers \
huggingface_hub pydub edge-tts \
celery redis python-multipart \
openai pillow requests tiktokencd frontend
npm installOpen 4 terminals:
# Terminal 1 — Redis
redis-server
# Terminal 2 — FastAPI backend
uvicorn router.main:app --host 0.0.0.0 --port 8000 --reload
# Terminal 3 — Celery workers (all queues)
celery -A queues.celery_config.celery_app worker \
--queues=text,image,audio,video \
--concurrency=4 --loglevel=info
# Terminal 4 — React frontend
cd frontend && npm run dev| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/chat-direct |
RAG text query — JSON {"message": "..."} |
POST |
/api/v1/upload-direct |
Upload file (text/PDF/image/audio) via Celery |
POST |
/api/v1/vision |
Image + optional prompt → Kimi-K2.5 answer |
POST |
/api/v1/voice |
Audio file + optional query → Whisper + Groq |
GET |
/api/v1/status/{job_id} |
Poll async Celery job |
GET |
/api/v1/health |
Health check |
| Endpoint | Description |
|---|---|
ws://localhost:8000/ws/voice |
Real-time push-to-talk voice conversation |
Client → Server:
{ "event": "audio_chunk", "data": "<base64 PCM>", "sample_rate": 16000 }
{ "event": "end_of_speech" }
{ "event": "reset" }Server → Client:
{ "event": "transcript", "text": "What is the capital of France?" }
{ "event": "token", "text": "Paris" }
{ "event": "audio_chunk", "data": "<base64 mp3>", "format": "mp3" }
{ "event": "processing", "stage": "stt|llm|idle" }
{ "event": "turn_end" }
{ "event": "error", "message": "..." }User query
│
▼
Embed query (all-MiniLM-L6-v2, 384-dim)
│
▼
Pinecone vector search (cosine, top-k=5)
│
▼
Build prompt (transcript + retrieved chunks)
│
▼
Groq llama3-8b-8192 → answer
Ingest documents:
from pipelines.text_pipeline import ingest_documents
ingest_documents([
{"id": "doc1-p0", "text": "...", "metadata": {"source": "manual.pdf"}},
])Image upload (PNG / JPEG / WEBP)
│
▼
Resize to max 1024px (Pillow)
│
▼
Base64 encode → data URL
│
▼
moonshotai/Kimi-K2.5 (HuggingFace free inference)
│
▼
Text answer
Audio upload (WAV / MP3 / OGG / FLAC)
│
▼
Convert to 16kHz mono WAV (pydub + ffmpeg)
│
▼
HuggingFace whisper-large-v3-turbo
(router.huggingface.co — correct 2025 URL)
│
├─ No message → raw transcript
└─ message → Groq LLM answers using transcript as context
Microphone (push-to-talk)
│ PCM chunks over WebSocket every ~256ms
▼
SimpleVAD (energy-based silence detection)
│ utterance complete → fire asyncio.create_task
▼
Groq Whisper STT (~0.5–1.5s)
│
▼
Groq LLM streaming (~0.5s first token)
│ tokens stream in concurrently with TTS
▼
SentenceChunker (detects . ! ? boundaries)
│ per sentence → asyncio.create_task
▼
edge-tts / Groq TTS
│
▼
base64 MP3 → WebSocket → AudioContext queue → speaker
All three stages (STT → LLM → TTS) overlap. The user hears the first sentence while the LLM is still generating the second.
Expected latency:
| Stage | Time |
|---|---|
| STT | 0.5–1.5 s |
| LLM first token | ~0.5 s |
| TTS per sentence | ~0.8–1.5 s |
| User hears first response | ~2–3 s |
# Create a small ingest script
python - << 'EOF'
from pipelines.text_pipeline import ingest_documents
import os
docs = []
for filename in os.listdir("./docs"):
if filename.endswith(".txt"):
with open(f"./docs/{filename}") as f:
text = f.read()
docs.append({
"id": filename,
"text": text,
"metadata": {"source": filename}
})
ingest_documents(docs)
print(f"Indexed {len(docs)} documents.")
EOFFor PDFs, use utils/pdf_extractor.py to extract text first, then chunk and ingest.
- Push to GitHub
- Create a new Web Service on render.com
- Set Start Command:
uvicorn router.main:app --host 0.0.0.0 --port $PORT - Add all environment variables from
.envin the Render dashboard - Add a Redis instance on Render and set
CELERY_BROKER_URL
For Celery workers, create a separate Render Background Worker:
celery -A queues.celery_config.celery_app worker --queues=text,image,audio --concurrency=2 --loglevel=info- Build the React app:
cd frontend && npm run build
- Drag the
dist/folder to netlify.com/drop - Or connect your GitHub repo and set:
- Build command:
npm run build - Publish directory:
dist
- Build command:
- Set environment variable:
VITE_WS_URL=wss://your-render-app.onrender.com/ws/voice
In
useVoiceSocket.js, replace theWS_URLconstant with:const WS_URL = import.meta.env.VITE_WS_URL || "ws://localhost:8000/ws/voice";
| Error | Cause | Fix |
|---|---|---|
410 Gone on HF ASR |
Old api-inference.huggingface.co URL |
Use router.huggingface.co/hf-inference/models/... |
Groq 400 Bad Request on LLM |
Empty message content in history | Sanitise messages before sending (already fixed in stream_llm_tokens) |
503 on HF Whisper |
Model cold-starting on free tier | Retry after ~20 seconds |
| Single-word transcripts firing LLM | VAD cutting off too early | Increase VAD_SILENCE_MS to 1000–1200 |
| Audio not playing in browser | AudioContext suspended (autoplay policy) |
Already handled — context resumes on first user gesture |
| CORS error from React | Origins not whitelisted | Add your Netlify URL to ALLOWED_ORIGINS |
| Service | Free Limit |
|---|---|
| Groq API | 14,400 req/day, 30 req/min |
| Pinecone | 1 index, 2GB storage, 100K vectors |
| HuggingFace Inference | Rate-limited, cold starts on inactivity |
| edge-tts | Unlimited (Microsoft Edge TTS, no key) |
| Render | 750 hrs/month free, spins down after 15 min idle |
MIT