wiki/z-archive/concepts/Commcoach-Voice-Recording-Streaming-Konzept.md

20 KiB

CommCoach Voice Recording Streaming Konzept

Ausgangslage

Die aktuelle CommCoach-Spracheingabe basiert auf Browser SpeechRecognition (webkitSpeechRecognition). Auf Mobile-Browsern wird die Aufnahme typischerweise alle ca. 5-7 Sekunden automatisch beendet und neu gestartet. Das ist kein sauber behebbarer App-Bug, sondern eine Browser/API-Limitation.

Zielbild

Die Spracheingabe soll auf Mobile und Desktop stabil laufen, ohne erzwungene 5-Sekunden-Resets, ohne Textverlust zwischen Restart-Gaps und ohne MSFT-Abhängigkeiten.

Antwort auf die Kernfragen

1) Ist Option 3 ein genereller Umbau oder nur Mobile?

Beides ist möglich:

  • Variante A (Mobile-only Umbau): Neue Audio-Streaming-Pipeline nur auf Mobile aktivieren, Desktop bleibt vorerst bei Browser STT.
  • Variante B (Genereller Umbau): Neue Pipeline für alle Clients (Mobile + Desktop), einheitliches Verhalten und weniger Wartung.

Empfehlung: Variante B, da nur ein Stack gepflegt werden muss und CommCoach-Logik konsistent bleibt.

2) Kann das komplett in unserer Plattform umgesetzt werden?

Ja. Vollständig in eigener Plattform ist möglich.

Notwendig sind:

  • Eigene UI- und Gateway-Implementierung (WebSocket Streaming)
  • Eigener STT-Service (self-hosted), z. B. faster-whisper
  • Optional eigene VAD (Voice Activity Detection), z. B. webrtcvad

Nicht zwingend notwendig:

  • Externe SaaS-STT Provider
  • MSFT/Azure Speech

Zielarchitektur (MSFT-frei)

UI (Frontend CommCoach)

  • Mikrofonaufnahme via MediaStream + AudioWorklet oder MediaRecorder
  • Chunking (z. B. 100-250 ms)
  • Streaming per WebSocket an Gateway
  • Lokaler Live-Preview-Text kommt nicht mehr aus Browser-STT, sondern aus Server-Interims
  • Bestehende State-Machine bleibt als Steuerlogik erhalten (idle, listening, botSpeaking, interrupted, muted orthogonal)

Gateway

  • Neuer Endpoint, z. B. ws /api/feature/commcoach/{instanceId}/stt/stream
  • Sessiongebundene Stream-Verarbeitung
  • Weiterleitung der Audio-Chunks an STT-Worker
  • Rückkanal von Interims + Final-Text zum UI
  • Serverseitige Segmentierung (Silence / VAD) statt Browser onend

STT-Service (self-hosted)

  • faster-whisper als Runtime (GPU empfohlen, CPU möglich)
  • Sprachen konfigurierbar (z. B. de, en)
  • Ausgabe:
    • interim Events (optional, je nach Latenzbudget)
    • final Segmente
    • Zeitmarken für Debug und Nachvollziehbarkeit

Optional: VAD-Service

  • Entweder im Gateway oder STT-Worker
  • Trigger für Segment-Ende statt fester 1s-Timer
  • Stabiler als browserseitige onspeechstart/onspeechend

Ist-Analyse der bestehenden Codebase (UI + Gateway)

Frontend (frontend_nyla)

  • src/pages/views/commcoach/useVoiceController.ts

    • Nutzt Browser SpeechRecognition direkt.
    • Auto-Restart bei onend mit REC_AUTORESTART_DELAY_MS = 300.
    • SILENCE_TIMEOUT_MS = 1000 finalisiert User-Text.
    • Mobile-Problem entsteht hier durch Browser-onend-Zwang (alle ~5-7s).
  • src/pages/views/commcoach/CommcoachDossierView.tsx

    • Bindet Voice-State-Machine an UI/TTS.
    • voice.liveTranscript wird als Live-User-Bubble angezeigt.
    • Debuglog existiert bereits via window.__dlog.
  • src/hooks/useCommcoach.ts

    • Text läuft über sendMessageStreamApi (SSE).
    • Audio läuft derzeit nur als One-Shot Blob über sendAudioStreamApi (SSE).
    • Kein echtes bidirektionales Low-Latency Audio-Streaming.
  • src/api/commcoachApi.ts

    • Bestehende Endpunkte sind HTTP + SSE.
    • Es gibt noch keinen WebSocket-Endpunkt für chunkweises Audio.

