Telegram-Bot mit lokaler KI
Was dieser Artikel behandelt
- Wie Du einen Telegram-Bot mit der offiziellen Bot-API und Ollama baust.
- BotFather-Setup, Polling vs. Webhooks und Token-Verwaltung.
- Komplette Code-Beispiele mit aiogram (Python).
- Erweiterungen: Inline-Keyboards, Gruppen-Moderation, RAG, Bilder und Sprachnachrichten.
- Docker-Deployment, systemd-Service und Betrieb im Dauerbetrieb.
Einleitung
Telegram ist die bot-freundlichste Plattform überhaupt: Die Bot-API ist kostenlos, offiziell dokumentiert und braucht kein Business-Konto, keine Genehmigung und keine Zertifikate. Einen Bot legst Du in zwei Minuten mit dem @BotFather an, danach kann er Nachrichten empfangen, beantworten und eigene KI-Funktionen ausführen.
Kombiniert mit Ollama bekommst Du einen Chat-Assistenten, der komplett auf Deinem Server läuft: Fragen beantworten, Texte zusammenfassen, Dokumente durchsuchen (RAG), Gruppen moderieren, ohne dass Chat-Inhalte an OpenAI oder andere Cloud-Anbieter gehen.
Typische Anwendungsszenarien
- Persönlicher KI-Assistent: Chatte mit Deinem lokalen LLM vom Handy, von unterwegs, ohne VPN zum Webinterface.
- Community-Support: Beantworte wiederkehrende Fragen in Deiner Telegram-Gruppe automatisch.
- Benachrichtigungs-Bot: Monitoring-Alerts, Backup-Status oder smarte Home-Events direkt in Telegram.
- Dokumenten-Assistent: Schick dem Bot ein PDF, er fasst es zusammen (RAG + OCR).
- Moderation: Spam-Erkennung, Begrüßung neuer Mitglieder, automatische Antworten auf Regelverstöße.
- Eingangskanal für Agenten: Telegram als Frontend für Deine KI-Agenten: Aufgaben per Chat auslösen.
Voraussetzungen
- Telegram-Account
- Server mit Ollama (mindestens ein Modell geladen, z. B.
llama3.1:8b) - Python 3.10+
- Optional: Docker für den Betrieb, Domain + Reverse Proxy für Webhooks
Schritt 1: Bot beim BotFather anlegen
Öffne Telegram und schreibe @BotFather an:
/newbot
BotFather fragt nach Name (Anzeigename, z. B. „Mein KI-Assistent”) und Username (muss auf bot enden, z. B. mein_ki_bot). Du bekommst einen Token:
123456789:AAEhBOweik5ad9rQXMENKJBI...
Den Token niemals in den Code schreiben, immer als Umgebungsvariable oder über ein Secret-Management. Wer den Token hat, kontrolliert den Bot.
Nützliche BotFather-Befehle für später:
/setdescription # Beschreibung im Bot-Profil
/setcommands # Befehlsliste (/start, /hilfe, ...)
/setprivacy # Wichtig für Gruppen: Disable = Bot liest alle Nachrichten
/setjoingroups # Ob der Bot zu Gruppen hinzugefügt werden darf
Schritt 2: Minimaler Bot mit aiogram
aiogram ist das aktuell gepflegte Python-Framework für die Telegram Bot API (async, Type-Hints, Router-Konzept). python-telegram-bot ist die Alternative, beide funktionieren.
pip install aiogram ollama python-dotenv
.env-Datei:
TELEGRAM_TOKEN=123456789:AAEhBOweik5ad9rQXMENKJBI...
OLLAMA_URL=http://localhost:11434
OLLAMA_MODEL=llama3.1:8b
bot.py:
import asyncio
import os
from dotenv import load_dotenv
from aiogram import Bot, Dispatcher, F
from aiogram.filters import Command
from aiogram.types import Message
import ollama
load_dotenv()
bot = Bot(token=os.environ["TELEGRAM_TOKEN"])
dp = Dispatcher()
ollama_client = ollama.Client(host=os.environ["OLLAMA_URL"])
MODEL = os.environ.get("OLLAMA_MODEL", "llama3.1:8b")
SYSTEM_PROMPT = """Du bist ein hilfreicher Assistent auf Telegram.
Antworte kurz und präzise. Maximal 3-4 Absätze.
Nutze einfache Sprache, keine Fachbegriffe ohne Erklärung."""
@dp.message(Command("start"))
async def cmd_start(message: Message):
await message.answer(
"Hallo! Ich bin ein KI-Bot mit lokalem Modell. "
"Schreib mir einfach eine Nachricht oder nutze /hilfe."
)
@dp.message(Command("hilfe"))
async def cmd_help(message: Message):
await message.answer(
"Befehle:\n"
"/start - Bot starten\n"
"/reset - Konversation zurücksetzen\n"
"/modell - Zeigt das aktuelle Modell\n"
"Einfach losschreiben für KI-Antworten."
)
@dp.message(Command("modell"))
async def cmd_model(message: Message):
await message.answer(f"Aktives Modell: `{MODEL}`", parse_mode="Markdown")
# Konversations-Kontext pro User (einfacher In-Memory-Speicher)
conversations: dict[int, list] = {}
@dp.message(Command("reset"))
async def cmd_reset(message: Message):
conversations.pop(message.from_user.id, None)
await message.answer("Konversation zurückgesetzt.")
@dp.message(F.text)
async def handle_message(message: Message):
user_id = message.from_user.id
# "Schreibt..."-Indikator anzeigen
await bot.send_chat_action(message.chat.id, "typing")
# Kontext aufbauen (max. letzte 10 Nachrichten)
history = conversations.setdefault(user_id, [])
history.append({"role": "user", "content": message.text})
history = history[-10:]
try:
response = ollama_client.chat(
model=MODEL,
messages=[{"role": "system", "content": SYSTEM_PROMPT}] + history,
)
answer = response["message"]["content"]
history.append({"role": "assistant", "content": answer})
conversations[user_id] = history
# Telegram limitiert Nachrichten auf 4096 Zeichen
for i in range(0, len(answer), 4000):
await message.answer(answer[i:i + 4000])
except Exception as e:
await message.answer("Fehler beim Generieren der Antwort. Ist Ollama erreichbar?")
print(f"Ollama-Fehler: {e}")
async def main():
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
Starten:
python bot.py
Schreib dem Bot in Telegram eine Nachricht, er antwortet mit dem lokalen Modell. Das war’s: Die einfachste Plattform-Integration überhaupt.
Polling vs. Webhook
| Modus | Wie es funktioniert | Wann nutzen |
|---|---|---|
| Polling | Bot fragt Telegram alle paar Sekunden nach neuen Nachrichten | Entwicklung, Home-Server ohne Domain, hinter NAT |
| Webhook | Telegram pusht Updates an Deinen HTTPS-Endpoint | Produktion mit Domain, hohe Nachrichtenlast, schnellere Antwortzeiten |
Polling ist für Self-Hoster meist ausreichend und deutlich einfacher, kein Reverse Proxy, kein TLS-Zertifikat nötig. Webhook lohnt sich bei vielen Nutzern oder wenn Du mehrere Bots auf einem Server betreibst.
Webhook-Setup mit aiogram:
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
from aiohttp import web
async def main():
await bot.set_webhook(
"https://bots.deine-domain.de/telegram/webhook",
secret_token=os.environ["WEBHOOK_SECRET"],
)
app = web.Application()
SimpleRequestHandler(dispatcher=dp, bot=bot).register(app, path="/telegram/webhook")
setup_application(app, dp, bot=bot)
web.run_app(app, host="127.0.0.1", port=8080)
Dazu ein Reverse Proxy (Nginx/Caddy) mit TLS, siehe Reverse Proxy.
Erweiterungen
Inline-Keyboards: Buttons unter Nachrichten
Keyboards eignen sich für Auswahlmöglichkeiten, etwa „Modell wechseln” oder „Ergebnis bewerten”:
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.filters import Command
@dp.message(Command("modellwahl"))
async def cmd_modellwahl(message: Message):
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Llama 3.1 (8B)", callback_data="model:llama3.1:8b")],
[InlineKeyboardButton(text="Qwen2.5 (14B)", callback_data="model:qwen2.5:14b")],
[InlineKeyboardButton(text="DeepSeek-R1 (8B)", callback_data="model:deepseek-r1:8b")],
])
await message.answer("Welches Modell?", reply_markup=keyboard)
@dp.callback_query(F.data.startswith("model:"))
async def model_chosen(callback):
model = callback.data.split(":")[1]
user_models[callback.from_user.id] = model
await callback.message.edit_text(f"Modell gesetzt: `{model}`", parse_mode="Markdown")
await callback.answer()
Gruppen-Betrieb und Moderation
Damit der Bot in Gruppen alle Nachrichten liest (nicht nur /commands), muss im BotFather /setprivacy auf Disable stehen. Danach:
@dp.message(F.text & F.chat.type.in_({"group", "supergroup"}))
async def group_handler(message: Message):
# Nur antworten, wenn der Bot erwähnt wird
if f"@{BOT_USERNAME}" in message.text:
await handle_message(message)
return
# Einfache KI-Moderation: Nachricht klassifizieren lassen
check = ollama_client.chat(
model=MODEL,
messages=[{
"role": "user",
"content": f"Klassifiziere diese Nachricht als OK oder SPAM. "
f"Antworte nur mit einem Wort.\n\n{message.text}"
}],
)
if check["message"]["content"].strip().upper() == "SPAM":
await message.delete()
await message.answer(f"@{message.from_user.username}: Nachricht entfernt.")
RAG: Bot beantwortet Fragen aus Deinen Dokumenten
Der Bot durchsucht eine Vektordatenbank, bevor er antwortet. Aufbau wie in Lokales RAG:
from qdrant_client import QdrantClient
qdrant = QdrantClient(host="localhost", port=6333)
def retrieve_context(query: str) -> str:
embedding = ollama_client.embeddings(model="bge-m3", prompt=query)["embedding"]
hits = qdrant.search(collection_name="dokumente", query_vector=embedding, limit=3)
return "\n\n".join(h.payload["text"] for h in hits)
@dp.message(F.text)
async def handle_message(message: Message):
context = retrieve_context(message.text)
response = ollama_client.chat(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT +
f"\n\nNutze diesen Kontext zur Antwort:\n{context}"},
{"role": "user", "content": message.text},
],
)
await message.answer(response["message"]["content"])
Bilder und Sprachnachrichten
Telegram liefert Dateien über getFile/download. Sprachnachrichten lassen sich an Whisper schicken, Bilder an ein Vision-Modell wie llava oder minicpm-v:
@dp.message(F.voice)
async def handle_voice(message: Message):
file = await bot.get_file(message.voice.file_id)
path = await bot.download_file(file.file_path)
text = whisper_transcribe(path.name) # lokales Whisper
await message.answer(f"Verstanden: „{text}“\n\n")
# danach wie normale Textnachricht behandeln
@dp.message(F.photo)
async def handle_photo(message: Message):
file = await bot.get_file(message.photo[-1].file_id)
path = await bot.download_file(file.file_path)
response = ollama_client.chat(
model="minicpm-v",
messages=[{
"role": "user",
"content": "Beschreibe dieses Bild.",
"images": [path.name],
}],
)
await message.answer(response["message"]["content"])
Streaming-Antworten
Ollama kann streamen; Telegram-Nachrichten lassen sich nachträglich editieren:
sent = await message.answer("…")
text = ""
async for chunk in ollama_client.chat(model=MODEL, messages=msgs, stream=True):
text += chunk["message"]["content"]
if len(text) % 80 == 0: # nicht jedes Token senden (Rate-Limit!)
await sent.edit_text(text)
await sent.edit_text(text)
Betrieb: Docker und systemd
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY bot.py .
CMD ["python", "bot.py"]
docker-compose.yml (Bot + Ollama + Qdrant im selben Stack):
services:
telegram-bot:
build: .
restart: always
env_file: .env
depends_on:
- ollama
networks: [botnet]
ollama:
image: ollama/ollama:latest
restart: always
volumes:
- ollama_data:/root/.ollama
networks: [botnet]
# deploy.resources für GPU siehe Docker-Artikel
volumes:
ollama_data:
networks:
botnet:
Wichtig: In .env dann OLLAMA_URL=http://ollama:11434: Container erreichen sich über Service-Namen, nicht localhost.
Sicherheit und Datenschutz
- Token-Schutz: Token in
.env/Secrets, nie im Git-Repo. Bei Leak beim BotFather/revoke. - Zugriffsbeschränkung: Erlaube nur bestimmte User-IDs, sonst kann jeder Deinen Bot (und Dein Ollama) nutzen:
ALLOWED_USERS = {123456789} # Deine Telegram-User-ID
@dp.message(F.text)
async def handle_message(message: Message):
if message.from_user.id not in ALLOWED_USERS:
return
# ...
- Datenschutz-Hinweis: Nachrichten gehen an Telegrams Server (Client-Server-verschlüsselt, nicht E2E). Für wirklich vertrauliche Chats eignen sich Matrix-Bots oder Signal-Bots besser.
- Rate-Limiting: Telegram erlaubt ~30 Nachrichten/Sekunde; bei Streaming-Edits Drossel einbauen.
- Kostenkontrolle: Jeder Aufruf kostet Rechenzeit auf Deinem Server, bei öffentlichen Bots Limits pro User setzen. Siehe Kostenkontrolle.
Typische Stolpersteine
- Bot antwortet in Gruppen nicht:
/setprivacyauf Disable stellen, Bot neu zur Gruppe hinzufügen. - 409 Conflict beim Start: Eine zweite Instanz pollt denselben Token, alte Prozesse/Container killen.
- Nachricht zu lang: Telegram-Limit 4096 Zeichen: Antworten splitten (siehe Code oben).
- Markdown-Fehler:
parse_mode="Markdown"wirft Fehler bei unbalancierten Zeichen aus dem LLM, zur Notparse_mode=Noneoder Fehler abfangen. - Speicher wächst: In-Memory-Kontext pro User nie begrenzen → Langzeitbetrieb bläht RAM auf. Limit setzen (siehe Code) oder auf Redis/DB auslagern.
Weiterführende Links
- IRC-Coding.de: Vertiefende Programmier-Tutorials: Bot-Architekturen, Python-Async, Event-Handling und weitere Bot-Projekte.
- Chatbot-Grundlagen: Regelbasiert vs. KI, Agenten vs. Chatbots.
- Discord-Bot Grundlagen: Alternative Plattform.
- Ollama API: Die HTTP-Schnittstelle im Detail.
- Lokales RAG: Dokumenten-Wissen für den Bot.
- Docker: Container-Grundlagen.
Key Takeaways:
- Telegram ist die einfachste Bot-Plattform: BotFather → Token → aiogram → fertig.
- Polling reicht für Self-Hoster; Webhook nur bei Last oder mehreren Bots.
- Erweiterungen: Inline-Keyboards, Gruppen-Moderation, RAG, Vision, Whisper für Sprache.
- Zugriff auf eigene User-IDs beschränken, Token als Secret behandeln.
- Nicht E2E-verschlüsselt, für vertrauliche Chats besser Matrix oder Signal.
FAQ
Kostet ein Telegram-Bot etwas?
Polling oder Webhook?
Warum antwortet der Bot in Gruppen nicht?
aiogram oder python-telegram-bot?
Können mehrere Nutzer den Bot gleichzeitig nutzen?
Wie merkt sich der Bot den Kontext?
Sind die Chats privat?
Kann der Bot in Kanälen posten?
Quellen und weiterführende Literatur
- Telegram Bot API: Offizielle Referenz.
- aiogram: Python-Framework-Dokumentation.
- Ollama: Lokaler Modellserver.
- IRC-Coding.de: Programmier-Tutorials für Bot-Entwicklung.


