Hotel AI Concierge
Ein Telegram-Concierge, der Gäste in vier Sprachen mit den Fakten des Hotels beantwortet — und alles andere an die Rezeption weitergibt.
- Rolle
- Eigenes Projekt — Konzept, Code, Tests
- Jahr
- 2026
- Kategorie
- KI · Bots
- Technologien
- Python 3.12, aiogram 3, Anthropic API, OpenAI API

Das Problem
Die Rezeption beantwortet den ganzen Tag dieselben Fragen: Frühstückszeiten, das WLAN-Passwort, wann der Pool öffnet, wie man einen Transfer bucht. In einem Ferienhotel in der Türkei fragen Gäste auf Russisch, Englisch, Türkisch und Deutsch, und das Personal spricht nicht alle vier Sprachen.
Ein einfacher Chatbot ist hier riskant. Eine KI, die eine Check-out-Zeit oder einen Preis erfindet, sorgt an der Rezeption für echten Ärger, und der Gast gibt dem Hotel die Schuld, nicht dem Bot.
Die Lösung
Ein Telegram-Bot, der nur mit Fakten antwortet, die das Hotel aufgeschrieben hat, und bei allem anderen einen Menschen fragt:
- Die Wissensbasis ist eine Markdown-Datei, die das Hotel selbst pflegt.
/reloadübernimmt Änderungen ohne Neustart; eine fehlerhafte Datei lässt die alte Version in Betrieb. - Das Sprachmodell (Claude oder OpenAI, in den Einstellungen gewählt) liefert strukturiertes JSON mit dem Feld
found_in_knowledge_base. „Weiß ich das?" ist damit ein Ja oder Nein und keine Vermutung aus der Formulierung des Modells. - Unbekannte Fragen gehen mit englischer Übersetzung an eine Telegram-Gruppe des Personals. Das Personal tippt einfach auf „Antworten"; der Bot übersetzt die Antwort in die Sprache des Gastes und zeigt das Original darunter.
- Die Sprache des Gastes wird ohne KI erkannt: Alphabet, Buchstaben wie ı ş ğ oder ä ß und häufige Wörter. Kurze Nachrichten wie „ok" behalten die Sprache des Gesprächs.
- Jeder Fehler endet bei einem Menschen: API nicht erreichbar, Ablehnung durch das Modell oder abgeschnittene Antwort gehen ans Personal; ist die Gruppe nicht erreichbar, bittet der Bot den Gast, an der Rezeption zu fragen.
Demo
Screenshots

Technologien
- Python 3.12
- aiogram 3
- Anthropic API
- OpenAI API
- SQLite
- pytest
- Docker
Statt einer Vektorsuche geht die ganze Wissensbasis in den Prompt: Ein Hotel-FAQ umfasst ein paar tausend Token, eine Suche würde nur eine neue Fehlerquelle hinzufügen. Weil sich dieser Prompt zwischen Gästen nicht ändert, speichert Anthropic ihn im Cache, und wiederholte Anfragen kosten weniger. Ein Offline-Modell als Attrappe lässt den ganzen Ablauf ohne API-Schlüssel laufen.
Code im Detail
Die zentrale Entscheidung ist unabhängig von Telegram, deshalb laufen Bot, Terminal-Demo und Tests mit demselben Code:
async def handle_guest_message(self, guest: Guest, text: str) -> GuestReply:
language = await self.guest_language(guest, text)
history = format_history(
await self._repo.recent_messages(guest.chat_id, self._settings.history_messages)
)
await self._repo.upsert_guest(guest.chat_id, guest.name, guest.username, language)
await self._repo.log(guest.chat_id, Role.GUEST, Kind.QUESTION, text, language)
result: ConciergeAnswer | None = None
try:
result = await self._llm.answer(text, language, self._knowledge.text, history)
except LLMError as exc:
logger.warning("LLM failed, escalating to staff: %s", exc)
if result and result.found_in_knowledge_base and result.answer.strip():
answer = result.answer.strip()
await self._repo.log(guest.chat_id, Role.BOT, Kind.ANSWER, answer, language)
return GuestReply(language, answer)
# Not in the knowledge base, or the AI is unavailable: never guess, ask a human.
reason = "not in the knowledge base" if result else "AI assistant unavailable"
question_en = result.question_in_english if result else text
escalation_id = await self._repo.create_escalation(
guest.chat_id, language, text, question_en, reason
)
...Ein Fehler, den ich gefunden und behoben habe
Befehle von Gästen gingen als Fragen an die KI und an die Rezeption
Was kaputt war
Nichts stürzte ab, und der Test war grün. Beim Schreiben eines End-to-End-Tests für den
Admin-Befehl /reload verfolgte ich, was passiert, wenn ein Gast ihn schickt. Der Bot bezahlte
einen KI-Aufruf, fand nichts in der Wissensbasis und schickte „/reload" als Gastfrage an die
Personalgruppe. Dasselbe passierte mit /help und jedem anderen Befehl.
Ursache
aiogram prüft die Handler in der Reihenfolge ihrer Registrierung und nimmt den ersten, dessen Filter passen. Der Handler nur für Admins ignorierte den Gast korrekt, also fiel die Nachricht bis zum letzten Handler durch, der jeden Text als Frage behandelt.
Die Lösung
Vor diesem Handler steht jetzt einer für Befehle in privaten Chats, der den Begrüßungstext zeigt. Der Test prüft nun beide Seiten: Der Gast bekommt die Begrüßung, und in der Personalgruppe kommt nichts an. Lehre: Ein Test, der nur prüft „das Schlechte ist nicht passiert", kann aus dem falschen Grund grün sein.
router.message.register(on_staff_reply, IsStaffChat(), F.reply_to_message, F.text)
router.message.register(on_guest_text, private, F.text)
router.message.register(on_guest_other, private)router.message.register(on_staff_reply, IsStaffChat(), F.reply_to_message, F.text)
# /help or a command the guest may not use: show the welcome text, do not ask the LLM.
router.message.register(cmd_start, private, F.text.startswith("/"))
router.message.register(on_guest_text, private, F.text)
router.message.register(on_guest_other, private)Ergebnis
- 68 automatische Tests, darunter ein Durchlauf durch den Dispatcher von aiogram mit einer simulierten Telegram-Verbindung und Tests der Claude- und OpenAI-Clients gegen einen simulierten HTTP-Server.
- Die Offline-Demo beantwortet vier Gäste in vier Sprachen, gibt die fünfte Frage ans Personal und liefert dessen Antwort zurück.
- Läuft im Demo-Modus ganz ohne API-Schlüssel.