Gateway (gateway)

  • modules/features/commcoach/routeFeatureCommcoach.py

    • POST .../message/stream (SSE) für Text.
    • POST .../audio/stream (SSE) für Audio-One-Shot.
    • Audio-Endpoint liest await request.body() komplett und startet danach STT.
  • modules/features/commcoach/serviceCommcoach.py

    • processAudioMessage(...): STT auf gesamtem Blob, danach processMessage(...).
    • Keine Segment-/Chunk-Logik für laufende Erkennung.
  • modules/interfaces/interfaceVoiceObjects.py

    • Kapselt STT/TTS aktuell über Google Connector.
    • API ist bereits zentralisiert und damit gut erweiterbar.
  • modules/connectors/connectorVoiceGoogle.py

    • Verwendet recognize auf Einzeldateien (nicht Streaming).
    • Damit strukturell ungeeignet für kontinuierliche Mobile-Spracheingabe.

Implementierung im bestehenden System (konkret)

Ziel: bestehende CommCoach-Pipeline beibehalten, nur STT-Eingang ersetzen

Unverändert bleiben:

  • Session- und Message-Processing in serviceCommcoach.py (processMessage, Events, TTS).
  • UI-State-Machine-Fachlogik (idle, listening, botSpeaking, interrupted, muted).

Ersetzt wird:

  • Browser-STT (SpeechRecognition) durch Streaming-STT Provider.
  • One-Shot Audio-Upload durch WebSocket-Audio-Stream.

A) Frontend Änderungen

  1. Neue API-Funktion in src/api/commcoachApi.ts

    • openSttStreamApi(instanceId, sessionId, handlers, options) via WebSocket.
    • Handler: onStatus, onInterim, onFinal, onError, onClose.
  2. Neues Hook src/hooks/useAudioStreamTranscription.ts

    • Mikrofon aufnehmen (MediaStream + AudioWorklet oder MediaRecorder).
    • Audio in 100-250ms Chunks an WS senden.
    • Event-Rückkanal verarbeiten (interim/final/status/error).
    • Reconnect-Mechanismus für Mobile-Netzwechsel.
  3. src/pages/views/commcoach/useVoiceController.ts refactor

    • Provider-Interface einführen:
      • browserSpeech (legacy)
      • streamedStt (neu)
    • transcriptPartsRef und liveTranscript aus Serverevents speisen.
    • SILENCE_TIMEOUT_MS nur noch als optionales Guard-Rail, nicht als Kernsegmentierung.
  4. src/pages/views/commcoach/CommcoachDossierView.tsx

    • Keine Verhaltensänderung in Tabs/State nötig.
    • Nur Provider initialisieren und vorhandene Debuganzeige um STT-WS Events erweitern.

B) Gateway Änderungen

  1. Neue WS-Route in modules/features/commcoach/routeFeatureCommcoach.py

    • Beispiel: ws /api/commcoach/{instanceId}/sessions/{sessionId}/stt/stream
    • Ownership-/Session-Checks analog zu message/stream.
    • WebSocket akzeptieren, Audio-Chunks entgegennehmen, Events zurücksenden.
  2. Neuer Service modules/features/commcoach/serviceCommcoachSttStream.py

    • Sessiongebundene Streamverwaltung.
    • Chunk-Buffer, VAD/Silence-Regeln, Segmentbildung.
    • Für jedes finale Segment: processMessage(sessionId, contextId, finalText, interface) aufrufen.
  3. Anpassung modules/features/commcoach/serviceCommcoach.py

    • processAudioMessage bleibt für Legacy/Upload kompatibel.
    • Neue Streaming-Nutzung erfolgt über den neuen STT-Stream-Service.
  4. Erweiterung modules/interfaces/interfaceVoiceObjects.py

    • Neue Methoden ergänzen:
      • startSttStream(...)
      • pushSttAudioChunk(...)
      • stopSttStream(...)
    • Bestehende Methoden (speechToText, textToSpeech) unverändert lassen.

