# Blueprint: Voice-Agent

Version 1.0 · denzer.ai · Lizenz: frei nutzbar mit Quellenangabe

Dein Agent hört dir zu und antwortet mit einer Stimme deiner Wahl — als Sprach-Modus für deinen Personal Agent, oder als Voice-Widget auf deiner Webseite für Besucher. Zwei Wege, ein Blueprint.

**Fallbeispiel.** Du willst zwei Dinge:
1. Im eigenen Agent einen Push-to-Talk-Modus: gedrückt halten, reden, Frage stellen, Antwort als Sprache zurück
2. Auf deiner Webseite ein Voice-Widget, das Besucher anklicken können und das wie du klingt, sodass sie sich kurz "live" mit deinem Brand-Agent unterhalten können

---

## Teil B — Für dich (Operator)

### Was ist das?

Zwei Schichten:

**Lokaler Voice-Modus.** Push-to-Talk im eigenen Agent. Aufnahme im Browser, Transkription per Whisper (lokal oder API), Antwort vom Modell, Text-to-Speech über ElevenLabs (oder OpenAI-TTS, lokal). Stimme kannst du frei wählen oder eine eigene klonen.

**Web-Widget.** Ein eingebettetes Widget auf deiner Webseite (z. B. ElevenLabs ConvAI). Besucher klickt, redet, dein Agent antwortet mit deiner Stimme. Sicherheits-Limit: nur Themen, die du explizit freigegeben hast, kein Werbe-Lautsprecher.

### Warum so und nicht anders?

Drei Vergleichspunkte:

1. **OpenAI-TTS lokal komplett.** Günstig (~0,015 USD pro 1000 Zeichen), läuft ohne ElevenLabs. Stimme klingt aber generisch, kein Voice-Cloning. Gut für Nice-to-have-Voice-Modus, schwach für Brand-Erlebnis
2. **Komplett ElevenLabs auch lokal.** Beste Stimm-Qualität, aber teuer und Quota-empfindlich (du hast es selbst gemerkt — eine Stunde ConvAI auf der Webseite verbrennt das Monatsbudget)
3. **Hybrid.** Lokaler PTT-Modus mit OpenAI-TTS oder ElevenLabs nach Wahl, Webseite mit ConvAI nur wenn aktiv beworben und mit hartem Tages-Budget

Daher: **konfigurierbar**, kein Lock-in. Für deinen eigenen Voice-Modus stelle die Stimme ein, die zu dir passt. Auf der Webseite ConvAI nur einschalten, wenn aktiv beworben.

**URL-Antwort statt Bytes-Stream** im Backend, weil iOS-PWA mit StreamingResponse zickt — der Body kommt manchmal leer an. Mit einer Audio-URL plus FileResponse-Endpoint funktioniert es auch in Safari.

**Persistentes Audio-Element** im Frontend, weil iOS sonst die User-Activation während des Fetches verliert und den Play-Aufruf blockiert.

### Was brauchst du dafür?

- Lokaler Modus: Mikrofon im Browser, OpenAI-Key oder lokales Whisper, ElevenLabs- oder OpenAI-TTS
- Web-Widget: ElevenLabs ConvAI-Account, deine Webseite mit Embed-Möglichkeit
- Optional: eine eigene Stimme (Voice-Cloning bei ElevenLabs, 1 Minute Aufnahme reicht)

### Wie startest du?

1. Starter laden, Agent geben
2. Sagen: "richte mir den lokalen Voice-Modus ein" oder "bau mir das ConvAI-Widget auf der Webseite ein"
3. Browser fragt nach Mikrofon-Rechten, einmal erlauben
4. Test: kurze Frage stellen, prüfen ob Antwort sauber klingt

### Wo sind die Grenzen?

- iOS-Safari ist zickig mit Mikrofon-Berechtigungen, gerade in PWA — manchmal hilft nur App-Reload
- TTS aus dem Browser braucht eine gültige User-Geste vor dem ersten Play (iOS-Regel)
- ConvAI-Widget rechnet pro Sekunde Gesprächszeit ab — ohne Limit kann das teuer werden
- Stimm-Klone klingen 90% wie du, die letzten 10% hört man

### Was kostet das?

- ElevenLabs Pro: 22 USD/Monat für ~30 Minuten ConvAI plus etliches TTS
- ConvAI auf der Webseite: nochmal extra, **streng begrenzen oder die Quota ist schnell weg**
- OpenAI-TTS: deutlich günstiger als ElevenLabs, dafür weniger menschlich
- Whisper API: ~0,6 Cent pro Minute Audio

### Wann lohnt es sich, wann nicht?

Lohnt sich, wenn du selbst gern sprichst statt tippst (PT, Pendeln) oder wenn dein Brand-Erlebnis "wie ein Gespräch" sein soll. Lohnt sich nicht, wenn du fast nur am Schreibtisch sitzt und tippen schneller geht.

### Wie weißt du, dass es funktioniert?

- Lokaler Modus: Aufnahme → Antwort in unter 5 Sekunden
- Stimme klingt natürlich, keine Robotik
- Web-Widget kostet nicht mehr Credits als geplant (Tagesbudget setzen!)

### Stolpersteine

