Build an AI Telegram Bot with Python and Claude: Complete Tutorial
Build a Telegram bot powered by Claude that handles conversations, analyzes images, and remembers context. Full code included — from BotFather setup to deployment.
Telegram bots are one of the most practical AI projects you can build. Unlike a web app that requires frontend development, hosting, and user authentication, a Telegram bot gives you an instant UI, push notifications, and 900 million monthly active users who already know how to use it.
In this tutorial, you’ll build a Telegram bot powered by Claude that:
- Maintains conversation history per user
- Analyzes images sent by users
- Supports custom system prompts
- Handles rate limiting gracefully
- Deploys to a VPS for 24/7 availability
Total build time: about 90 minutes.
Prerequisites
- Python 3.11+
- Telegram account
- Anthropic API key ($5 in credits is enough for testing)
- A VPS for deployment (optional — can run locally for testing)
Step 1: Create Your Telegram Bot
Open Telegram and message @BotFather:
You: /newbot
BotFather: Alright, a new bot. How are we going to call it?
You: My AI Assistant
BotFather: Good. Now pick a username for your bot.
You: my_ai_assistant_bot
BotFather: Done! Your bot token is:
7123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw
Save that token. You’ll need it.
Configure your bot:
/setdescription - Add a description
/setabouttext - Add an about section
/setuserpic - Upload a profile photo
/setcommands - Set command menu:
start - Start a conversation
clear - Clear conversation history
help - Show available commands
mode - Change AI behavior mode
Step 2: Project Setup
# Create project directory
mkdir ai-telegram-bot && cd ai-telegram-bot
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install python-telegram-bot anthropic python-dotenv
# Create project structure
mkdir -p src
touch src/__init__.py src/bot.py src/ai_handler.py src/config.py
touch .env .gitignore requirements.txt
Create .env:
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
ANTHROPIC_API_KEY=your_anthropic_api_key_here
MAX_HISTORY_LENGTH=20
MODEL_NAME=claude-sonnet-4-20250514
Create .gitignore:
.env
venv/
__pycache__/
*.pyc
Step 3: Build the AI Handler
# src/ai_handler.py
"""Handles communication with Claude API."""
import anthropic
import base64
from typing import Optional
class AIHandler:
def __init__(self, api_key: str, model: str = "claude-sonnet-4-20250514"):
self.client = anthropic.Anthropic(api_key=api_key)
self.model = model
self.conversations: dict[int, list] = {} # user_id -> message history
self.system_prompts: dict[int, str] = {} # user_id -> custom prompt
self.default_system_prompt = (
"You are a helpful AI assistant in a Telegram chat. "
"Keep responses concise and well-formatted for mobile reading. "
"Use markdown formatting when helpful. "
"If asked to analyze an image, describe what you see in detail."
)
def set_system_prompt(self, user_id: int, prompt: str) -> None:
"""Set a custom system prompt for a user."""
self.system_prompts[user_id] = prompt
def get_system_prompt(self, user_id: int) -> str:
"""Get the system prompt for a user."""
return self.system_prompts.get(user_id, self.default_system_prompt)
def clear_history(self, user_id: int) -> None:
"""Clear conversation history for a user."""
self.conversations.pop(user_id, None)
def _get_history(self, user_id: int) -> list:
"""Get conversation history, creating if needed."""
if user_id not in self.conversations:
self.conversations[user_id] = []
return self.conversations[user_id]
def _trim_history(self, user_id: int, max_length: int = 20) -> None:
"""Keep only the last N messages to manage token usage."""
history = self._get_history(user_id)
if len(history) > max_length:
self.conversations[user_id] = history[-max_length:]
async def chat(
self,
user_id: int,
message: str,
image_data: Optional[bytes] = None,
image_media_type: str = "image/jpeg"
) -> str:
"""Send a message and get a response from Claude."""
history = self._get_history(user_id)
# Build the user message content
content = []
if image_data:
image_b64 = base64.standard_b64encode(image_data).decode("utf-8")
content.append({
"type": "image",
"source": {
"type": "base64",
"media_type": image_media_type,
"data": image_b64,
}
})
content.append({"type": "text", "text": message})
# Add user message to history
history.append({"role": "user", "content": content})
try:
response = self.client.messages.create(
model=self.model,
max_tokens=1024,
system=self.get_system_prompt(user_id),
messages=history,
)
assistant_message = response.content[0].text
# Add assistant response to history
history.append({
"role": "assistant",
"content": assistant_message
})
# Trim history to prevent token overflow
self._trim_history(user_id)
return assistant_message
except anthropic.RateLimitError:
# Remove the user message we just added
history.pop()
return "I'm receiving too many requests right now. Please try again in a moment."
except anthropic.APIError as e:
history.pop()
return f"API error occurred: {str(e)}"
except Exception as e:
history.pop()
return f"An unexpected error occurred: {str(e)}"
Step 4: Build the Bot
# src/bot.py
"""Main Telegram bot implementation."""
import logging
from telegram import Update, BotCommand
from telegram.ext import (
Application,
CommandHandler,
MessageHandler,
filters,
ContextTypes,
)
from .ai_handler import AIHandler
from .config import Config
# Set up logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
level=logging.INFO
)
logger = logging.getLogger(__name__)
# Initialize AI handler
config = Config()
ai = AIHandler(api_key=config.anthropic_api_key, model=config.model_name)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /start command."""
user = update.effective_user
await update.message.reply_text(
f"Hey {user.first_name}! I'm an AI assistant powered by Claude.\n\n"
f"Just send me a message and I'll respond. You can also:\n"
f"- Send images for analysis\n"
f"- Use /clear to reset our conversation\n"
f"- Use /mode to change my behavior\n"
f"- Use /help for more info"
)
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /help command."""
await update.message.reply_text(
"*Available Commands:*\n\n"
"/start - Start a new conversation\n"
"/clear - Clear conversation history\n"
"/mode <prompt> - Set custom AI behavior\n"
"/mode reset - Reset to default behavior\n"
"/help - Show this help message\n\n"
"*Tips:*\n"
"- Send images and I'll analyze them\n"
"- I remember our conversation context\n"
"- Long conversations use more API credits\n"
"- Use /clear periodically to save costs",
parse_mode="Markdown"
)
async def clear(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /clear command."""
user_id = update.effective_user.id
ai.clear_history(user_id)
await update.message.reply_text("Conversation history cleared. Fresh start!")
async def mode(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /mode command to set custom system prompt."""
user_id = update.effective_user.id
if not context.args:
current = ai.get_system_prompt(user_id)
await update.message.reply_text(
f"*Current mode:*\n{current}\n\n"
f"To change: /mode <your instructions>\n"
f"To reset: /mode reset",
parse_mode="Markdown"
)
return
new_prompt = " ".join(context.args)
if new_prompt.lower() == "reset":
ai.system_prompts.pop(user_id, None)
ai.clear_history(user_id)
await update.message.reply_text("Mode reset to default. History cleared.")
return
ai.set_system_prompt(user_id, new_prompt)
ai.clear_history(user_id)
await update.message.reply_text(
f"Mode updated! I'll now behave according to:\n\n"
f"_{new_prompt}_\n\n"
f"Conversation history cleared for the new mode.",
parse_mode="Markdown"
)
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle text messages."""
user_id = update.effective_user.id
message_text = update.message.text
# Show typing indicator
await update.message.chat.send_action("typing")
response = await ai.chat(user_id, message_text)
# Telegram has a 4096 character limit per message
if len(response) > 4096:
# Split into chunks
chunks = [response[i:i+4096] for i in range(0, len(response), 4096)]
for chunk in chunks:
await update.message.reply_text(chunk, parse_mode="Markdown")
else:
try:
await update.message.reply_text(response, parse_mode="Markdown")
except Exception:
# If Markdown parsing fails, send as plain text
await update.message.reply_text(response)
async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle photo messages."""
user_id = update.effective_user.id
# Get the highest resolution photo
photo = update.message.photo[-1]
# Download the photo
file = await context.bot.get_file(photo.file_id)
image_bytes = await file.download_as_bytearray()
# Get caption or use default prompt
caption = update.message.caption or "What's in this image? Describe it in detail."
# Show typing indicator
await update.message.chat.send_action("typing")
response = await ai.chat(
user_id,
caption,
image_data=bytes(image_bytes),
image_media_type="image/jpeg"
)
try:
await update.message.reply_text(response, parse_mode="Markdown")
except Exception:
await update.message.reply_text(response)
async def post_init(application: Application) -> None:
"""Set bot commands after initialization."""
commands = [
BotCommand("start", "Start a conversation"),
BotCommand("clear", "Clear conversation history"),
BotCommand("mode", "Change AI behavior mode"),
BotCommand("help", "Show available commands"),
]
await application.bot.set_my_commands(commands)
def main() -> None:
"""Start the bot."""
app = (
Application.builder()
.token(config.telegram_bot_token)
.post_init(post_init)
.build()
)
# Register handlers
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("help", help_command))
app.add_handler(CommandHandler("clear", clear))
app.add_handler(CommandHandler("mode", mode))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
app.add_handler(MessageHandler(filters.PHOTO, handle_photo))
# Start polling
logger.info("Bot started. Polling for updates...")
app.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()
Step 5: Configuration
# src/config.py
"""Bot configuration from environment variables."""
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
def __init__(self):
self.telegram_bot_token = os.getenv("TELEGRAM_BOT_TOKEN")
self.anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
self.model_name = os.getenv("MODEL_NAME", "claude-sonnet-4-20250514")
self.max_history = int(os.getenv("MAX_HISTORY_LENGTH", "20"))
if not self.telegram_bot_token:
raise ValueError("TELEGRAM_BOT_TOKEN not set in .env")
if not self.anthropic_api_key:
raise ValueError("ANTHROPIC_API_KEY not set in .env")
Step 6: Run Locally
# From project root
python -m src.bot
# Or create a run script
echo 'python -m src.bot' > run.sh
chmod +x run.sh
./run.sh
Open Telegram, find your bot, and start chatting. Send text messages, images, try /mode to change behavior, and /clear to reset.
Step 7: Deploy to a VPS
For 24/7 availability, deploy to a cheap VPS ($5-10/month from DigitalOcean, Hetzner, or Vultr):
# On your VPS
sudo apt update && sudo apt install -y python3-pip python3-venv git
# Clone your project
git clone https://github.com/yourusername/ai-telegram-bot.git
cd ai-telegram-bot
# Set up environment
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Create .env file with your tokens
nano .env
# Create systemd service for auto-start
sudo tee /etc/systemd/system/telegram-bot.service << 'EOF'
[Unit]
Description=AI Telegram Bot
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/ai-telegram-bot
Environment=PATH=/home/ubuntu/ai-telegram-bot/venv/bin
ExecStart=/home/ubuntu/ai-telegram-bot/venv/bin/python -m src.bot
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# Start the service
sudo systemctl enable telegram-bot
sudo systemctl start telegram-bot
# Check logs
sudo journalctl -u telegram-bot -f
Step 8: Add Cost Controls
To prevent runaway API costs, add rate limiting:
# Add to src/ai_handler.py
import time
from collections import defaultdict
class RateLimiter:
def __init__(self, max_requests: int = 20, window_seconds: int = 3600):
self.max_requests = max_requests
self.window = window_seconds
self.requests: dict[int, list[float]] = defaultdict(list)
def is_allowed(self, user_id: int) -> bool:
now = time.time()
# Clean old requests
self.requests[user_id] = [
t for t in self.requests[user_id]
if now - t < self.window
]
if len(self.requests[user_id]) >= self.max_requests:
return False
self.requests[user_id].append(now)
return True
def remaining(self, user_id: int) -> int:
now = time.time()
active = [t for t in self.requests[user_id] if now - t < self.window]
return max(0, self.max_requests - len(active))
Cost Estimate
Typical usage for a personal bot:
- 50 messages/day average
- ~500 input tokens + ~300 output tokens per message
- Using Claude Sonnet
Daily cost: 50 × (500 × $0.003/1K + 300 × $0.015/1K) = $0.30/day
Monthly cost: ~$9/month
With rate limiting (20 msgs/hour per user):
- Very manageable for personal/small group use
- Add user allowlisting for public bots
Next Steps
Once your basic bot is running, consider adding:
- Voice message support — Use Whisper API to transcribe voice messages, then process with Claude
- Document analysis — Accept PDF/DOCX files and answer questions about them
- Group chat support — Respond when mentioned in group chats
- Persistent storage — Use SQLite to maintain conversation history across restarts
- Admin commands — Allow specific users to manage the bot remotely
- Multi-language support — Auto-detect language and respond accordingly
The full code for this tutorial is about 200 lines of Python. That’s all it takes to build a private AI assistant accessible from any device with Telegram installed. No app store approval, no web hosting, no UI design — just a bot that responds to your messages with the full power of Claude behind it.
Sources
> Want more like this?
Get the best AI insights delivered weekly.
> Related Articles
Web Scraping with AI: Build a Smart Data Extraction Pipeline
Traditional web scraping breaks when websites change layouts. AI-powered scraping understands page structure and extracts data intelligently. Here's how to build one using Python, Beautiful Soup, and Claude.
Create an AI Art Portfolio: From Generation to Gallery in One Weekend
Build a professional AI art portfolio website with curated collections, consistent style, and proper attribution. Covers prompt engineering, style consistency, curation, and deployment.
Build an AI Chrome Extension: Add Claude to Any Webpage in 60 Minutes
Build a Chrome extension that summarizes web pages, answers questions about content, and rewrites selected text — all powered by Claude. Full source code and step-by-step instructions included.
Tags
> Stay in the loop
Weekly AI tools & insights.