C) STT Worker (self-hosted)

  1. Neuer Connector (z. B.) modules/connectors/connectorVoiceWhisper.py

    • faster-whisper Integration.
    • Modell, Sprache, VAD-Parameter konfigurierbar.
  2. Optional separater Worker-Prozess

    • Entkoppelt Gateway-Latenz von STT-Rechenlast.
    • Kommunikation intern über Queue/IPC oder internen WS.

WS Event Contract (verbindlich)

Client -> Server

  • open
    • { "type": "open", "sessionId": "...", "language": "de-DE", "codec": "pcm16" }
  • audio
    • { "type": "audio", "seq": 123, "chunk": "<base64>" }
  • commit
    • { "type": "commit", "reason": "silence|manual|stateChange" }
  • close
    • { "type": "close" }

Server -> Client

  • status
    • { "type": "status", "label": "Sprache wird erkannt..." }
  • interim
    • { "type": "interim", "text": "...", "segmentId": "..." }
  • final
    • { "type": "final", "text": "...", "segmentId": "...", "confidence": 0.0 }
  • ack
    • { "type": "ack", "seq": 123 }
  • error
    • { "type": "error", "code": "stt_failed", "message": "..." }
  • closed
    • { "type": "closed", "reason": "client|server|timeout" }

Migrationsstrategie (ohne doppelte Logikfalle)

  1. Feature-Flag commcoachVoiceProvider auf Instanzebene

    • Werte: browserSpeech | streamedStt.
  2. Rolloutpfad

    • Schritt 1: intern auf Mobile aktivieren.
    • Schritt 2: nach Stabilisierung global aktivieren.
    • Schritt 3: Browser-STT-Code entfernen.
  3. Expliziter Fehlerpfad

    • Kein stiller Fallback auf Browser-STT, wenn streamedStt aktiv ist.
    • Fehler wird sichtbar im UI (Banner + Debuglog).

Konkrete Taskliste pro Repository

Repo frontend_nyla

  1. commcoachApi.ts: openSttStreamApi ergänzen.
  2. Neues Hook useAudioStreamTranscription.ts implementieren.
  3. useVoiceController.ts: Provider-Abstraktion + streamed Provider integrieren.
  4. CommcoachDossierView.tsx: Provider-Init + Debug-Events anzeigen.
  5. Tests:
    • unit: Segment-Assembler
    • integration: state transitions bei interim/final/error

Repo gateway

  1. WS-Route für STT-Stream in routeFeatureCommcoach.py.
  2. Neuer Stream-Service serviceCommcoachSttStream.py.
  3. interfaceVoiceObjects.py um Streaming-Methoden erweitern.
  4. Neuer Whisper-Connector connectorVoiceWhisper.py.
  5. Telemetrie + Logs:
    • stream open/close
    • first interim latency
    • first final latency
    • reconnect count

Variante A vs. Variante B

Kriterium Variante A: Nur Mobile Variante B: Alle Plattformen
Time-to-first-fix schneller mittel
Komplexität gesamt höher langfristig (2 Stacks) niedriger langfristig (1 Stack)
UX-Konsistenz unterschiedlich je Device einheitlich
Wartung doppelt einfach
Risiko mittelhoch (Divergenz) mittel (einmaliger Umbau)

Empfehlung: Variante B.

Implementierungsplan

Phase 1: Infrastruktur und Prototyp

  1. STT-Worker als eigener Service im Gateway-Umfeld bereitstellen
  2. Streaming-Protokoll definieren (Events: audio, interim, final, status, error, close)
  3. WS-Route im Gateway für CommCoach implementieren
  4. Test-Client mit Beispielaudio aufbauen (ohne UI) zur Last-/Latenzprüfung

