# Conversational AI 2.0: Natürliches Turn-Taking und Interruption Handling **Meta-Description:** Fortgeschrittene Techniken für natürliche Sprachinteraktion. Turn-Taking-Modelle, intelligente Unterbrechungserkennung und Full-Duplex-Kommunikation. **Keywords:** Conversational AI, Turn-Taking, Interruption Handling, Full-Duplex, Voice Agent, Natural Conversation, VAD, TRP --- ## Einführung Die größte Herausforderung für Voice AI ist nicht die Spracherkennung – es ist das **Timing**. Wann ist der User fertig? Wann darf der Agent sprechen? Wie reagiert man auf Unterbrechungen? > "OpenAIs neue Audio-Architektur 2026 zielt auf niedrigere Latenz und natürlicheres Back-and-Forth ab – weg vom Walkie-Talkie-Modell." --- ## Das Problem mit Silence-Based Detection ### Warum einfache Stille nicht funktioniert ``` ┌─────────────────────────────────────────────────────────────┐ │ SILENCE-BASED VS. INTELLIGENT DETECTION │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Silence-Based: │ │ User: "Ich möchte..." [500ms Pause] "...einen Kaffee" │ │ Agent: [Unterbricht bei Pause] "Wie kann ich helfen?" │ │ ❌ Frustrierend für User │ │ │ │ Intelligent Turn-Taking: │ │ User: "Ich möchte..." [500ms Pause] "...einen Kaffee" │ │ Agent: [Wartet auf semantisches Ende] │ │ User: "...mit Milch." │ │ Agent: "Einen Kaffee mit Milch, kommt sofort!" │ │ ✅ Natürliche Konversation │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### Transition-Relevance Places (TRPs) Natürliche Gespräche haben **Übergangspunkte**, die nicht nur durch Stille definiert sind: | Signal | Beispiel | Zuverlässigkeit | |--------|----------|-----------------| | **Syntaktisch** | Vollständiger Satz | Hoch | | **Prosodisch** | Fallende Intonation | Mittel | | **Pragmatisch** | Frage gestellt | Hoch | | **Semantisch** | Gedanke abgeschlossen | Mittel | | **Stille** | > 700ms Pause | Niedrig | --- ## Turn-Taking Architektur ```typescript // src/turn-taking/detector.ts interface TurnTakingSignals { // Audio-basiert silenceDurationMs: number; pitchContour: 'rising' | 'falling' | 'flat'; speechRate: number; // Text-basiert (von ASR) lastUtterance: string; isQuestion: boolean; isComplete: boolean; // Kontext conversationHistory: Message[]; expectedResponseType: 'answer' | 'continuation' | 'acknowledgment'; } interface TurnDecision { action: 'wait' | 'respond' | 'backchannel'; confidence: number; reasoning: string; } class IntelligentTurnDetector { private model: TurnPredictionModel; constructor() { this.model = new TurnPredictionModel(); } async predict(signals: TurnTakingSignals): Promise { // Multi-Signal-Analyse const features = this.extractFeatures(signals); // Heuristiken für schnelle Entscheidungen if (signals.silenceDurationMs > 2000) { return { action: 'respond', confidence: 0.95, reasoning: 'Long silence' }; } if (signals.isQuestion && signals.silenceDurationMs > 300) { return { action: 'respond', confidence: 0.9, reasoning: 'Question asked' }; } // ML-basierte Prediction für komplexe Fälle const prediction = await this.model.predict(features); return prediction; } private extractFeatures(signals: TurnTakingSignals) { return { // Normalisierte Features für ML silenceNormalized: Math.min(signals.silenceDurationMs / 2000, 1), pitchFalling: signals.pitchContour === 'falling' ? 1 : 0, syntacticCompleteness: this.analyzeSyntax(signals.lastUtterance), semanticCompleteness: this.analyzeSemantics(signals.lastUtterance), questionProbability: signals.isQuestion ? 1 : 0, // ...weitere Features }; } private analyzeSyntax(utterance: string): number { // Einfache Heuristik: Endet mit Satzzeichen? if (/[.!?]$/.test(utterance.trim())) return 1; // Unvollständiger Satz if (/\b(und|aber|oder|weil|dass)\s*$/.test(utterance)) return 0; return 0.5; } private analyzeSemantics(utterance: string): number { // Kann mit LLM verbessert werden const incompletePatterns = [ /ich möchte\s*$/i, /ich brauche\s*$/i, /können Sie\s*$/i, /wie wäre es\s*$/i ]; for (const pattern of incompletePatterns) { if (pattern.test(utterance)) return 0; } return 0.7; } } ``` --- ## Interruption Handling ### Typen von Unterbrechungen ```typescript enum InterruptionType { // Kooperativ AGREEMENT = 'agreement', // "Ja, genau!" ASSISTANCE = 'assistance', // User hilft Agent beim Formulieren CLARIFICATION = 'clarification', // "Moment, was meinen Sie?" // Disruptiv DISAGREEMENT = 'disagreement', // "Nein, das stimmt nicht" TOPIC_CHANGE = 'topic_change', // "Egal, andere Frage..." CORRECTION = 'correction', // "Nicht 3, ich sagte 5" // Technisch BARGE_IN = 'barge_in' // User will Agent stoppen } interface InterruptionEvent { type: InterruptionType; timestamp: number; userUtterance: string; agentWasSpeaking: boolean; agentUtteranceProgress: number; // 0-1 } ``` ### Intelligente Reaktionen ```typescript // src/turn-taking/interruption-handler.ts class InterruptionHandler { async handleInterruption( event: InterruptionEvent, agent: VoiceAgent ): Promise { // 1. Agent sofort stoppen await agent.stopSpeaking(); // 2. Typ klassifizieren const type = await this.classifyInterruption(event); // 3. Entsprechend reagieren switch (type) { case InterruptionType.AGREEMENT: // Kurz bestätigen, dann weitermachen await agent.say("Genau."); await agent.continueFromLastPoint(); break; case InterruptionType.CLARIFICATION: // Erklärung geben await agent.say("Lass mich das erklären..."); await agent.clarifyLastStatement(); break; case InterruptionType.CORRECTION: // Korrektur akzeptieren await agent.say("Entschuldigung, ich korrigiere..."); await agent.processUserInput(event.userUtterance); break; case InterruptionType.BARGE_IN: // Komplett neuen Input verarbeiten await agent.processUserInput(event.userUtterance); break; case InterruptionType.TOPIC_CHANGE: // Context wechseln await agent.say("Okay, zum neuen Thema..."); await agent.processUserInput(event.userUtterance); break; } } private async classifyInterruption( event: InterruptionEvent ): Promise { // Schnelle Heuristiken const text = event.userUtterance.toLowerCase(); if (/^(ja|genau|richtig|stimmt)/.test(text)) { return InterruptionType.AGREEMENT; } if (/^(nein|falsch|nicht|stop)/.test(text)) { return InterruptionType.DISAGREEMENT; } if (/^(was|wie|warum|moment)/.test(text)) { return InterruptionType.CLARIFICATION; } // LLM für komplexere Fälle return await this.classifyWithLLM(event); } } ``` --- ## Full-Duplex Communication ### Das NVIDIA PersonaPlex Konzept ```typescript // Full-Duplex: Agent kann gleichzeitig hören und sprechen class FullDuplexAgent { private isSpeaking = false; private isListening = true; // Immer an private audioBuffer: Buffer[] = []; async processAudioStream( input: AsyncIterable, output: (chunk: Buffer) => void ) { // Parallele Verarbeitung const [transcription, speechOutput] = await Promise.all([ this.transcribeStream(input), this.generateSpeech() ]); // Während Agent spricht, weiter zuhören for await (const audioChunk of input) { // VAD prüfen if (this.detectVoiceActivity(audioChunk)) { // User spricht während Agent spricht if (this.isSpeaking) { await this.handleOverlap(audioChunk); } } } } private async handleOverlap(userAudio: Buffer) { // Analyse: Ist es eine Unterbrechung? const energy = this.calculateEnergy(userAudio); if (energy > this.thresholdForInterruption) { // Lautstärke des Agents reduzieren this.reduceAgentVolume(0.3); // Warten ob User weiterspricht await this.waitForUserIntent(500); if (this.userContinuesSpeaking) { // Komplett stoppen await this.stopSpeaking(); } else { // War nur Backchannel, weitermachen this.restoreAgentVolume(); } } } } ``` --- ## Backchannel Responses ### Natürliche Bestätigungen während User spricht ```typescript interface BackchannelConfig { enabled: boolean; responses: string[]; triggerInterval: number; // ms maxPerTurn: number; } class BackchannelGenerator { private config: BackchannelConfig = { enabled: true, responses: ['Mhm', 'Ja', 'Verstehe', 'Okay', 'Aha'], triggerInterval: 3000, maxPerTurn: 3 }; private backchannelCount = 0; private lastBackchannel = 0; shouldGenerateBackchannel( signals: TurnTakingSignals ): { should: boolean; response: string } { const now = Date.now(); // Limits prüfen if (this.backchannelCount >= this.config.maxPerTurn) { return { should: false, response: '' }; } if (now - this.lastBackchannel < this.config.triggerInterval) { return { should: false, response: '' }; } // Trigger-Bedingungen const shouldTrigger = signals.silenceDurationMs > 200 && signals.silenceDurationMs < 500 && !signals.isComplete && signals.lastUtterance.length > 20; if (shouldTrigger) { this.backchannelCount++; this.lastBackchannel = now; const response = this.selectResponse(signals); return { should: true, response }; } return { should: false, response: '' }; } private selectResponse(signals: TurnTakingSignals): string { // Kontext-abhängige Auswahl if (signals.lastUtterance.includes('Problem')) { return 'Oh je'; } if (signals.lastUtterance.includes('?')) { return 'Mhm'; } // Random für Variation const idx = Math.floor(Math.random() * this.config.responses.length); return this.config.responses[idx]; } resetForNewTurn() { this.backchannelCount = 0; } } ``` --- ## Production-Ready Implementation ```typescript // src/voice-agent-v2.ts import { DeepgramStreamer } from './services/deepgram'; import { ElevenLabsStreamer } from './services/elevenlabs'; import { IntelligentTurnDetector } from './turn-taking/detector'; import { InterruptionHandler } from './turn-taking/interruption-handler'; import { BackchannelGenerator } from './turn-taking/backchannel'; export class ConversationalVoiceAgent { private deepgram = new DeepgramStreamer(); private elevenlabs = new ElevenLabsStreamer(); private turnDetector = new IntelligentTurnDetector(); private interruptionHandler = new InterruptionHandler(); private backchannel = new BackchannelGenerator(); private state: 'listening' | 'processing' | 'speaking' = 'listening'; private currentUtterance = ''; async start( audioInput: AsyncIterable, onAudioOutput: (chunk: Buffer) => void ) { await this.deepgram.startStream( { model: 'nova-2', language: 'de', smart_format: true, interim_results: true, endpointing: 300 // Schnelles Feedback }, async (text, isFinal) => { this.currentUtterance = text; // Interim: Prüfe auf Unterbrechung if (!isFinal && this.state === 'speaking') { await this.interruptionHandler.handleInterruption( { type: InterruptionType.BARGE_IN, timestamp: Date.now(), userUtterance: text, agentWasSpeaking: true, agentUtteranceProgress: 0.5 }, this ); return; } // Final: Turn-Taking-Entscheidung if (isFinal) { const decision = await this.turnDetector.predict({ silenceDurationMs: 0, pitchContour: 'falling', speechRate: 1, lastUtterance: text, isQuestion: text.includes('?'), isComplete: true, conversationHistory: [], expectedResponseType: 'answer' }); if (decision.action === 'respond') { this.state = 'processing'; await this.generateResponse(text, onAudioOutput); } } else { // Backchannel prüfen const bc = this.backchannel.shouldGenerateBackchannel({ silenceDurationMs: 300, pitchContour: 'flat', speechRate: 1, lastUtterance: text, isQuestion: false, isComplete: false, conversationHistory: [], expectedResponseType: 'continuation' }); if (bc.should) { // Leises Backchannel ohne State-Wechsel await this.speakQuietly(bc.response, onAudioOutput); } } } ); for await (const chunk of audioInput) { this.deepgram.sendAudio(chunk); } } private async speakQuietly( text: string, onOutput: (chunk: Buffer) => void ) { // Niedrige Lautstärke für Backchannel for await (const chunk of this.elevenlabs.streamSpeech(text, { voiceId: 'default', modelId: 'eleven_flash_v2_5', stability: 0.3, similarityBoost: 0.5, latencyOptimization: 4 })) { onOutput(this.reduceVolume(chunk, 0.4)); } } private reduceVolume(audio: Buffer, factor: number): Buffer { // PCM volume reduction const samples = new Int16Array(audio.buffer); for (let i = 0; i < samples.length; i++) { samples[i] = Math.round(samples[i] * factor); } return Buffer.from(samples.buffer); } async stopSpeaking() { // Implementation für sofortigen Stop this.state = 'listening'; } private async generateResponse( input: string, onOutput: (chunk: Buffer) => void ) { this.state = 'speaking'; // LLM + TTS Pipeline... this.state = 'listening'; this.backchannel.resetForNewTurn(); } } ``` --- ## Metriken für Turn-Taking Qualität | Metrik | Zielwert | Beschreibung | |--------|----------|--------------| | **Turn-Taking Latenz** | < 500ms | Zeit von User-Ende bis Agent-Start | | **False Interruptions** | < 5% | Agent unterbricht User fälschlich | | **Missed TRPs** | < 10% | Agent verpasst Übergangspunkte | | **Interruption Recovery** | < 1s | Zeit bis normale Konversation | --- ## Fazit Natürliches Turn-Taking erfordert: 1. **Multi-Signal-Analyse**: Nicht nur Stille, sondern Syntax + Semantik + Prosodie 2. **Intelligente Unterbrechungserkennung**: Kooperativ vs. Disruptiv unterscheiden 3. **Backchannel-Responses**: Aktives Zuhören signalisieren 4. **Full-Duplex**: Gleichzeitig hören und sprechen können Das Walkie-Talkie-Modell ist Geschichte. --- ## Bildprompts 1. "Two people in natural conversation with speech bubbles overlapping, timing visualization, warm illustration style" 2. "AI voice assistant with sound waves showing bidirectional flow, full-duplex concept, modern tech art" 3. "Conversation flow diagram with turn-taking points marked, linguistic analysis visualization" --- ## Quellen - [NVIDIA PersonaPlex Research](https://research.nvidia.com/labs/adlr/personaplex/) - [Amazon Nova 2 Sonic Announcement](https://aws.amazon.com/blogs/aws/introducing-amazon-nova-2-sonic-next-generation-speech-to-speech-model-for-conversational-ai/) - [Retell AI Turn-Taking Model](https://www.retellai.com/blog/how-retell-ais-turn-taking-model-ensures-seamless-calls) - [Interruption Handling Research (arXiv)](https://arxiv.org/html/2501.01568v1) - [Turn-Taking in Conversational Systems (MDPI)](https://www.mdpi.com/2227-7080/13/12/591)