Voice-Bot Substanz-Iteration — Knowledge-Base + Verfuegbarkeit + Confirm-Loop
Overview
Sub-Plan zum Master-Plan 2026-05-13-001. Detailliert die Master-Units 2 (Knowledge-Base + Tool-Splitting), 3 (Verfuegbarkeits-Check) und 4 (Confirm-Loop) in 6 implementierbare Sub-Units. Zerlegt die im Master schon entschiedenen Architektur-Pfaeder so dass ein Implementer (Marvin + Claude in ce:work) ohne Re-Planning direkt loslegen kann.
Stand vor diesem Plan (2026-05-13 Abend, nach Live-Test):
- Master-Units 1 (Telegram-Push) + 5 (Eskalation) implementiert + Live-getestet
- Live-Fix: Multi-Lang (DE/TR/AR/KU/RU/PL/IT/FR) Code-Switching +
end_call-Tool - Marvin’s Test-Feedback: „Das ist schon ziemlich gut” — Bot redet sauber, eskaliert, verabschiedet sich, ist mehrsprachig. Halluziniert aber bei Speisekarten-Fragen offensichtlich („Karte wechselt regelmaessig” — frei erfunden, siehe _index.md §Aylem-Test-Outputs).
Was sich aendert: Bot bekommt eine echte Knowledge-Base (menu.md, hours.md, policies.md, faq.md) und 4 neue Tools (search_knowledge + 3 Convenience-Tools get_menu/get_hours/get_allergens). Verfuegbarkeits-Check verhindert Buchungen ausserhalb Oeffnungszeit. Confirmation-Loop verhindert dass STT-Verhoerer ungeprueft in die DB wandern.
Was sich nicht aendert: Stack, Tools 1/5 (Push/Eskalation), Multi-Lang-Verhalten, end_call, DSGVO-Stand. Master-Plan-Decisions bleiben — dieser Sub-Plan operationalisiert nur was dort schon entschieden ist.
Problem Frame
Aus Live-Test 2026-05-13 — drei konkrete UX-Defekte:
- Halluzinations-Problem (Menu/Preise/Allergene): Bot antwortet auf „Was kostet ein Doener-Teller?” oder „Habt ihr was Veganes?” aus dem LLM-Pretraining + System-Prompt-Improvisation, nicht aus echten Restaurant-Daten. Glaubwuerdigkeitskiller in Sales-Demo.
- Blind-Buchungs-Problem (Verfuegbarkeit): Bot ruft
book_reservationohne zu pruefen ob das Restaurant zur gewuenschten Zeit ueberhaupt offen ist. Anrufer kann „Tisch am Montag” buchen obwohl Doener-Restaurant typisch Montag-Ruhetag hat. - STT-Vertrauens-Problem (Confirm-Loop): Bot uebernimmt STT-Output 1:1 in
book_reservation-Args. Bei Verhoerer „50 statt 15 Personen” landet das ungeprueft in der DB. Master-Plan-Unit 4 hat das spezifiziert — hier wird es operationalisiert.
Quelle dieses Sub-Plans: Master-Plan-Units 2/3/4 (schon im document-review-pass gehaertet) plus zwei Live-Test-Iterationen mit gezielten Test-Calls.
Requirements Trace
- R1. Bot beantwortet Menu-/Preis-Fragen ausschliesslich aus
knowledge/aylem/menu.md, niemals aus Pre-Training. Verifizierbar via Conversation-Log: jeder Menu-Antwort geht ein Tool-Callget_menuodersearch_knowledgevoraus. - R2. Bot beantwortet Oeffnungszeiten-Fragen aus
knowledge/aylem/hours.md. Versteht relative Anfragen („Habt ihr morgen offen?” → Datum aus Date-Context + Tag-of-week-Lookup). - R3. Bot beantwortet Allergen-Fragen aus
menu.md(Allergene pro Gericht) oderfaq.md. Bei „ich habe Nussallergie” → konkrete Antwort welche Gerichte safe sind, kein generisches „rufen Sie an”. - R4. Bot ruft
check_availability(date, time, guests)vor jedembook_reservationauf. Bei nicht-verfuegbar antwortet er hilfreich („Montags haben wir leider Ruhetag — Dienstag oder Donnerstag ist frei”) statt zu buchen. - R5. Bot wiederholt vor jedem
book_reservationundtake_orderdie gesammelten Slots zur Bestaetigung und ruft das Tool nur nach explizitem „ja/passt/stimmt” auf. Bei Schweigen: nicht auto-buchen, sondern nachfragen oder eskalieren. - R6. Bei
take_ordermit mehr als 5 Items chunked Bot die Bestaetigung in 2 Bloecke („Erst die Hauptgerichte: …, passt das? Und an Beilagen/Getraenken: …, passt das auch?”). - R7. System-Prompt erzwingt Tool-Order: bei KB-Fragen ZUERST Knowledge-Tool, bei Reservierung ZUERST
check_availability, vor Buchung ZUERST CONFIRM-State. Reihenfolge wird in der Eval-Dimension „Tool-Correctness” (Master-Plan Unit 7) gemessen.
Scope Boundaries
In Scope:
- 4 neue Markdown-Files unter
intern/projekte/telefon-assistent-aws/tools-mcp/knowledge/aylem/(menu, hours, policies, faq) - 5 neue MCP-Tools in
tools-mcp/server.py:search_knowledge,get_menu,get_hours,get_allergens,check_availability(plus 1 Refactor:get_servicesdelegiert intern anget_menu) - System-Prompt-Erweiterung in
livekit-agent/agent.py(Tool-Discipline + Confirm-Loop-Block + Multi-Intent + LMIV-Klartext-Pflicht) - Knowledge-Loader-Modul
_knowledge.pyals stdlib-only Helper inkl. LMIV-Code-zu-Klartext-Mapping - Tool-Liste nach Sub-Plan: 9 MCP-Tools (4 existing: book_reservation, take_order, escalate_to_human, get_services; 5 neu wie oben).
end_calllebt LOKAL im livekit-agent (kein MCP-Tool), nicht mitgezaehlt.
Out of Scope (explizit; alles im Master-Plan oder Folge-Plan abgedeckt):
- Echte API-Anbindung an OrderStork / TastyIgniter / GloriaFood (Master-Plan-Open-Loop)
- Multi-Tenant (Master-Unit 11 — eigener Sub-Plan oder eigene Iteration)
- DSGVO-Pre-Flight + Loesch-CLI + AVV (Master-Unit 12 — eigener Sub-Plan)
- Conversation-Logging in JSONL (Master-Unit 6)
- Claude-as-Judge-Eval (Master-Unit 7)
- System-Prompt-Restruktur in 7 Sektionen (Master-Unit 8 — die Confirm-Loop-Aenderung hier ist eine Vorstufe)
- STT-Confidence-Fallback / Senior-Pattern (Master-Sub-Unit 4b — Folge-Iteration)
- Synthetic Test-Personas (Master-Plan-Out-of-Scope)
- Demo-Pack (Master-Unit 13)
Context & Research
Relevant Code and Patterns
- server.py — heutiger MCP-Server. Pattern fuer neue Tools:
@mcp.tool() async def name(...) -> strmit DB-Insert + Telegram-Push + Logging. Bei diesem Sub-Plan: Knowledge-Tools haben keinen DB-Insert, aber Logging + Push (nur bei Confirm-Trigger-Eskalationen). - _telegram.py — Vorbild fuer den neuen
_knowledge.py-Helper (stdlib-only, swallowing errors, sauberes Logging). - agent.py —
SYSTEM_PROMPT_TEMPLATEist der Erweiterungspunkt. Aktuell hat es 3 Blocks (Persona, Mehrsprachigkeit, Eskalation) — der neue Block „Tool-Discipline + Confirm-Loop” wird vor „Mehrsprachigkeit” eingefuegt damit Tool-Order vor Sprach-Switch geklaert ist. - voice-bot-de-pattern.md — Anti-IVR-Phrasen-Liste, Spoken-German-Block. Wird in den Tool-Discipline-Block gegen Banned-Phrases verlinkt.
Institutional Learnings
- Knowledge-RAG ist Greenfield im Vault — keine bestehende Implementation zum Wiederverwenden. Erste Implementation. Wenn das Pattern gut funktioniert: kandidat fuer voice-bot-de-pattern.md Erweiterung.
- Telegram-Push-Pattern (Phase 1, Unit 1) hat Cross-Repo-Bindungs-Problem gezeigt —
agentic_common.telegramwar Lambda-Layer, nicht pip-installable. Sub-Plan zieht Konsequenz:_knowledge.pyebenfalls stdlib-only, kein Import aus anderen Repos. - DB-Schema-Freeze ist seit Master-Plan-Operational-Note aktiv — Sub-Plan aendert keine Tabellen, nur Reads aus Knowledge-Files.
External References
Bereits im Master-Plan zitiert, hier nur die fuer diese Iteration relevanten:
- GetStream Restaurant RAG Pattern — Vorbild fuer Markdown-Files unter
knowledge/, Tool delegiert an Retrieval. Wir nehmen das Pattern, aber ohne Turbopuffer (Substring + DE-Synonyme reichen fuer <10 kB). - Kwindla Advice on Voice Agents §Tool-Splitting — bestaetigt unseren Master-Plan-Entscheidung: spezifische Tools (
get_menu,get_hours) statt eines generischen Search-Tools sind robuster. - LMIV (EU Lebensmittel-Informations-Verordnung) — Pflichtangaben Allergene. Beeinflusst Inhalt von
menu.md(alle 14 EU-Allergene pro Gericht annotieren).
Key Technical Decisions
- Knowledge-Files unter
tools-mcp/knowledge/aylem/, nicht im Vault unterintern/kunden/aylem/. Begruendung: Knowledge-Files werden vom MCP-Subprocess gelesen — Pfad-Auflosung relativ zum MCP-Verzeichnis. Vault-Konvention waere semantisch sauberer, aber praktisch unguenstig. Plus:tools-mcp/ist.gitignored(Vault-Pattern fuer Code-Subprojekte) — Knowledge-Files leben mit dem Code zusammen. hours.md-Format: YAML-Frontmatter mitweekly_hours-Dict + optionalholidays-Liste. Begruendung: strukturierte Daten = strukturierter Parser = robuste Verfuegbarkeit-Logic. Markdown-Tabellen waeren menschen-lesbarer aber Parser-anfaelliger. Beispiel-Struktur siehe High-Level Technical Design.menu.md-Format: Markdown mit Headings pro Kategorie + Frontmatter mit Tenant-Meta. Begruendung: jede Kategorie (Hauptgerichte / Beilagen / Getraenke / Vegetarisch) ist eine Section, Heading-basierter Chunker zerlegt automatisch. Allergene pro Gericht inline als „[Allergene: A, B, F]“-Notation.- Section-Retrieval: Substring + DE-Synonym-Liste, kein BM25. Begruendung: Master-Plan-Decision. Mit 4 Files je 1–3 kB ist Substring schnell und ausreichend. Synonym-Liste wird beim Schreiben iterativ aufgebaut (Test-Queries → Misses identifizieren → Synonyme ergaenzen).
check_availabilityreturnt strukturiertes Dict{"available": bool, "reason": str, "alternative_suggestion": str}(als JSON-String fuer MCP-Kompatibilitaet). Begruendung: Bot soll selbst die Anrufer-Antwort formulieren (mehrsprachig!), nicht das Tool eine fertige Phrase mitliefern. Tool gibt Rohdaten + Begruendung, Bot baut die Antwort.- Confirmation-Loop als System-Prompt-Pattern, nicht als Tool. Begruendung: Master-Plan-Decision. CONFIRM-State ist Konversations-Mechanik, kein Backend-Vorgang. Kein neues Tool noetig — nur Prompt-Block.
- Fail-Behavior bei Parser-/Read-Fehlern (differenziert). Begruendung: nicht alle „Fehler” sind gleich, Pauschal-fail-open ist gefaehrlich (siehe security + adversarial Review). Differenzierung:
- hours.md komplett unlesbar oder nicht vorhanden (Setup-Fehler, kein Buchungs-Risk):
{"available": true, "reason": "Verfuegbarkeit konnte nicht geprueft werden, Team meldet sich"}+ log.exception + Telegram-Push miturgent=Truean Restaurant. Selbst dann: kein book_reservation ohne explizite User-Confirm. - hours.md parsbar aber requested weekday closed oder Holiday-Treffer: harter
{"available": false, ...}— KEIN fail-open. - Datum oder Time-String unparsbar:
{"available": false, "reason": "Datum konnte ich nicht verstehen"}— Bot fragt nach. - realistic-mock-Daten + Holiday-Liste unvollstaendig: bekanntes Risiko (Mock hat nicht alle Feiertage). Mitigation: wenn
inhalt_status: realistic-mock, Bot-System-Prompt zwingt vorbook_reservationzusaetzlich „Bitte bestaetigen Sie nochmal das Datum”-Confirm-Pass (siehe Unit 5 Approach).
- hours.md komplett unlesbar oder nicht vorhanden (Setup-Fehler, kein Buchungs-Risk):
- LMIV-Allergen-Codes zentral in
_knowledge.pyzu Klartext aufloesen. Begruendung: User-Feedback aus design-Review — „Allergene A, G” am Telefon ist unverstaendlich. _knowledge.py haelt das LMIV-Code-Mapping (A=Gluten, G=Milch, ...), alle Tools (search_knowledge, get_menu, get_allergens) returnen IMMER Klartext (enthaelt Gluten und Milch). Codes erscheinen NIE im Tool-Output und damit NIE in Voice-TTS. - Knowledge-Files-Trust-Modell: operator-Level. Begruendung: Knowledge-Files werden nicht von Anrufern editiert. Trust-Boundary verlaeuft zwischen Anrufer-Input (untrusted) und Knowledge-File-Inhalt (trusted). Bei Multi-Tenant-Erweiterung (Master-Unit 11) muss diese Annahme neu gepruef werden — dort kann der Kunde sein eigenes Knowledge editieren, dann ist Trust-Level
untrusted operator. In diesem Sub-Plan: Single-Tenant, Marvin editiert allein, Risiko niedrig. visibility: public-kundeals Pflicht-Frontmatter-Feld auf allen Knowledge-Files. Begruendung: Vault-CLAUDE.md-Rule 7 (Internes nie in Kunden-Export). Knowledge-Files gehen ueber Voice-Output an Anrufer = Kunden-Export. Loader-Code verweigert das Laden eines Files ohnevisibility: public-kunde(fail-closed).get_serviceswird Wrapper umget_menu. Begruendung: Backward-Compatibility mit bestehendem System-Prompt (Master-Plan-Unit-2-Approach hat das auch so). Wenn das Tool aufgerufen wird, returnt es Menu-Inhalt — fuer alte Tool-Aufrufe transparent.
Open Questions
Resolved During Planning
- Format hours.md / menu.md → YAML-Frontmatter + Markdown-Sections (siehe Decisions).
- Section-Retrieval-Algorithmus → Substring + Synonyme, kein BM25 (Master-Plan-Decision, hier bestaetigt).
- Wo Knowledge-Files liegen →
tools-mcp/knowledge/<tenant>/(siehe Decisions). check_availabilityReturn-Format → JSON-String mit Dict-Inhalt (siehe Decisions).
Deferred to Implementation
- Konkrete Aylem-Speisekarten-Items + Preise. Marvin hat „keine Aylem-Telefonate”-Annahme. Quellen fuer Inhalt: (a) aylem.orderstork.com Public-Menu, (b) typische DACH-Doener-Restaurant-Karten als Default. Inhalt wird beim Schreiben des Files realistisch gefuellt — Marvin kann selbst nach-editieren. Implementer-Disclaimer im Frontmatter:
inhalt_status: realistic-mockdamit klar ist dass die Karte nicht von Aylem freigegeben ist. - Deutsche Synonym-Liste fuer
search_knowledge. Initialer Startsatz (knapp, iterativ erweiterbar):oeffnungszeit ↔ öffnungszeit ↔ öffnungszeiten ↔ geoeffnet,karte ↔ speisekarte ↔ menü,preis ↔ kostet ↔ kosten ↔ EUR ↔ Euro,allergie ↔ allergen,vegan ↔ vegetarisch ↔ fleischlos. Plus ae/oe/ue ↔ ä/ö/ü als generische Regel. EUR/Euro im Preis-Synonym-Set ist zwingend, weil menu.md die Codes „12.50 EUR” enthaelt und der Substring-Score sonst kein Match auf „was kostet” findet (siehe adversarial F2). Weitere Synonyme erst nach realen Test-Queries — Test-Iteration-Aufwand fuer iterative Erweiterung: ~10 s pro Patch (Worker-Restart noetig, da Liste Code-Konstante). - Konkrete Aylem-Oeffnungszeiten. Default-Annahme: Montag Ruhetag, Di–Do 11:30–22:30, Fr 11:30–23:00, Sa 12:00–23:00, So 12:00–22:00. Bei realer Aylem-Pilotierung (Folge-Plan) korrigieren.
- Wo wird die Synonym-Liste verwaltet — Code oder Daten-File? Default: als Konstante in
_knowledge.py(Code). Wenn sich zeigt dass pro Tenant unterschiedlich noetig: nachknowledge/<tenant>/_synonyms.yamlmigrieren (Master-Unit-11-Multi-Tenant-Iteration). Konsequenz fuer Sub-Plan-Iteration: Jeder Synonym-Edit kostet Worker-Restart (~5–10 s). Plant 10 s pro Patch ein wenn Sie 20+ Synonym-Adjustments brauchen. - Section-Chunker:
##vs###Hierarchie. Default: Chunker splittet primaer auf##(Top-Level-Kategorien).###-Sub-Headings bleiben innerhalb der##-Section sichtbar. Fuerget_allergensfaehrt ein zusaetzlicher Sub-Chunker auf###der nur innerhalb der##-Section sucht. Score aufsection_text = heading + "\n" + body(Heading + Body konkateniert, Heading nicht extra gewichtet — einfacher, vorhersagbarer). Konkrete Implementation in Unit 2. - Score-Floor fuer
search_knowledge: wenn Top-1-Score == 0 (keine Substring/Synonym-Treffer), returnt das Tool den Default-String"Zu deiner Frage habe ich nichts Spezifisches gefunden. Bitte praezisier oder ich leite an unser Team weiter."statt random Top-3-Sections. Verhindert dass der LLM unfundiert Material reininterpretiert. Konkrete Schwelle wird beim Schreiben des Scoring-Codes festgelegt — vermutlich Top-1-Match-Count >= 1.
High-Level Technical Design
Das illustriert die intendierte Architektur und ist directional guidance fuer Review, nicht Implementation-Spec.
Tool-Call-Sequence: „Was kostet ein Doener-Teller?”
sequenceDiagram participant U as Anrufer participant B as Bot (Haiku 4.5 EU) participant M as tools-mcp Subprocess participant K as knowledge/aylem/ U->>B: "Was kostet ein Doener-Teller?" Note over B: System-Prompt-Regel:<br/>Menu-Frage -> get_menu zuerst B->>M: get_menu(category="doener-teller") M->>K: load menu.md, chunk by heading K-->>M: section "Doener-Teller" M-->>B: "Doener-Teller mit Lamm: 12.50 EUR, scharf optional. Allergene: A, G." B->>U: "Der Doener-Teller mit Lamm kostet zwoelf-fünfzig."
hours.md-Format (Beispiel-Sketch)
---
tenant: aylem
inhalt_status: realistic-mock
visibility: public-kunde
last_review: 2026-05-13
weekly_hours:
monday: closed
tuesday: ["11:30-15:00", "17:00-22:30"]
wednesday: ["11:30-22:30"]
thursday: ["11:30-22:30"]
friday: ["11:30-23:00"]
saturday: ["12:00-23:00"]
sunday: ["12:00-22:00"]
# holidays-Feld out-of-scope dieser Iteration — siehe Open Questions / Risks.
# notes_de-Feld entfernt — Reservierungs-Policy gehoert nach policies.md.
---
# Oeffnungszeiten Aylem Eat & Meet
## Regelbetrieb
Montags Ruhetag. Dienstag bis Donnerstag von 11:30 bis 22:30 Uhr,
mit Mittagspause Dienstag 15:00-17:00.
...Strukturierte Daten im Frontmatter (Parser-Input fuer check_availability), Mensch-lesbare Beschreibung im Body (Output fuer get_hours).
menu.md-Format (Beispiel-Sketch)
---
tenant: aylem
inhalt_status: realistic-mock
visibility: public-kunde
currency: EUR
last_review: 2026-05-13
---
## Doener-Teller
### Doener-Teller mit Lamm
Preis: 12.50 EUR · scharf optional · Allergene: Gluten (A), Milch (G)
### Doener-Teller vegetarisch (Halloumi)
Preis: 11.50 EUR · Allergene: Gluten (A), Milch (G)
## Beilagen & Vorspeisen
...
## Getraenke
...
## Allergen-Codes (nur tatsaechlich verwendete, LMIV-konform)
A=Gluten, C=Eier, F=Soja, G=Milch/Laktose, M=Senf, N=SesamDas Wort „Preis” im Body ist bewusst — der Substring-Score braucht das Token,
weil Anrufer „was kostet” sagt und der Synonym kostet ↔ Preis ↔ EUR matched.
Allergene werden im File auch schon als Klartext-Format (Gluten (A)) hinterlegt
damit der Tool-Output ohne weitere Aufloesung verstaendlich ist.
Confirmation-Loop-State (Pseudo-Struktur im System-Prompt)
[CONFIRM-State fuer book_reservation]:
Pflicht-Slots: {date, time, guests, name, phone}
Optional-Slots (kein Confirm noetig): {notes}
Wenn alle Pflicht-Slots gesammelt:
1. "Lassen Sie mich kurz wiederholen: <slots in 1 Satz>. Passt das so?"
2. Auf "ja" / "passt" / "stimmt" / "genau" / "richtig" warten
3. Auf Korrektur: betroffenen Slot updaten, dann zurueck zu Schritt 1
4. Bei Schweigen: einmal nachfragen ("Sind Sie noch dran? Soll ich die
Reservierung jetzt anlegen?")
5. Bei weiterem Schweigen / unklarer Antwort: escalate_to_human(
reason="Confirm-Stille", summary=<slots>, urgency="normal")
[CONFIRM-State fuer take_order]:
Pflicht-Slots: {items, customer_name, phone}
Optional-Slots: {address, pickup_time}
Bei >5 items: chunk in 2 Bloecke
Block 1: Hauptgerichte
Block 2: Beilagen + Getraenke
Pro Block: wiederholen + Bestaetigung abwarten
Sonst wie book_reservation-CONFIRM.
Implementation Units
flowchart TB U1[Unit 1: Aylem Knowledge-Files anlegen] U2[Unit 2: _knowledge.py Loader + search_knowledge Tool] U3[Unit 3: get_menu/get_hours/get_allergens Convenience-Tools] U4[Unit 4: check_availability Tool + hours.md Parser] U5[Unit 5: System-Prompt Tool-Discipline + Confirm-Loop] U6[Unit 6: End-to-End Smoke-Test] U1 --> U2 U1 --> U4 U2 --> U3 U3 --> U5 U4 --> U5 U5 --> U6
-
Unit 1: Aylem Knowledge-Files anlegen (2026-05-13: 4 Files unter
tools-mcp/knowledge/aylem/mit YAML-Frontmatterinhalt_status: realistic-mock+visibility: public-kunde. YAML-Parsing verifiziert.)Goal: Vier Markdown-Files (
menu.md,hours.md,policies.md,faq.md) untertools-mcp/knowledge/aylem/mit realistic-mock-Inhalt — genug Substanz dass spaetere Tools sinnvolle Outputs liefern, klar als „nicht von Aylem freigegeben” gekennzeichnet.Requirements: R1, R2, R3 (Daten-Substrat fuer alle drei)
Dependencies: keine
Files:
- Create:
intern/projekte/telefon-assistent-aws/tools-mcp/knowledge/aylem/menu.md - Create:
intern/projekte/telefon-assistent-aws/tools-mcp/knowledge/aylem/hours.md - Create:
intern/projekte/telefon-assistent-aws/tools-mcp/knowledge/aylem/policies.md - Create:
intern/projekte/telefon-assistent-aws/tools-mcp/knowledge/aylem/faq.md - (
knowledge/README.mdgestrichen — premature documentation; Multi-Tenant-Pattern landet stattdessen invoice-bot-de-pattern.mdwenn die Iteration validiert ist.)
Approach:
- Jedes File mit YAML-Frontmatter (Felder:
tenant,inhalt_status: realistic-mock,currencybei menu,last_review).inhalt_statusmacht deutlich dass Marvin den Inhalt nicht von Aylem verifiziert hat. menu.md: 4–6 Kategorien (Doener-Teller, Beilagen, Getraenke, Vegetarisch, Desserts), pro Kategorie 3–5 Items, Preis in EUR + auch das WortEURausgeschrieben (damit Substring-Score auf „kostet” via Synonymkostet↔EURmatched), Allergene als LMIV-Code (nur die in der Karte tatsaechlich vorkommenden Codes, typisch A=Gluten, C=Eier, F=Soja, G=Milch, M=Senf, N=Sesam). Allergen-Legende am Ende der Datei (Section## Allergen-Codes (LMIV-EU-konform)).hours.md: YAMLweekly_hoursmit Mo–So. Body als Mensch-lesbarer Text fuerget_hours-Output.holidays-Feld ist out-of-scope dieser Iteration (kein Maintenance-Mechanismus, kein klarer Owner) — wird erst spaeter ergaenzt wenn Aylem-Live-Pilot startet. Folge:check_availabilityprueft nur Wochentag + Oeffnungsfenster + Personenzahl, nicht Feiertage.policies.md: Reservierungs-Policy (min 2 Personen, Stornierung), Lieferzone (Hamm + 5 km), Mindestbestellwert (20 EUR), Zahlungsarten.faq.md: Top-10 FAQ — Parken, WLAN, Kinderstuhl, Hund-erlaubt, Geburtstagsfeiern (— letzteres triggert Eskalation, gut fuer Test), vegane Optionen, glutenfrei, halal.- Inhalts-Quelle:
aylem.orderstork.comPublic-Page als Anhaltspunkt, generische Doener-Restaurant-Defaults wo nicht verfuegbar. Implementer-Notiz im File-Header: „Inhalte sind realistische Defaults, nicht von Aylem-Inhaber freigegeben. Vor Live-Pilot mit echtem Inhaber-Input ersetzen.”
Patterns to follow:
- YAML-Frontmatter-Konvention aus conventions.md
- Markdown-Struktur aus voice-bot-de-pattern.md (kebab-case, deutsche Umlaute im Body, ae/oe/ue nur in Dateinamen)
Test scenarios:
- Happy path: alle 4 Files existieren, YAML-Frontmatter parsbar, Body nicht leer
- Happy path:
menu.mdhat mindestens 12 Gerichte mit Preisen und Allergen-Codes - Happy path:
hours.mdYAML-Block hat alle 7 Wochentage definiert (closedoder Liste) - Edge case: jede mit
inhalt_status: realistic-mockmarkiert - Integration: PyYAML kann hours.md ohne Exception laden
Verification:
python -c "import yaml; data = yaml.safe_load(open('knowledge/aylem/hours.md').read().split('---')[1]); print(data['weekly_hours'])"zeigt strukturierte Wochentag-Daten. Bei manuellem Lesen ergibt jeder File ein konsistentes Bild eines plausiblen Doener-Restaurants. - Create:
-
Unit 2:
_knowledge.pyLoader +search_knowledgeTool (2026-05-13:_knowledge.pymit load_tenant_files, chunk_by_heading, score_section, search, parse_hours;search_knowledgeTool in server.py; pyyaml + pytest installiert; 16 Tests in tests/test_knowledge.py gruen.)Goal: Markdown-Heading-Chunker + Substring-Retrieval +
search_knowledgeMCP-Tool. Fuer generische KB-Fragen die nicht inget_menu/get_hours/get_allergensfallen.Requirements: R1, R2, R3 (Retrieval-Layer fuer alle drei)
Dependencies: Unit 1
Files:
- Create:
intern/projekte/telefon-assistent-aws/tools-mcp/_knowledge.py - Modify:
intern/projekte/telefon-assistent-aws/tools-mcp/server.py(search_knowledge-Tool + Import_knowledge) - Modify:
intern/projekte/telefon-assistent-aws/tools-mcp/requirements.txt(pyyaml>=6.0+pytest>=8.0ergaenzen — beides nicht im aktuellen venv installiert) - Test:
intern/projekte/telefon-assistent-aws/tools-mcp/tests/test_knowledge.py(pytest, neu — bisher gibt es keintests/-Setup; Unit 2 baut das auf) - Create:
intern/projekte/telefon-assistent-aws/tools-mcp/tests/__init__.py(leer, damit pytest discoverable) - Modify:
intern/projekte/telefon-assistent-aws/tools-mcp/README.md(Tools-Tabelle ergaenzen)
Approach:
_knowledge.pyenthaelt:load_tenant_files(tenant: str) -> dict[str, str]— laedt alle 4 Markdown-Files, returnt{filename: content}. Fail-loud wenn Tenant-Ordner fehlt.chunk_by_heading(content: str) -> list[(heading, body)]— splittet an##/###Headings, gibt Sections zurueck.score_section(query: str, section_text: str) -> float— Substring-Match-Count + Synonym-Expansion. Synonyme als Modul-KonstanteDE_SYNONYMS: dict[str, list[str]].search(query: str, tenant: str, top_k: int = 3, max_output_chars: int = 1500) -> str— orchestriert: load → chunk → score → top_k → joinen mit Source-Tag. Schneidet hart bei 1500 Zeichen.
search_knowledgeMCP-Tool: thin wrapper um_knowledge.search(). Query auf 200 Zeichen begrenzen, sonst returnt „Frage ist zu lang, bitte praezisieren”.- System-Prompt-Update fuer dieses Tool kommt in Unit 5.
- Path-Resolution:
knowledge/<tenant>/viaPath(__file__).resolve().parent / "knowledge" / tenant(file-relativ, niemals cwd-relativ). Override per ENVTOOLS_KNOWLEDGE_DIR— Pfad-Whitelist-Pruefung: ENV-Wert MUSS unterhalb vonPath(__file__).resolve().parentliegen, sonst fail-closed mit ValueError beim Modul-Import (Path-Traversal-Schutz).tenant-String aus TOOLS_TENANT-ENV: nur[a-z0-9-_]erlaubt, kein/, kein..— Substring-Pruefung via Regex beim Load. - Tool-Argument-Validierung pro Tool (Input-Sanitization, gegen Path-Traversal + ReDoS + DoS):
search_knowledge(query): max 200 Zeichen, sonst Hinweis-String returnen.get_menu(category): optional, falls vorhanden max 60 Zeichen, nur[a-zA-Z0-9-_äöüß ], kein Pfad-Separator.get_allergens(item): required-non-empty (sonst „bitte Gericht nennen”), max 100 Zeichen, gleicher Whitelist-Regex wieget_menu.get_hours(day): optional, falls vorhanden Whitelist{None, "monday"-"sunday", ISO-Datum-Regex\d{4}-\d{2}-\d{2}}.check_availability(date, time, guests): date Whitelist ISO, time\d{2}:\d{2}, guests 1..50 (bei >12 returnt false mit „grosse Gruppe”, siehe Unit 4).- File-Read-Cap: max 256 KB pro Knowledge-File, sonst log.warning + Skip dieser Datei.
- LMIV-Code-Mapping als Konstante:
LMIV_CODES = {"A": "Gluten", "G": "Milch", ...}. Initiale Liste nur die Codes die in der realen Doener-Karte vorkommen (A, C, F, G, M, N — kein Krebstier/Lupin/Sulfit-Code wenn nicht im menu.md vorhanden). Tool-Output ersetzt Codes IMMER mit Klartext bevor Returnen.
Patterns to follow:
- _telegram.py — stdlib-only, kein Cross-Repo-Import, error-swallowing-Pattern, Logger-Setup.
- server.py — Tool-Decorator-Pattern, async def, log.info-Format.
Test scenarios:
- Happy path:
search("doener teller preis")→ liefert Menu-Section mit „Doener-Teller mit Lamm: 12.50 EUR” - Happy path:
search("oeffnungszeit montag")→ liefert hours.md-Section mit Ruhetag-Hinweis - Edge case: Synonyme —
search("öffnungszeiten")matched gegen „oeffnungszeit” via Synonym-Map - Edge case: query > 200 Zeichen → returnt Praezisierungs-Hinweis, keine Suche
- Edge case: leere Knowledge-Base (Tenant-Ordner existiert aber Files leer) → returnt „keine Wissensbasis verfuegbar”
- Edge case: Tenant-Ordner fehlt → returnt sauber „keine Wissensbasis verfuegbar”, loggt warning, kein Crash
- Edge case: query mit Sonderzeichen / Umlauten / Anfuehrungszeichen → kein Crash, sauberes Matching
- Error path: YAML-Parse-Fehler in hours.md → search returnt nur die anderen Files, loggt warning
- Integration: zwei aufeinanderfolgende
search()-Aufrufe in derselben Session liefern konsistente Top-3
Verification:
pytest tests/test_knowledge.pyist gruen. Manueller Direkt-Tool-Call viapython -c "from server import search_knowledge; import asyncio; print(asyncio.run(search_knowledge('doener preis')))"liefert sinnvollen Output mit File-Source-Tag. - Create:
-
Unit 3: Convenience-Tools
get_menu,get_hours,get_allergens(2026-05-13: alle 3 Tools live +get_servicesrefactored zu wrapper. Smoke-Tests gruen: get_hours() liefert heute+morgen formatiert, get_hours(“monday”) liefert “Ruhetag”, get_allergens(empty) returnt freundlichen Fallback.)Goal: Drei spezifische Tools die fuer den LLM klarer benannt sind als generisches
search_knowledge— folgt der Master-Plan-Decision „Tool-Splitting statt Discipline-Erziehung”.Requirements: R1, R2, R3, R7
Dependencies: Unit 2
Files:
- Modify:
intern/projekte/telefon-assistent-aws/tools-mcp/server.py(drei neue Tools +get_servicesRefactor) - Test:
intern/projekte/telefon-assistent-aws/tools-mcp/tests/test_knowledge.py(Tests fuer die Convenience-Wrapper ergaenzen)
Approach:
get_menu(category: Optional[str] = None) -> str: delegiert an_knowledge.search(query=f"menu speisekarte {category or ''}", ...). Beicategory=None: returnt Top-Kategorien-Uebersicht. Beicategory="doener-teller": returnt nur diese Section.get_hours(day: Optional[str] = None) -> str: beiday=Noneheute + naechster Tag. Beiday="monday"Wochentag-spezifisch. Beiday="2026-05-17"ISO-Datum-spezifisch. Nutzt Date-Context-Logic aus agent.pybuild_date_contextals Inspiration fuer Relative-Datum-Aufloesung.get_allergens(item: str) -> str: searched gezielt in menu.md nach dem Gericht + LMIV-Code-Mapping. Returnt z.B. „Doener-Teller mit Lamm enthaelt: Gluten (A), Milch (G). Kein Schweinefleisch.”get_services(bestehend) wird thin wrapper umget_menu(category=None)— Backward-Compat fuer alte System-Prompts.
Patterns to follow:
- Bestehende Tool-Signaturen in server.py.
Optional[str]-Default-Pattern wie inbook_reservation.notes.
Test scenarios:
- Happy path:
get_menu()→ returnt Top-Kategorien-Liste - Happy path:
get_menu("doener-teller")→ returnt die Section mit allen Doener-Variationen - Happy path:
get_hours()→ returnt heute + morgen, mit[empfehlung]-Block bei Ruhetag - Happy path:
get_hours("monday")→ returnt „Montag: Ruhetag” - Happy path:
get_allergens("doener-teller mit lamm")→ returnt „Gluten, Milch” + Klarstellung „kein Schwein” - Edge case:
get_menu("nonsense-category")→ returnt „Kategorie nicht gefunden, hier die Top-Kategorien-Uebersicht” als sinnvoller Fallback - Edge case:
get_hours("31. Februar")→ returnt „Datum nicht erkannt, bitte praezisieren” - Edge case:
get_allergens("")→ returnt „bitte Gericht nennen” statt Crash - Integration: alter Tool-Call
get_services()returnt weiterhin sinnvoll (Menu-Top-Kategorien) - Integration: Voice-Bot fragt zu Anfang
get_hours()und kriegt heute/morgen ohne zusaetzliche Args
Verification: Smoke-Test in Browser: „Was kostet ein Doener?” → Log zeigt
get_menuAufruf, Bot-Antwort enthaelt echten Preis aus menu.md. „Habt ihr morgen offen?” → Log zeigtget_hoursAufruf, Bot kennt Wochentag. - Modify:
-
Unit 4:
check_availabilityTool +hours.mdParser (2026-05-13: Tool returnt JSON-String mit available/reason/alternative_suggestion. Mo=Ruhetag/Di-Mittagspause/Vergangenheit/>12 Personen/Datumsfehler alle korrekt. Fail-open mit urgent Telegram-Push bei hours.md-Read-Fehler. 10 Tests in tests/test_availability.py gruen.)Goal: Vor jedem
book_reservationprueft Bot ob Datum/Zeit in Oeffnungs-Fenster liegt und Personenzahl plausibel ist. Bei Nicht-Verfuegbarkeit liefert Tool eine Begruendung + Alternative-Vorschlag, Bot formuliert die Anrufer-Antwort daraus.Requirements: R4
Dependencies: Unit 1 (
hours.mdmuss existieren), Unit 2 (Loader-Helper im_knowledge.pywiederverwenden)Files:
- Modify:
intern/projekte/telefon-assistent-aws/tools-mcp/_knowledge.py(Funktionparse_hours(tenant) -> dict) - Modify:
intern/projekte/telefon-assistent-aws/tools-mcp/server.py(neuescheck_availability-Tool) - Test:
intern/projekte/telefon-assistent-aws/tools-mcp/tests/test_availability.py
Approach:
parse_hours(tenant)in_knowledge.py: laedtknowledge/<tenant>/hours.md, extrahiert YAML-Frontmatter viayaml.safe_load(). Returnt Dict{"weekly_hours": {"monday": "closed", "tuesday": [(time(11,30), time(15,0)), (time(17,0), time(22,30))], ...}, "holidays": [...]}. Wandelt String-Intervals direkt indatetime.time-Tuples um.check_availability(date: str, time: str, guests: int)MCP-Tool:- Datum parsen (
datetime.fromisoformat(date)), bei Parse-Fehler →{"available": false, "reason": "Datum konnte ich nicht verstehen"}. - In-Vergangenheit? →
{"available": false, "reason": "Datum liegt in der Vergangenheit"}. - Personenzahl 1..12 plausibel? Sonst →
{"available": false, "reason": "Bei Gruppen ueber 12 bitte direkt im Restaurant anrufen"}. - Holiday? (
hours["holidays"]durchsuchen) →{"available": false, "reason": <holiday_note>}. - Wochentag-Slot pruefen:
weekly_hours[weekday]ist"closed"→{"available": false, "reason": "<weekday> Ruhetag", "alternative_suggestion": <naechster offener Tag>}. - Wochentag-Slot ist Liste von Intervals: liegt
timein einem? →{"available": true, "reason": ""}. Sonst →{"available": false, "reason": "Zur gewuenschten Uhrzeit haben wir leider zu", "alternative_suggestion": <nearest open slot>}. - Bei jedem Parse-/Read-Fehler: fail-open auf
{"available": true, "reason": "Verfuegbarkeit konnte nicht geprueft werden, Team meldet sich zur Bestaetigung"}plus log.exception. Anrufer soll nie wegen unserem Code-Problem abgewiesen werden.
- Datum parsen (
- Returnt JSON-serialisiertes Dict als String (MCP-Tool-Kompatibilitaet).
- System-Prompt-Update fuer dieses Tool kommt in Unit 5.
Patterns to follow:
- Date-Context-Logic aus agent.py
build_date_context(Wochentag-Names auf Deutsch, ISO-Format). - Tool-Error-Handling aus server.py
book_reservation(try/except sqlite3.Error → fallback-string).
Test scenarios:
- Happy path: Mi 2026-05-21 um 19:00, 4 Personen →
{"available": true, "reason": ""} - Edge case: Mo (Ruhetag laut hours.md) →
{"available": false, "reason": "Montags Ruhetag", "alternative_suggestion": "Dienstag ab 11:30"} - Edge case: Di 16:00 (Mittagspause 15:00-17:00) →
{"available": false, "reason": "Mittagspause", "alternative_suggestion": "ab 17:00"} - Edge case: Datum in der Vergangenheit (heute-1) →
{"available": false, "reason": "Datum liegt in der Vergangenheit"} - Edge case: 50 Personen →
{"available": false, "reason": "Bei Gruppen ueber 12 bitte direkt anrufen"} - Edge case: ISO-Datum mit Holiday-Treffer (
2026-12-25) →{"available": false, "reason": "1. Weihnachtsfeiertag, geschlossen"} - Edge case: malformed Datum-String („nicht-isoformat”) →
{"available": false, "reason": "Datum konnte ich nicht verstehen"} - Edge case: malformed Time-String → analog
- Error path: hours.md fehlt → fail-open auf available=true mit reason „Verfuegbarkeit nicht pruefbar”
- Error path: YAML-Parse-Fehler in hours.md → analog fail-open
- Integration: Voice-Bot-Call „Tisch am Montag 19 Uhr” → check_availability returnt Mo-Ruhetag, Bot schlaegt Di vor → User akzeptiert Di → check_availability(Di) returnt available
Verification: Direkt-Tool-Aufruf mit allen Edge-Case-Inputs liefert konsistente Strukturen. Voice-Test gegen Mo + 50 Personen + Mittagspause produziert hilfreiche statt blinde Antworten.
- Modify:
-
Unit 5: System-Prompt Tool-Discipline + Confirmation-Loop (2026-05-13: TOOL-DISCIPLINE-Block ersetzt die alten “Karte wechselt regelmaessig”-Mock-Anweisungen. Multi-Intent Question-First-Pattern + Korrektur-Limit + Multi-Lang Bestaetigungs-Worte (TR/AR/RU/PL/IT/FR) + Realistic-Mock-Disclaimer-Zusatz + Chunking >5 Items. Worker neu gestartet, neue MCP-Tools sichtbar.)
Goal: System-Prompt erzwingt Reihenfolge: KB-Frage → Knowledge-Tool, Reservierung →
check_availability→ CONFIRM-State →book_reservation. CONFIRM-State wiederholt Slots, wartet auf „ja”, nutzt Chunking bei >5 Items, eskaliert bei Schweigen.Requirements: R5, R6, R7
Dependencies: Units 2, 3, 4 (alle Tools muessen existieren bevor der Prompt sie referenziert)
Files:
- Modify:
intern/projekte/telefon-assistent-aws/livekit-agent/agent.py(SYSTEM_PROMPT_TEMPLATE-Block „Tool-Discipline + Confirm-Loop” neu)
Approach:
- Neuer Prompt-Block direkt nach „Wie du Anrufer behandelst” und vor „MEHRSPRACHIGKEIT”:
TOOL-DISCIPLINE — Welches Tool wann: - Speisekarte / Preis / Gericht / „was habt ihr": ZUERST get_menu() oder get_menu(category="<kategorie-name>") aufrufen, dann antworten - Oeffnungszeiten / „habt ihr offen" / Wochentag: ZUERST get_hours() oder get_hours(day="<wochentag>"|"YYYY-MM-DD") aufrufen, dann antworten - Allergene / „enthaelt X" / vegan / glutenfrei: ZUERST get_allergens(item="<gericht>") aufrufen, dann antworten - Sonstige Wissens-Fragen (Parkplatz, WLAN, Hund, Catering...): ZUERST search_knowledge(query="...") aufrufen - Reservierung: ZUERST check_availability(date, time, guests) aufrufen. Bei available=false: dem Anrufer die reason erklaeren NATUERLICH (nicht IVR-haft) und alternative_suggestion vorschlagen. Beispiel-Phrasen statt IVR: IVR-schlecht: „Montags haben wir Ruhetag. Eine Alternative waere Dienstag um 11:30." natuerlich: „Ach Mist, Montag ist bei uns Ruhetag. Ginge Dienstag auch? Da sind wir ab halb zwoelf da." Bei available=true: weiter zum CONFIRM-State. - Bestellung: keinen Verfuegbarkeits-Check noetig (Restaurants nehmen waehrend Oeffnungszeit Bestellungen an — Confirm-Loop reicht). Niemals Menu/Preis/Allergen/Oeffnungszeit aus dem Kopf beantworten. Wenn ein KB-Tool nichts liefert oder einen „nicht gefunden"-String returnt: ehrlich „das weiss ich nicht, mein Team meldet sich gleich" und escalate_to_human aufrufen. KB-Tool-OUTPUT-LANGE: Tools koennen bis zu 1.5 kB Text zurueckliefern. Du LIEST DAS NIE WORTWOERTLICH VOR. Du fasst in 1-2 Saetzen zusammen und bietest an „mehr Details bei Bedarf". Beispiel: Tool returnt: <8 vegetarische Gerichte, alle mit Preis + Allergenen> Bot sagt: „Vegetarisch haben wir einiges, am beliebtesten ist der Falafel-Teller und die Halloumi-Pfanne. Soll ich Ihnen zu einem davon mehr sagen?" LMIV-Allergen-CODES: niemals Codes wie „A" oder „G" am Telefon vorlesen — IMMER Klartext. Beispiel: nicht „Allergene A, G" sondern „enthaelt Gluten und Milch". Das _knowledge-Modul liefert dir bereits aufgeloeste Klartext-Form — bleib bei der. CONFIRM-State vor book_reservation und take_order: Sobald alle Pflicht-Slots gesammelt sind, MUSST du die Slots zurueckspielen und auf explizite Bestaetigung warten. NIEMALS direkt das Tool aufrufen. Fuer book_reservation Pflicht-Slots (5): {date, time, guests, customer_name, phone}. Optional (no-confirm): notes. Fuer take_order Pflicht-Slots (3): {items, customer_name, phone}. Optional (no-confirm): address, pickup_time. HINWEIS: Slot-Name ist `customer_name` fuer beide Tools (frueher hiess es bei book_reservation `name` — wird einheitlich auf `customer_name` umgestellt in Unit 1 server.py-Refactor). Confirm-Phrasen variieren (nie zweimal denselben Satz): - „Okay, also: 4 Personen am Samstag um 19 Uhr, auf Mueller, 0170 12345. Passt das so?" - „Lassen Sie mich kurz zusammenfassen — 4 Leute, Sa 19:00, Mueller, 0170 12345. Stimmt das?" - „Ich hab notiert: 4 Personen, Samstag 19 Uhr, Mueller, 0170 12345. Richtig?" Auf „ja" / „passt" / „stimmt" / „genau" / „richtig" → Tool aufrufen. Mehrsprachige Bestaetigungs-Worte (gelten genauso als „ja"): - Tuerkisch: „tamam", „evet", „olur", „oldu" - Arabisch: „naam", „tamam", „aiwa" - Russisch: „da", „horosho", „davai" - Polnisch: „tak", „ok", „dobrze" - Italienisch: „si", „va bene", „perfetto" - Franzoesisch: „oui", „d'accord", „parfait" Wiederhole den Confirm IMMER in der Sprache des Anrufers. Auf Korrektur (z.B. „nein, 5 Personen") → Slot updaten, dann erneut bestaetigen. KORREKTUR-LIMIT: Wenn der Anrufer im selben Vorgang 3x Korrekturen macht, statt 4. Confirm-Loop direkt eskalieren: escalate_to_human(reason="Slot-Verwirrung", summary=<slots>, urgency="normal"). MULTI-INTENT mid-CONFIRM: Wenn der Anrufer mid-CONFIRM eine Frage einwirft (z.B. „warte, was kostet der Doener-Teller?" waehrend gerade Reservierungs-Slots wiederholt werden): 1. Slots in deinem Kopf BEHALTEN — nicht vergessen 2. Die Frage mit dem richtigen KB-Tool beantworten (kurz) 3. Anschluss-Satz: „Okay — und zurueck zur Reservierung: <slot-wiederholung>. Passt das so?" Dieser Question-First-mit-Slot-Persistence-Pfad ist Pflicht. Auf Schweigen (Bot wartet >5 s nach Confirm-Frage) → einmal nachfragen: „Sind Sie noch dran? Soll ich die Reservierung jetzt anlegen?". Bei weiterem Schweigen (>10 s) → escalate_to_human( reason="Confirm-Stille", summary=<slots>, urgency="normal"). NIE auto-buchen. REALISTIC-MOCK-CONTEXT (gilt fuer Aylem-Demo-Tenant): hours.md und menu.md sind nicht von Aylem verifiziert. Bei Reservierungs-Confirm im Realistic-Mock-Modus ergaenze immer einmal: „Ich melde das gleich ans Team weiter zur Bestaetigung — die rufen Sie zurueck unter <phone>." Macht klar dass die Reservierung noch nicht final-zugesagt ist. CHUNKING bei take_order mit MEHR als 5 verschiedenen Items: - Block 1: Hauptgerichte zurueckspielen, bestaetigen lassen - Block 2: Beilagen + Getraenke zurueckspielen, bestaetigen lassen - Dann take_order aufrufen - Genau 5 Items oder weniger: kein Chunking (eine Confirm-Wiederholung reicht). - Beispiel: „Erstmal die Hauptgerichte — 2x Doener mit scharf, 1x Falafel ohne Zwiebel. Passt das?" User „ja". „Und an Beilagen — 3x Ayran, 1x Salat. Passt das auch?" User „ja". → take_order. WICHTIG: Confirm-Loop und alle obigen Regeln gelten genauso bei Mehrsprachigkeit (TR/AR/RU/PL/...). Wiederhole in derselben Sprache. Eskalations-Trigger gelten ebenfalls. - System-Prompt-Laenge bleibt unter 2.5 kB damit Cache greift. Wenn knapp: Anti-IVR-Phrasen-Block kuerzen, Verweis auf voice-bot-de-pattern.md.
Patterns to follow:
- Bestehender System-Prompt-Aufbau in agent.py.
- Pflicht-Vier-Bloecke aus voice-bot-de-pattern.md.
- „Tool-Pflicht ohne Tool kein Confirm” aus dem bestehenden Block fuer book_reservation (Z. 155).
Test scenarios (alle als Voice-End-to-End, kein Unit-Test moeglich):
- Happy path: Reservierungs-Flow mit allen 5 Slots, User bestaetigt mit „ja passt” → check_availability + Confirm-Wiederholung + book_reservation in Log
- Happy path: Menu-Frage „Was kostet ein Doener-Teller?” → get_menu im Log, Bot antwortet mit Preis aus menu.md (nicht halluziniert)
- Happy path: Oeffnungszeit-Frage „Habt ihr morgen offen?” → get_hours im Log, korrekte Antwort
- Edge case: STT-Verhoerer (50 statt 15 Personen) → Bot wiederholt „50”, User korrigiert „nein, 15”, Bot updated, wiederholt erneut, dann book mit 15
- Edge case: User schweigt nach Confirm-Frage → Bot fragt einmal nach, dann eskaliert (escalate_to_human in Log statt book_reservation)
- Edge case: Tisch am Montag → check_availability returnt Mo-Ruhetag, Bot schlaegt Di vor, User akzeptiert Di, neuer check_availability(Di), book_reservation(Di)
- Edge case: Bestellung mit 8 Items → Bot chunked in 2 Bloecke, je einzeln bestaetigen lassen
- Edge case: User wechselt mid-CONFIRM zu TR („tamam”) → Bot bestaetigt auf TR + book_reservation
- Integration: Allergen-Frage „Habt ihr was ohne Gluten?” → get_allergens / search_knowledge im Log, Bot antwortet konkret welche Gerichte glutenfrei sind
Verification: 10+ Voice-Test-Calls in versch. Variationen — keine Halluzination bei KB-Fragen, keine Buchung ohne Confirm, kein Tool-Call ohne Wiederholung im Transkript.
- Modify:
-
Unit 6: End-to-End Smoke-Test (manuelle Test-Sequenz)
Goal: Definierter Test-Pfad der die Units 1–5 zusammen validiert. Wird einmal manuell durchgespielt nach Implementierung, Logs werden archiviert als „Reference-Case-Demo-Baseline” fuer den Master-Plan Unit 13 (Demo-Pack).
Requirements: alle (R1–R7)
Dependencies: Units 1–5 vollstaendig
Files:
- Create:
intern/projekte/telefon-assistent-aws/demo-pack/2026-05-XX-substanz-iter-walkthrough.md(Datum bei Durchfuehrung einsetzen)
Approach:
- Voller Browser-Call-Pfad gegen
/cartesia, folgendes Skript abspielen:- Greeting → Bot grueßt mit KI-Disclaimer
- Frage zur Karte „Was habt ihr fuer Doener-Teller?” → erwartet
get_menu("doener-teller")Log + konkrete Antwort - Allergen-Frage „Hab ich Gluten-Allergie — geht das?” → erwartet
get_allergensLog + konkrete glutenfreie Option - Oeffnungs-Frage „Habt ihr Montag offen?” → erwartet
get_hours("monday")Log + „Mo Ruhetag” Antwort - Reservierungs-Versuch Mo „Reservieren Sie mir bitte einen Tisch fuer Montag 19 Uhr” → erwartet
check_availabilityLog + Bot schlaegt Alternative - Reservierung Di „Dann Dienstag, 4 Personen, 19 Uhr, Mueller, 0170 12345” → erwartet
check_availabilityok + CONFIRM-Wiederholung - Confirm-Korrektur Bot wiederholt mit „50” statt „5” Personen → User korrigiert → Bot updated und wiederholt nochmal → User „ja” → erwartet
book_reservationLog + Telegram-Push - Bestellung 8 Items „Ich moechte zur Mitnahme bestellen — [8 Items]” → erwartet Chunking in 2 Bloecke + zwei einzelne Bestaetigungen +
take_orderLog - Sonderwunsch „Macht ihr Catering fuer 80 Personen?” → erwartet
escalate_to_human(reason="Sonderwunsch")Log + Telegram-Push - Verabschiedung „Danke, Tschuess” → Bot verabschiedet +
end_call+ Session-Disconnect
- Walkthrough-Datei dokumentiert pro Schritt: User-Ausgabe, Bot-Antwort, Tool-Calls aus Log, Telegram-Push-Screenshot (Hash-anonymisiert).
Patterns to follow:
- Bestehende Aylem-Test-Outputs-Tabelle in _index.md §Phase 0.
Test expectation: none — manueller Walkthrough, kein Code-Test. Die Validierung ist die Walkthrough-Datei selbst.
Verification: Walkthrough-File existiert mit allen 10 Schritten dokumentiert. Keine Halluzination, keine Blind-Buchung, keine Geister-Reservierung. Pro Schritt klare Tool-Call-Spuren im Log.
- Create:
System-Wide Impact
- Interaction graph:
livekit-agent/agent.py(System-Prompt) ↔tools-mcp/server.py(5 neue Tools) ↔tools-mcp/_knowledge.py(Helper) ↔tools-mcp/knowledge/aylem/*.md(Daten). Plus: bestehende Tools (book_reservation,take_order,escalate_to_human,end_call,get_services) bleiben funktional,get_serviceswird thin wrapper umget_menu. - Error propagation: Knowledge-File-Read-Errors → fail-open auf „weiss ich nicht, Team meldet sich” + log.warning. YAML-Parse-Errors in
hours.md→check_availabilityfail-open auf available=true (Anrufer-Schutz). Tool-Call-Errors duerfen Tool-Discipline-Prompt nicht durchbrechen — Bot soll dann eskalieren statt halluzinieren. - State lifecycle risks: Confirmation-Loop ist Prompt-State, kein DB-State — bei Worker-Restart mitten in einem Call ist der State weg. Anrufer-Connection bricht in dem Fall auch ab (LiveKit-WebRTC), daher kein Daten-Verlust-Risk. Knowledge-Files werden bei jedem Tool-Call frisch gelesen (kein Cache) — Aenderungen sind sofort wirksam, kein Worker-Reload noetig.
- API surface parity:
get_servicesbleibt erhalten (als Wrapper anget_menu) — Backward-Compat fuer bestehenden System-Prompt-Pfad und externe Claude-Code/Desktop-Nutzer die den MCP eingebunden haben. Tool-Liste waechst von 4 (book_reservation, take_order, escalate_to_human, get_services) auf 9 (zusaetzlich search_knowledge, get_menu, get_hours, get_allergens, check_availability). MCP-Tool-Liste ist proAgentSessiongecached — Aenderungen an server.py werden NICHT automatisch erkannt; nach jedem server.py-Edit MUSS der Worker manuell neu gestartet werden (Ctrl-Cim dev-Terminal + erneutpython agent.py dev). Knowledge-Markdown-Edits sind ohne Restart sofort wirksam (Tool liest pro Call frisch). - Integration coverage: Voice-End-to-End-Tests (Unit 6) sind nicht durch unit-Tests ersetzbar — Tool-Discipline-Prompt-Befolgung, Confirm-Loop-Verhalten und STT-Korrektur-Flows brauchen den echten Voice-Loop.
- Unchanged invariants: SQLite-Schema (reservations, orders, escalations) bleibt unveraendert. ID-Format (RES/ORD/ESC-XXXXXX) bleibt. Bedrock-Modell, LiveKit-Region, Cartesia-Voice bleiben. ADR
keine-eigene-plattformweiter eingehalten — alle neuen Tools im MCP.
Risks & Dependencies
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Knowledge-File-Inhalt inhaltlich falsch (Marvin hat keine Aylem-Inhaber-Verifikation) | Hoch | Hoch (LMIV-Werbeaussage-Risiko bei Allergenen) | inhalt_status: realistic-mock-Marker im Frontmatter PLUS System-Prompt-Pflicht-Erweiterung (Unit 5): Bot ergaenzt jede Reservierungs-Confirm um „Team meldet sich zur Bestaetigung” — verschiebt die finale Zusage zum Inhaber. Bei Live-Demo gegen Sales-Lead: Reference-Case-Onepager (Master-Unit 13) macht den Demo-Status explizit. Eskalation: bei Aylem-Live-Pilot-Folge-Plan zwingend echter Inhaber-Input vorab. |
| Substring-Score liefert irrelevante Sections | Mittel | Mittel | Synonym-Liste iterativ erweitern beim Testen. Top-3-Hard-Cap auf 1.5 kB verhindert Token-Aufblaehung selbst bei mehrfachen Treffern. |
| LLM ignoriert Tool-Discipline-Prompt und antwortet aus Pre-Training | Mittel | Hoch | Tool-Splitting (klar benannte spezifische Tools) reduziert das schon strukturell. Eval (Master-Unit 7) misst „Hallucination” als Dimension. Iteratives Prompt-Tightening wenn nach 20 Test-Calls Hallucinations sichtbar sind. |
check_availability schlaegt fail-open bei realistic-mock zu generoes — bucht obwohl Restaurant zu | Mittel (bei realistic-mock) | Hoch (Anrufer steht vor zugesperrtem Laden) | Differenzierter Fail-Pfad (siehe Decisions): fail-open NUR bei Setup-Fehler (hours.md fehlt komplett), nicht bei Daten-Luecken. Plus: System-Prompt zwingt Realistic-Mock-Disclaimer „Team meldet sich zur Bestaetigung” — verschiebt finale Zusage zum Inhaber. Telegram-Push bei fail-open mit urgent=True. |
search_knowledge ohne Score-Floor liefert random Top-3 bei No-Match-Query („was habt ihr da so?“) | Mittel | Mittel | Score-Floor (siehe Decisions): bei Top-1-Score=0 returnt das Tool Praezisierungs-Hinweis statt random Sections. Verhindert Hallucinations-Surrogate. |
| Confirm-Loop bei Multi-Intent (KB-Frage mid-CONFIRM) loescht gesammelte Slots | Mittel | Mittel | System-Prompt-Block „MULTI-INTENT mid-CONFIRM” (Unit 5): Question-First-mit-Slot-Persistence-Pfad ist Pflicht. Test-Szenario in Unit 6 ergaenzen. |
| LLM liest LMIV-Allergen-Codes statt Klartext am Telefon vor | Mittel | Mittel | _knowledge.py Code-zu-Klartext-Mapping zentralisiert; Tools liefern bereits Klartext im Output; System-Prompt-Verbot Codes vorzulesen. menu.md schreibt Allergene schon in Klartext-Format Gluten (A). |
| Tool-Liste-Cache pro AgentSession — neue Tools sichtbar erst nach Worker-Restart | Hoch (waehrend Implementation) | Niedrig | Operational-Notes klargestellt (siehe oben). Implementer-Hinweis: nach jedem server.py-Edit Ctrl-C + neu starten. |
| Confirmation-Loop greift bei Multi-Lang nicht (TR/AR-Anrufer wird ohne Confirm gebucht) | Mittel | Mittel | System-Prompt-Block explizit: „Auch bei Mehrsprachigkeit gilt der Confirm-Loop”. Test-Persona „TR-Reservierung” in Unit 6 Smoke-Test. |
| Knowledge-File-Inhalt aendert sich live waehrend Call (z.B. menu.md edit) | Niedrig | Niedrig | Tool liest pro Call frisch. Im schlechtesten Fall: Bot zitiert die alte und die neue Version in zwei aufeinanderfolgenden Saetzen — fuer Reference-Case-Demos akzeptabel. |
| LMIV-Allergen-Codes in menu.md inkonsistent / falsch zugeordnet | Mittel | Niedrig (Reference-Case) / Hoch (Live-Pilot) | Realistic-Mock-Disclaimer. Echter Live-Pilot braucht ohnehin LMIV-Audit (Folge-Plan). |
Worker-Hot-Reload via python agent.py dev greift nicht bei _knowledge.py-Aenderung | Niedrig | Niedrig | livekit-agents dev-mode hat File-Watcher — testen waehrend Implementation. Wenn nicht: manuell Worker neustarten (~5 s). |
search_knowledge-Output bricht UTF-8-Encoding bei Sonderzeichen | Niedrig | Niedrig | Python-Default-UTF-8 fuer File-IO. Telegram-Format hat Escape-Helper. Test-Szenario: Umlaut-haltige Section. |
Documentation Plan
- Update Master-Plan: Units 2/3/4 markieren als „siehe Sub-Plan 2026-05-13-002” oder direkt abhaken wenn Sub-Plan komplett ist.
- Update _index.md Open-Loops-Tabelle: Sub-Plan-Eintrag „Substanz-Iteration geplant 2026-05-13” plus separate Eintraege fuer Knowledge-Base / Verfuegbarkeit / Confirm-Loop wenn implementiert.
- Update README.md: Tools-Tabelle waechst auf 9 Tools (book, take, escalate, services, search_knowledge, get_menu, get_hours, get_allergens, check_availability) + neues Setup-Step „knowledge-Folder anlegen”.
- Erweitern voice-bot-de-pattern.md: neuer Section „Knowledge-Base-Pattern fuer Service-Vertikalen” mit
_meta.md-Format + Substring+Synonyme-Approach als Wiederverwendungs-Vorlage.
Operational / Rollout Notes
- Rollout: Lineare Sequenz Unit 1 → 2 → 3 → 4 → 5 → 6. Unit 1 ist parallelisierbar mit Unit 2-Beginn (Mock-Files schreiben + Loader-Code starten ohne Wartezeit), aber Unit 2 braucht Unit 1-Files fertig fuer den ersten Smoke-Test. Unit 5 braucht Tools 2+3+4 alle live damit System-Prompt nicht auf nicht-existierende Tools verweist.
- Reload-Strategie (verifiziert gegen livekit-agents 1.5.8):
python agent.py devhat einen File-Watcher der ausschliesslichagent.pyund Plugin-Module ueberwacht — NICHTtools-mcp/. Konkret:- agent.py-Edit → automatischer Hot-Reload, neuer Worker-Process. OK.
- server.py- oder _knowledge.py-Edit → kein automatischer Reload. Plus: das MCP-Subprocess wird zwar bei jedem neuen Job neu gespawned, aber die MCP-Tool-Liste ist pro AgentSession gecached (livekit-agents
MCPToolset.setup()faehrt einmal beim Session-Start). Wenn neue Tools dazukommen, sieht eine laufende Session sie nicht. Konsequenz: Nach jedem server.py-/_knowledge.py-Edit Ctrl-C + erneutpython agent.py dev(~5 s). - Knowledge-Markdown-Edit (menu.md, hours.md, policies.md, faq.md) → sofort wirksam beim naechsten Tool-Call, kein Reload. Tool laedt pro Call frisch (kein Cache).
- Knowledge-File-Updates ohne Restart: Tool liest pro Call frisch. Aenderungen in
menu.mdsind sofort wirksam, kein Reload noetig. - DB-Schema-Freeze bleibt: keine neuen Tabellen, keine ADD COLUMN. Master-Plan-Operational-Note-konform.
- Eval-Hook: Unit 6 produziert eine Walkthrough-Datei die direkt fuer Master-Plan Unit 13 (Demo-Pack) wiederverwendbar ist. Plan-Verkettung: Substanz-Iter → Demo-Pack → Reference-Case-Asset-Done.
Sources & References
- Origin (Master-Plan): 2026-05-13-001-feat-voice-bot-de-production-plan.md — Units 2, 3, 4 als Substrat
- Voice-Bot-Pattern: voice-bot-de-pattern.md
- ADR Brain+MCPs: keine-eigene-plattform.md
- Code-Referenz Telegram-Pattern: _telegram.py — Vorbild fuer
_knowledge.py - Code-Referenz MCP-Tool-Pattern: server.py —
book_reservation-Tool als Vorbild fuer Struktur - Code-Referenz System-Prompt: agent.py —
SYSTEM_PROMPT_TEMPLATEals Erweiterungspunkt - Externe — GetStream Restaurant RAG: getstream.io/blog/restaurant-call-rag-agent — Pattern „Markdown unter knowledge/, Tool-Wrapper”
- Externe — Kwindla Voice Agents Advice: gist Tool-Splitting — bestaetigt Decision „spezifische Tools statt einem search_knowledge”
- Externe — LMIV EU: eur-lex.europa.eu CELEX:32011R1169 — Allergen-Pflichtangaben
Related
- _index — Projekt-Index Telefon-Assistent
- 2026-05-13-001-feat-voice-bot-de-production-plan — Master-Plan
- voice-bot-de-pattern
- keine-eigene-plattform