Phase 2: Frontend Integration

  1. Neues Modul useAudioStreamTranscription einführen
  2. useVoiceController auf Provider-Abstraktion umstellen:
    • Provider browserSpeech (bestehend)
    • Provider streamedStt (neu)
  3. Transcript-Handling auf Serverevents umstellen:
    • Interim in liveTranscript
    • Final in transcriptParts
  4. State-Transitions unverändert belassen, nur STT-Quelle ersetzen

Phase 3: Segmentierung und Qualität

  1. Serverseitige VAD aktivieren
  2. Segment-Ende sauber in onMessage überführen
  3. Doppelungen/Fragmentverluste mit Session-IDs und Segment-Counter verhindern
  4. Mobile Netzwechsel/WS-Reconnect robust behandeln

Phase 4: Rollout

  1. Feature-Flag: zuerst intern, dann Pilotmandanten
  2. Optionale Stufen:
    • Stufe 1: Nur Mobile
    • Stufe 2: Alle Plattformen
  3. Browser-STT nach Stabilitätsphase vollständig entfernen

Technische Leitplanken

  • Keine stillen Fallbacks, die Fehler verdecken
  • Explizite Fehlerzustände im UI (Mikrofon, Netzwerk, STT nicht verfügbar)
  • Klares Telemetrie-Set:
    • Time to first interim
    • Time to first final
    • Segment-Länge
    • Abbruchgründe
    • WS-Reconnect-Rate

Drittkomponenten

Minimal erforderlich

  • Python Packages (self-hosted):
    • faster-whisper
    • optional webrtcvad
    • Audio-Decode (ffmpeg Laufzeit)

Nicht erforderlich

  • MSFT/Azure Speech
  • Externe STT-SaaS

Risiken und Gegenmassnahmen

  • GPU-Kapazität fehlt: zunächst kleines Modell und Queueing, später GPU-Skalierung
  • Latenz zu hoch: Chunk-Grösse reduzieren, VAD feinjustieren, Modellwahl anpassen
  • Mobile Netz instabil: robustes Reconnect-Handling + Segment-ACKs
  • Drift zwischen UI und Backend: eindeutige Stream-/Segment-IDs und Idempotenzregeln

Fazit

Der Umbau ist vollständig in der eigenen Plattform machbar und löst das Mobile-5-Sekunden-Problem an der Wurzel. Für Wartbarkeit und konsistentes Verhalten ist ein genereller Umbau (Variante B) sinnvoll. Ein stufenweiser Rollout mit Feature-Flag minimiert Risiko.


Implementierungsfreigabe (Ready for Build)

Dieser Abschnitt ist die verbindliche Umsetzungsvorlage.

Verbindliche Entscheidungen

  1. Scope

    • Umsetzung als Variante B (ein Stack fuer Mobile + Desktop).
  2. Transport

    • STT-Eingang erfolgt ueber WebSocket.
    • Text-/Assistant-Antworten bleiben vorerst auf bestehendem SSE-Pfad.
  3. STT-Engine

    • Self-hosted Whisper-Pfad (faster-whisper) wird als neue Connector-Linie eingefuehrt.
    • Bestehender Google-One-Shot-Pfad bleibt nur als Legacy fuer audio/stream.
  4. Fehlerbehandlung

    • Keine stillen Fallbacks.
    • Bei aktivem streamedStt wird ein STT-Fehler sichtbar ins UI gemeldet.
  5. Feature Flag

    • commcoachVoiceProvider = browserSpeech | streamedStt.
    • Default initial: browserSpeech, Pilot: streamedStt.

Verbindliche API Spezifikation

WS Endpoint

  • GET ws /api/commcoach/{instanceId}/sessions/{sessionId}/stt/stream

Client -> Server Nachrichten

  1. open
{
  "type": "open",
  "sessionId": "string",
  "language": "de-DE",
  "codec": "pcm16",
  "sampleRate": 16000,
  "channels": 1
}
  1. audio
{
  "type": "audio",
  "seq": 1,
  "chunk": "base64-encoded-audio-bytes",
  "durationMs": 200
}
  1. commit
{
  "type": "commit",
  "reason": "silence"
}
  1. close
{
  "type": "close"
}

