Minimal but complete Telegram bot with Claude tool use. Clone, configure, launch.
3 demo tools included: save_fact, create_reminder, get_reminders — demonstrating write, time-based, and read patterns.
Telegram Bot Token:
- Open Telegram, message @BotFather
- Send
/newbot, follow prompts (pick a name and username) - Copy the token (looks like
123456789:ABCdefGHIjklMNOpqrSTUvwxYZ)
Anthropic API Key:
- Go to console.anthropic.com
- Create an account or sign in
- Go to API Keys → Create Key
- Copy the key (starts with
sk-ant-)
cp .env.example .envEdit .env and fill in:
TELEGRAM_BOT_TOKEN=your-token-from-botfather
ANTHROPIC_API_KEY=sk-ant-your-key
Option A — Local (Python 3.10+):
pip install -r requirements.txt
python -m app.mainOption B — Docker:
docker compose up --build- Open your bot in Telegram
- Send
/start - Say "remember that I like coffee" → bot saves a fact
- Say "remind me in 5 minutes to take a break" → bot sets a reminder
- Say "show my reminders" → bot lists pending reminders
- Wait 5 min → bot sends the reminder proactively
┌─────────────┐ ┌───────────────────┐ ┌─────────┐
│ Telegram │ polling │ app/ │ │ Claude │
│ (user) ├────────►│ handlers.py │ │ API │
│ │◄────────┤ chat.py ├────────►│ (tools) │
└─────────────┘ reply │ tools.py │◄────────┤ │
└─────────┬─────────┘ └─────────┘
│
┌─────────▼─────────┐
│ SQLite (aiosqlite)│
│ users, facts, │
│ reminders, │
│ conversations │
└───────────────────┘
User: "remember that I like hiking"
→ handlers.py receives message
→ chat.py builds context (facts + reminders + last 10 messages)
→ Claude API call with tools
→ Claude decides: tool_use → save_fact(category="hobby", fact="likes hiking")
→ chat.py executes tool → INSERT INTO facts
→ Claude receives result → generates text response
→ handlers.py sends reply to Telegram
| File | Purpose |
|---|---|
app/main.py |
Entry point — starts bot + reminder scheduler |
app/config.py |
Settings from environment variables |
app/database.py |
Async SQLAlchemy + SQLite setup |
app/models.py |
DB models: User, Fact, Reminder, Conversation |
app/chat.py |
Claude API call with tool use loop |
app/tools.py |
Tool definitions (JSON Schema) |
app/handlers.py |
Telegram handlers: /start, messages, callbacks |
- SQLite via aiosqlite — zero config. Swap to PostgreSQL by changing
DATABASE_URL - Polling not webhook — works locally without a domain
- Conversation history stored in DB, last 10 messages loaded per Claude call
- Reminder scheduler checks every 60 seconds, sends due reminders proactively
- System prompt configurable via
.env
Add to app/tools.py:
TOOLS = [
# ... existing tools ...
{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name",
},
},
"required": ["city"],
},
},
]Add to app/chat.py inside _execute_tool():
elif name == "get_weather":
city = params["city"]
# Call a weather API, query DB, or compute anything
weather = await fetch_weather(city)
return json.dumps({"city": city, "temp": weather.temp, "condition": weather.condition})Claude reads the tool's description and input_schema and decides when to call it. No routing logic needed — Claude figures out intent from the conversation.
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | — | From @BotFather |
ANTHROPIC_API_KEY |
Yes | — | From console.anthropic.com |
BOT_NAME |
No | Assistant |
Bot display name |
SYSTEM_PROMPT |
No | Generic assistant | Claude's system prompt |
CLAUDE_MODEL |
No | claude-sonnet-4-5-20250929 |
Claude model ID |
DATABASE_URL |
No | sqlite+aiosqlite:///data/bot.db |
SQLAlchemy async URL |
pip install asyncpgIn .env:
DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/botdb
No code changes needed — SQLAlchemy handles the rest.
| Layer | Technology |
|---|---|
| AI | Claude API (Anthropic Python SDK) |
| Bot | python-telegram-bot v20+ |
| DB | SQLAlchemy 2.x async + SQLite |
| Scheduler | APScheduler |
| Deploy | Docker Compose |