DE-Voice-Bot Pattern (Brain + MCPs)

Wiederholbares Pattern fuer Voice-Bots in Service-Businesses (Restaurant, Friseur, Praxis, Werkstatt, Hotel, Anwalt). Konsolidiert die Lessons aus dem Aylem-POC. Wenn ein neuer Kunden-Voice-Bot kommt: dieses File ist der Anker.

Erste Anwendung: Aylem Eat & Meet, 2026-05-09 bis 2026-05-10. Kompletter Stack live.

Stack-Komponenten (Default 2026-05)

Browser oder Phone
   ↓ WebRTC / SIP
LiveKit Cloud (Region Germany 2 = Frankfurt)
   ↑↓ Audio
Python Worker (livekit-agents 1.5+, eigener Container)
   ├─ silero VAD
   ├─ Deepgram nova-3 (DE) — STT
   ├─ Bedrock Claude Haiku 4.5 EU — LLM
   │    ↳ System-Prompt mit dynamischem Datum-Block + Spoken-German-Block + Tool-Call-Pflicht
   ├─ tts_node-Override: parst [emotion]-Tag, mappt auf Cartesia-Controls
   └─ Cartesia Sonic 2 (DE-Voice "Viktoria - Phone Conversationalist") — TTS
        ↓ MCP-Server (stdio oder HTTP/SSE)
   Tool-Schicht: service-business-tools MCP
   (book_reservation, take_order, get_services, ...)

Pflicht-Komponenten im System-Prompt

1. Datum-Block (dynamisch beim Worker-Start)

Ohne diesen halluziniert der LLM bei „naechste Woche Dienstag”. 14-Tage-Tabelle generieren:

def build_date_context(now=None):
    if now is None:
        now = datetime.now(ZoneInfo("Europe/Berlin"))
    today_label = f"{DE_WEEKDAYS[now.weekday()]}, der {now.day}. {DE_MONTHS[now.month]} {now.year}"
    rows = []
    for offset in range(0, 15):
        d = now + timedelta(days=offset)
        marker = " ← HEUTE" if offset == 0 else (" (morgen)" if offset == 1 else "")
        rows.append(f"  {d.strftime('%Y-%m-%d')}  {DE_WEEKDAYS[d.weekday()]:<10}  ...{marker}")
    return f"Heute ist {today_label}.\n\n14-Tage-Tabelle:\n" + "\n".join(rows)

Plus Anleitung im Prompt: „Wenn der Anrufer ‘naechste Woche Dienstag’ sagt, schau in die Tabelle und nimm den Dienstag NACH dem aktuellen Wochenende.”

2. Tool-Call-Pflicht (Nicht-Verhandelbar)

Sonst formuliert der Bot Bestaetigungen ohne das Tool aufzurufen → Reservierung ist nirgends im System.

ZWINGEND bei <Aktion>: Sobald du alle Slots hast, MUSST du `<tool>` aufrufen.
KEINE Bestaetigung ohne Tool-Call.
Bei Korrekturen Tool ERNEUT mit korrigierten Werten aufrufen — bestaetige NICHT den alten Wert.

3. Spoken-German-Block (Tonalitaet)

Richtige Mitte: Sie-Form, knapp, professionell-warm — wie ein erfahrener Restaurant-/Service-Mitarbeiter.

Verboten:

  • IVR-Phrasen: „Sehr gerne, ich notiere Ihnen…”, „Selbstverstaendlich”, „Mit Vergnuegen”, „Hervorragend”
  • Filler-Inflation: nicht jede Antwort mit „[mhm]” / „[ja]” beginnen
  • Steife Frageformen: „Darf ich noch Ihre Telefonnummer haben?” → besser „Und Ihre Telefonnummer?”

Bestaetigungs-Variation: „In Ordnung”, „Notiert”, „Alles klar”, „Hab ich” — nicht jedes Mal die gleiche Phrase.

Lesson: Beim ersten Patch ueber das Ziel geschossen mit Filler-Inflation. Marvin: „nicht Fake Cool, lass mal normal bleiben aber laessig”. Pattern: minimal-laessig statt aufgesetzt-locker.

4. KI-Disclaimer im Greeting (EU AI Act Art. 50)

Pflicht ist „erkennbar machen”, NICHT „klingen wie ein IVR-Disclaimer”. Gute Beispiele:

  • „Aylem Eat & Meet, ich bin der KI-Assistent. Wie kann ich Ihnen helfen?”
  • „Hier ist Aylem, am Telefon ist eine KI. Was kann ich fuer Sie tun?”

Nicht: „Hallo, hier ist der automatische Assistent von Aylem Eat & Meet.”

5. Audio-Tags (Cartesia-Feature) — sparsam

Nur wenn emotional wirklich passend, nicht als rhetorisches Mittel:

  • [seufzt] bei echter Beschwerde
  • [lacht leise] bei freundlicher Bemerkung
  • [pause] bei kurzer Denkpause vor Folgefrage

Keine [mhm]/[ja]-Inflation.

LLM-Wahl: Bedrock Claude Haiku 4.5 EU

Kriteriumgpt-4o-minigpt-4oBedrock Haiku 4.5 EU
Latenz (TTFT)~300ms~800-1500ms~300ms (mit Cache <200ms)
Tool-Calling-Reliabilitymittelsehr gutsehr gut
DSGVOUS (CLOUD Act)USEU (eu-central-1, Frankfurt)
Konsistenz mit unserem Stackneinneinja (Anthropic-zentrisch wie Claude Code, Becker-Lambda)
Kostensehr geringhochgering-mittel