Server -> Client Nachrichten

  1. status
{
  "type": "status",
  "label": "Sprache wird erkannt..."
}
  1. ack
{
  "type": "ack",
  "seq": 1
}
  1. interim
{
  "type": "interim",
  "segmentId": "seg-uuid",
  "text": "teiltranskript"
}
  1. final
{
  "type": "final",
  "segmentId": "seg-uuid",
  "text": "finales segment",
  "confidence": 0.91
}
  1. error
{
  "type": "error",
  "code": "stt_failed",
  "message": "Transkription fehlgeschlagen"
}
  1. closed
{
  "type": "closed",
  "reason": "server"
}

Orchestrierungsregeln (verbindlich)

  1. useVoiceController bleibt Owner der Zustandsmaschine.
  2. streamedStt liefert nur Transkript-Ereignisse, steuert keine State-Transitions direkt.
  3. Nur final Texte duerfen onMessage(...) triggern.
  4. interim aktualisiert nur liveTranscript.
  5. Bei ttsPlaying wird STT-Stream pausiert/geschlossen.
  6. Bei ttsEnded wird STT-Stream wieder geoeffnet.
  7. muted=true blockiert Senden von audio Chunks.

Konkrete Datei- und Funktionsaenderungen

Frontend frontend_nyla

  1. src/api/commcoachApi.ts

    • Neue Funktion openSttStreamApi(instanceId, sessionId, handlers, options).
    • Rueckgabeobjekt mit sendAudioChunk, sendCommit, close.
  2. src/hooks/useAudioStreamTranscription.ts (neu)

    • Intern:
      • _startMicCapture()
      • _stopMicCapture()
      • _encodePcm16Chunk()
      • _pushChunkToWs()
    • Extern:
      • startStream()
      • stopStream()
      • commitSegment(reason)
  3. src/pages/views/commcoach/useVoiceController.ts

    • Provider-Layer einfuegen:
      • browserSpeechProvider
      • streamedSttProvider
    • streamedSttProvider auf neues Hook mappen.
    • Bestehende State-Transitionen unveraendert lassen.
  4. src/pages/views/commcoach/CommcoachDossierView.tsx

    • Debugpanel um STT-WS Events erweitern (STT-OPEN, STT-INTERIM, STT-FINAL, STT-ERR, STT-CLOSE).

Gateway gateway

  1. modules/features/commcoach/routeFeatureCommcoach.py

    • Neue WS-Route .../stt/stream.
    • Auth/Ownership-Pruefung identisch zu bestehenden Session-Endpunkten.
  2. modules/features/commcoach/serviceCommcoachSttStream.py (neu)

    • Session-Stream-Manager:
      • _openSessionStream()
      • _handleAudioChunk()
      • _flushSegment()
      • _closeSessionStream()
    • Final-Segmente an CommcoachService.processMessage(...) uebergeben.
  3. modules/interfaces/interfaceVoiceObjects.py

    • Streaming API erweitern:
      • startSttStream(...)
      • pushSttAudioChunk(...)
      • finalizeSttSegment(...)
      • stopSttStream(...)
  4. modules/connectors/connectorVoiceWhisper.py (neu)

    • Whisper-basierte Implementierung fuer Streaming-Segmente.
    • Config fuer Modellgroesse, Sprache, VAD.

Reihenfolge fuer die Umsetzung

  1. Gateway WS Route + Dummy-Events (ohne echte STT).
  2. Frontend WS Client + Mikrofondaten senden.
  3. Connector/Whisper Integration im Gateway.
  4. Segmentierung und final -> processMessage.
  5. Feature-Flag Integration und Pilot-Rollout.
  6. Legacy-Bereinigung.

Abnahmekriterien (Definition of Done)

  1. Mobile Stabilitaet

    • 60 Sekunden durchgehendes Sprechen ohne erzwungenen 5-Sekunden-Reset.
  2. Textintegritaet

    • Keine abgeschnittenen Saetze zwischen Segmenten.
    • Keine Duplikate bei finalen Segmenten.
  3. State Machine Integritaet

    • Keine User-Transkripte waehrend botSpeaking.
    • muted blockiert Audio-Chunks sofort.
  4. Fehlertransparenz

    • STT- oder WS-Fehler werden im UI sichtbar angezeigt.
    • Kein stiller Fallback auf Browser-STT im streamedStt Modus.
  5. Performance

    • Time to first interim <= 1200 ms (WLAN Referenz).
    • Time to first final <= 2500 ms fuer kurze Saetze.