- **iOS-PWA Audio.** `audio`-Element verliert User-Activation während eines Fetches. Lösung: Audio über fixe URL statt Blob, persistentes Audio-Element vorhalten
- **Quota-Burner.** ConvAI auf der Webseite kann durch Besucher hochlaufen. Tages- und Wochen-Limit im ElevenLabs-Dashboard setzen, plus ein lokaler Counter
- **Voice-ID-Override.** Wenn du eine Voice-ID übergibst, die dein Account nicht hat, wirft ElevenLabs 401. Im Backend immer einen Fallback auf eine garantiert verfügbare Stimme
- **Lautstärke-Übersteuern.** Bei Whisper-Eingabe nicht zu nah ans Mic
- **Browser-Latenz.** PWA mit aktiver Wiedergabe kann bei Background-Wechsel WS-Verbindungen kurz verlieren. Banner-Schwelle für "Verbindung weg" auf >5s setzen

---

## Teil A — Für deinen Agent

### Agent-Triggersätze

- "Richte mir den Voice-Modus ein"
- "Bau das Voice-Widget für meine Webseite"
- "Lass Klaus mit dieser ElevenLabs-Stimme sprechen"
- "Klon meine Stimme bei ElevenLabs"

### Ziel

Zwei Modi, beide mit definierter Stimme. Lokaler PTT-Modus mit Audio-Aufnahme, Whisper-Transcription, LLM-Antwort, TTS-Wiedergabe. Web-Widget mit ConvAI-Embed plus Sicherheits-Limits.

### Erfolgskriterien

- Lokaler Modus: End-to-End-Latenz < 5 Sekunden für kurze Fragen
- Voice-Wiedergabe funktioniert in Desktop-Browser und iOS-PWA
- ConvAI-Widget mit Tages-Budget
- Fallback auf Standard-Voice bei Konfig-Fehler

### Eingabe-Schema

```yaml
local_voice:
  stt_provider:    "openai-whisper" | "local-whisper"
  tts_provider:    "elevenlabs" | "openai-tts"
  voice_id:        "<voice-id>"
  voice_settings:  {stability: 0.5, similarity_boost: 0.75, style: 0.0}
  endpoint:        "/api/tts"
web_widget:
  provider:        "elevenlabs-convai"
  agent_id:        "agent_<id>"
  domain:          "<deine-domain>"
  daily_budget_credits: 1000             # nicht überschreitbar
  embed_pages:     ["/", "/ai-sprint"]
```

### Verarbeitung — Lokaler Modus

1. **Push-to-Talk** — Button gedrückt, MediaRecorder aufnehmen
2. **Upload** — Audio-Chunk an `/api/voice/transcribe`
3. **STT** — Whisper → Text
4. **LLM** — Agent-Antwort generieren
5. **TTS** — Audio-URL erzeugen, im Cache ablegen
6. **Wiedergabe** — persistentes Audio-Element, src setzen, play()

### Verarbeitung — Web-Widget

1. **Embed** — `<elevenlabs-convai agent-id="...">`
2. **Quota-Wächter** — Cron prüft täglich Verbrauch, schaltet bei Limit ab
3. **Theme-Match** — Widget-Farben an Brand anpassen
4. **Themen-Guard** — System-Prompt im ConvAI-Agent begrenzt Antwort-Themen

### Endpoints (Lokaler Modus)

| Methode | Pfad | Zweck |
|---|---|---|
| POST | `/api/voice/transcribe` | Audio → Text |
| POST | `/api/tts` | Text → Audio-URL (JSON `{url, size}`) |
| GET | `/api/tts-audio/<key>.mp3` | Audio-File mit Range-Support |

### Sicherheit

- ElevenLabs-Key in `.env`
- Web-Widget: System-Prompt begrenzt scharf, was beantwortet wird
- Tages-Budget hart geschaltet, nicht "weich gewarnt"
- Aufnahmen werden nicht persistent gespeichert (außer Transcript)

### Fehler-/Edge-Cases

- iOS-PWA verliert Activation → Audio-URL statt Blob, persistentes Audio-Element
- ElevenLabs 401 (Voice nicht erlaubt) → Fallback auf Default-Voice, Operator-Hinweis
- Quota erreicht → Widget abschalten, lokaler Modus weiter mit OpenAI-TTS
- Whisper-Antwort leer → "Konnte dich nicht verstehen"-Hinweis
- Mikrofon-Permission denied → Operator-Hinweis mit Browser-Anleitung

### Verzeichnisstruktur

```
<root>/voice/
├── backend/
│   ├── tts.py
│   ├── transcribe.py
│   └── cache.py
├── frontend/
│   ├── audioQueue.ts
│   ├── VoiceController.tsx
│   └── ptt-button.tsx
└── widget/
    └── convai-embed.html
```

### Code-Snippets

Im Starter findest du Backend-Wrapper für TTS und Whisper, Frontend-Code für Push-to-Talk inkl. iOS-PWA-Handling, und ein Embed-Snippet für das Web-Widget mit Theme-Anpassung.

---

## Teil C — Was du als Nächstes tust

1. Starter laden, Agent geben
2. ElevenLabs-Account anlegen, optional Voice-Klon erzeugen (1 Min Aufnahme reicht)
3. Sagen: "richte den Voice-Modus mit Voice-ID `<id>` ein"
4. Mikrofon-Permission im Browser erlauben
5. Erster Test: kurze Frage
6. Wenn Web-Widget gewünscht: Embed-Code in Webseite einfügen, Tages-Budget setzen

### Lern-Checkliste

- Welche Stimme verwendet mein Agent und wo wechsle ich sie?
- Wo liegt mein ElevenLabs-Quota und wie sehe ich Verbrauch?
- Warum erlaubt iOS-PWA TTS manchmal nicht, und was hilft?
- Wie verhindere ich, dass Besucher das Web-Widget abusen?
- Wo werden meine Audio-Files zwischengespeichert und wann werden sie gelöscht?

Fragen: christian@denzer.ai