Entscheidung: eu.anthropic.claude-haiku-4-5-20251001-v1:0 via livekit-plugins-aws. Plus cache_system=True (System-Prompt mit Datum-Block ist >800 Tokens — Cache spart ~200ms ab zweitem Turn + Geld).

from livekit.plugins import aws
llm=aws.LLM(
    model="eu.anthropic.claude-haiku-4-5-20251001-v1:0",
    region="eu-central-1",
    temperature=0.0,
    cache_system=True,
),

Voraussetzung: AWS-Credentials erreichen Bedrock (Worker laeuft mit AWS_PROFILE=av-production oder vergleichbarem). Bedrock Model-Access fuer Haiku 4.5 muss in eu-central-1 aktiviert sein.

Latenz-Tuning (alle vier zusammen)

Im AgentSession Konstruktor:

session = AgentSession(
    vad=silero.VAD.load(),
    stt=deepgram.STT(language="de", model="nova-3"),
    llm=aws.LLM(model="eu.anthropic.claude-haiku-4-5-...", cache_system=True, ...),
    tts=cartesia.TTS(model="sonic-2", voice="b9de4a89-...", language="de"),
    mcp_servers=[tools_mcp],
    preemptive_generation=True,
    min_endpointing_delay=0.2,
    max_endpointing_delay=4.0,
    min_consecutive_speech_delay=0.1,
)

Plus LLM parallel_tool_calls=False (Anthropic) — verhindert Mehrfach-Token-Pfade.

Cartesia Voice-Wahl (DE)

Sonic 2 hat 6 DE-Voices in der Standard-Library (Stand 2026-05):

VoiceIDProfil
Viktoria - Phone Conversationalistb9de4a89-2257-424b-94c2-db18ba68c81afeminine, Phone-optimiert (Default)
Alina - Engaging Assistant38aabb6a-f52b-4fb0-a3d1-988518f4dc06feminine, Assistant-Vibe
Marlene - Elegant Speaker9b4d08b6-0494-4301-ab92-9150f4ee2718feminine, formeller
Moritz - Modern Communicator4ad22058-7cb6-402c-a115-196cbfc25dcemasculine, modern
Sebastian - Oratorb7187e84-fe22-4344-ba4a-bc013fcb533emasculine, narrator
Henrik - Steady Analystd1cbea67-e4d3-47cd-be2a-2bd4e646b002masculine, ruhig-sachlich

Emotion-Adaption (tts_node-Override)

LLM gibt Tag pro Antwort, Worker mappt auf Cartesia experimental_controls:

EMOTION_MAP = {
    "friendly":     {"emotion": ["positivity:medium"],                "speed": "normal"},
    "warm":         {"emotion": ["positivity:medium"],                "speed": "normal"},
    "empathetic":   {"emotion": ["sadness:low", "positivity:low"],    "speed": "slow"},
    "apologetic":   {"emotion": ["sadness:medium"],                   "speed": "slow"},
    "concerned":    {"emotion": ["sadness:low", "curiosity:low"],     "speed": "normal"},
    "excited":      {"emotion": ["positivity:high", "surprise:low"],  "speed": "normal"},
    "professional": {"emotion": [],                                   "speed": "normal"},
    "neutral":      {"emotion": [],                                   "speed": "normal"},
}

Im Agent: Override tts_node() Method, sammle LLM-Stream, parse ^[tag] regex, setze Cartesia-Controls vor Synth-Call.

Background-Ambient (Browser-Variante)

Subtile Restaurant-/Service-Geraeusche im Hintergrund machen den Bot deutlich realer („Verortung”). Pattern:

  • Zwei MP3-Loops in static/, Crossfade alle 5s damit Loop nicht erkennbar
  • Lautstaerke-Slider Default 18%, User-tunbar
  • Auto-Start bei Connect, Stop bei Disconnect

Source-Optionen:

  1. Pixabay CC0 (https://pixabay.com/sound-effects/, kein Lizenz-Risiko) — bewaehrt
  2. Eigene Field-Recording beim Kunden (iPhone-Voicememo, perfekt authentisch)
  3. ElevenLabs SFX generiert — Limit 5s pro Sample, kann dünn klingen bei Restaurant-Ambient

Bei SIP-Phone-Anbindung muss das Worker-side gemixt werden — separater Audio-Track im LiveKit-Room oder im TTS-Output gemixed.

Quirks / Stolperer

  • livekit-agents 1.5: Agent.default.tts_node ist die Override-Schnittstelle — nicht session.agent.tts_node (das ist private/nicht existent)
  • mcp_servers ist sowohl auf Agent als auch AgentSession — uebergebe an AgentSession damit der MCP-Subprocess vom Session-Manager geforked wird
  • Cartesia voice_id und voice_name nicht beide angeben im TTS-Call (auch im MCP text_to_speech)
  • Cartesia experimental_controls wird via Plugin-Opts gesetzt: tts._opts.experimental_controls = controls
  • AWS Bedrock cross-region inference profile: Modell-ID Prefix eu. (nicht anthropic.)
  • Datum-Block muss bei Worker-Reload neu generiert werden — load_dotenv aktualisiert env, aber statische Module-Konstanten muessen via Funktion neu evaluiert werden

Cross-Refs