Testplan (verbindlich)

  1. Unit Tests

    • Segment-Assembler und Seq-Handling.
    • State Transition Tests fuer ttsPlaying, ttsEnded, muted.
  2. Integration Tests

    • WS Open -> Audio -> Interim -> Final -> Close.
    • Reconnect nach Netzunterbruch.
  3. Manuelle Geraetetests

    • iOS Safari (aktuell + 1 Vorversion).
    • Android Chrome (aktuell + 1 Vorversion).
    • Desktop Chrome/Edge.
  4. Regression

    • Bestehende Text-SSE Flows unveraendert funktionsfaehig.
    • TTS Wiedergabe und Stop/Resume weiterhin stabil.

Rollout und Backout

  1. Rollout

    • Feature-Flag pilotweise pro Instanz aktivieren.
    • Metriken mindestens 3 Tage beobachten.
  2. Backout

    • Bei kritischen Fehlern Flag auf browserSpeech zuruecksetzen.
    • Keine DB-Migrationen erforderlich.

Aufwandsschaetzung

  • Gateway WS + Stream Service: 2-3 Tage
  • Whisper Connector + Tuning: 2-4 Tage
  • Frontend Hook + Provider Refactor: 2-3 Tage
  • Tests + Pilot-Hardening: 2-3 Tage

Gesamt: 8-13 Arbeitstage fuer produktionsreife Erstversion.

Reuse Snippet (AI Workspace und weitere Views)

Der Hook useSpeechAudioCapture ist generisch und kann ausserhalb von CommCoach wiederverwendet werden. Er kapselt Mikrofonzugriff, VAD, Segmentierung und Chunk-Emission.

Beispielintegration in einer anderen View:

import React, { useState } from 'react';
import { useSpeechAudioCapture } from '../../hooks/useSpeechAudioCapture';

export default function WorkspaceVoiceInput() {
  const [isMicActive, setIsMicActive] = useState(false);
  const [isMuted, setIsMuted] = useState(false);

  const speech = useSpeechAudioCapture(
    {
      // Controller-Bedingung: nur aufnehmen wenn aktiv und nicht stumm
      isCaptureAllowed: () => isMicActive && !isMuted,

      // Laufende Audio-Chunks an den eigenen Stream senden
      onChunk: async (audioChunk) => {
        await wsSendChunk(audioChunk); // eigene WS/API Implementierung
      },

      // Segmentabschluss bei Stille oder manuellem Stop
      onSegment: async ({ reason }) => {
        wsSendCommit(reason); // z. B. 'silence' oder 'manual'
      },

      // Optionales Debugging
      onDebug: (tag, info) => console.log(tag, info),
      onError: (error) => console.error('Speech capture error', error),
    },
    {
      silenceTimeoutMs: 1200,
      vadRmsThreshold: 0.03,
      vadIntervalMs: 120,
      minSegmentDurationMs: 450,
      recordingChunkMs: 250,
    },
  );

  const startMic = async () => {
    setIsMicActive(true);
    await speech.startCapture();
  };

  const stopMic = () => {
    setIsMicActive(false);
    speech.stopCapture('manual', true);
  };

  return (
    <div>
      <button onClick={startMic}>Mic Start</button>
      <button onClick={stopMic}>Mic Stop</button>
      <button onClick={() => setIsMuted(v => !v)}>{isMuted ? 'Unmute' : 'Mute'}</button>
      <div>{speech.liveTranscript || 'Mikrofon bereit'}</div>
    </div>
  );
}

Minimalanforderungen fuer Reuse:

  • isCaptureAllowed muss den lokalen UI-State korrekt abbilden.
  • onChunk und onSegment muessen auf den Ziel-Transport (WS/SSE/API) gemappt werden.
  • stopCapture(..., true) soll bei View-Wechsel oder Unmount aufgerufen werden, um Mic sauber freizugeben.