Telegram bot with per-user context memory — each user has their own private RAG namespace, so the bot remembers what they've talked about and never leaks context between users.
Backed by Claude Sonnet 4 for answers, SQLite + sentence-transformers for the per-user vector store. Runs on a free-tier Railway/Render dyno or your laptop.
Most "ChatGPT bot for Telegram" tutorials shove every user's conversation into one shared context. That:
- Leaks information between users (privacy bug + GDPR risk).
- Forgets prior conversations when context fills up (it always does).
- Treats power users and one-off users identically — neither gets useful behavior.
This bot fixes all three: every Telegram user gets a private namespaced vector store. The bot retrieves the user's relevant past messages before answering, so it actually remembers across days/weeks.
Telegram message ─► /webhook (FastAPI)
│
▼
get user_id from update
│
▼
embed the message
│
┌───────────┴───────────┐
▼ ▼
vector search save embedding
in user's namespace in user's namespace
│ │
└───────────┬───────────┘
▼
build prompt: [user history excerpts] + [current msg]
│
▼
Claude Sonnet 4
│
▼
reply via Telegram Bot API
git clone https://github.com/arpit2005/telegram-rag-bot.git
cd telegram-rag-bot
pip install -r requirements.txt
cp .env.example .env
# Set ANTHROPIC_API_KEY and TELEGRAM_BOT_TOKEN
# (get token from @BotFather on Telegram)
# Run with ngrok/cloudflared for local webhook testing, OR deploy to Railway
uvicorn app:app --host 0.0.0.0 --port 8000
# In another shell, point Telegram at your webhook:
python set_webhook.py https://your-public-url/webhookEvery embedding row in SQLite has a user_id column (the Telegram user ID from each update.message.from.id). All retrieval queries scope to WHERE user_id = ?. There is no shared search across users — full hard isolation at the DB layer.
CREATE TABLE memories (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
text TEXT NOT NULL,
embedding BLOB NOT NULL, -- numpy array, 384-d
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_user ON memories(user_id);- Free-tier deployable: SQLite is a file. No managed-DB cost. Works on Railway/Render free dynos out of the box.
- Fast enough: 384-d embeddings + cosine over 10K rows per user takes ~5ms. You'd need 100+ active users with thousands of messages each before perf matters.
- Migration path: when you outgrow SQLite, swap to MongoDB Atlas Vector or pgvector with the same per-user filter. No prompt or app-logic changes needed.
| File | LOC | Purpose |
|---|---|---|
app.py |
~120 | FastAPI webhook + reply pipeline |
memory.py |
~80 | SQLite storage + cosine retrieval, all per-user-id scoped |
set_webhook.py |
~15 | Helper to register your webhook URL with Telegram |
requirements.txt |
5 | Dependencies (anthropic, fastapi, sentence-transformers, etc.) |
- Rate limiting per user — drop in
slowapior Redis counter - Image input — Telegram sends photos; Claude is multimodal, easy to wire
- Memory pruning — after N memories per user, drop oldest. Currently unbounded.
/forgetcommand — let users wipe their own memory namespace- Postgres swap — for multi-instance deploys, swap SQLite → Postgres + pgvector
MIT.