AI-agenter · Lektion

Optimering af latenstid for voice-agenter

Streaming af TTS, opdeling af responses og minimering af latenstiden til første ord.

Lektion 4 af 413 trin

Optimering af latenstid for voice-agenter er en gratis AI-agenter-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i AI-agenter, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. AI-agenter-kurset indeholder 4 lektioner i alt.

Hvorfor latenstid er vigtig for tale

I tekstchat er en forsinkelse på 3 sekunder acceptabel. I en talesamtale føles alt over 1,5 sekunder unaturligt og bryder samtaleforløbet.

Latenstiden for tale består af tre hoveddele: transskriptionstid (STT), LLM-behandlingstid (TTFT + generering) og TTS-syntesetid. Optimering af hver del forstærker den samlede effekt og giver en markant bedre brugeroplevelse.

Måling af latenstidsbudgettet

Før du optimerer, skal du måle hver komponent. Instrumentér cyklussen med tidsmålinger, så du ved, hvor tiden faktisk bruges.

import time

def voice_loop_timed():
    timing = {}

    # 1. Record
    t0 = time.perf_counter()
    audio, sr = record_until_silence()
    timing['record'] = time.perf_counter() - t0

    # 2. Transcribe
    t0 = time.perf_counter()
    audio_path = audio_to_file(audio, sr)
    user_text = transcribe_file(audio_path)
    timing['transcription'] = time.perf_counter() - t0

    # 3. LLM
    t0 = time.perf_counter()
    agent_text = agent.respond(user_text)
    timing['llm'] = time.perf_counter() - t0

    # 4. TTS
    t0 = time.perf_counter()
    say(agent_text)
    timing['tts'] = time.perf_counter() - t0

    print('Latency breakdown:')
    total = sum(timing.values())
    for step, duration in timing.items():
        print(f'  {step:15} {duration*1000:.0f}ms ({duration/total*100:.0f}%)')

Streaming af TTS: Afspil under generering

Den største enkeltstående latenstidsgevinst kommer fra streaming af TTS. I stedet for at vente på, at hele lyden er syntetiseret, skal du begynde at afspille den første lydbid efter ca. 200 ms, mens resten genereres samtidig.

import threading
import queue
import sounddevice as sd
import numpy as np
import io

def stream_tts_and_play(text, voice='nova'):
    audio_queue = queue.Queue()

    def synthesize():
        from openai import OpenAI
        import os
        client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
        with client.audio.speech.with_streaming_response.create(
            model='tts-1', voice=voice, input=text
        ) as response:
            for chunk in response.iter_bytes(chunk_size=4096):
                audio_queue.put(chunk)
        audio_queue.put(None)  # sentinel

    synth_thread = threading.Thread(target=synthesize, daemon=True)
    synth_thread.start()

    # Collect and play (buffering first 2 chunks for smooth start)
    audio_buffer = b''
    min_buffer = 8192
    import pygame
    pygame.mixer.init()

    while True:
        chunk = audio_queue.get()
        if chunk is None:
            break
        audio_buffer += chunk
        if len(audio_buffer) >= min_buffer:
            # play buffer ...
            pass  # simplified — real impl streams to sounddevice

    synth_thread.join()

Streaming af TTS sætning for sætning

Den mest praktiske tilgang til streaming er at opdele LLM-svaret i sætninger og syntetisere og afspille dem én ad gangen. Den første sætning begynder at afspilles efter ca. 300 ms.

import re
import concurrent.futures

def split_sentences(text):
    return [s.strip() for s in re.split(r'(?<=[.!?])\s+', text) if s.strip()]

def tts_and_play_streaming(text, voice='nova'):
    sentences = split_sentences(text)
    if not sentences:
        return

    # Prefetch next sentence while current is playing
    executor = concurrent.futures.ThreadPoolExecutor(max_workers=2)

    # Kick off synthesis of first sentence
    futures = []
    for sentence in sentences:
        futures.append(executor.submit(cached_tts, sentence, voice))

    # Play each sentence as soon as it's ready
    for future in futures:
        audio_path = future.result(timeout=10)
        play_audio_file(audio_path)

    executor.shutdown(wait=False)

# Key insight: while sentence 1 plays (~2 seconds), sentence 2 is being synthesized in parallel

Optimering af TTFT: Tid til første token

TTFT (tid til første token) er forsinkelsen mellem at sende LLM-anmodningen og modtage det første token i svaret. Optimering af TTFT betyder, at brugerne hører begyndelsen af svaret hurtigere.

Brug LLM-svar via streaming, og send tokens videre til TTS, så snart der registreres en sætningsgrænse.

import openai
import os

client = openai.OpenAI(api_key=os.getenv('OPENAI_API_KEY'))

def stream_llm_to_tts(user_text, conversation_history, voice='nova'):
    buffer = ''

    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=conversation_history + [{'role': 'user', 'content': user_text}],
        stream=True  # streaming enabled
    )

    for chunk in stream:
        delta = chunk.choices[0].delta.content or ''
        buffer += delta

        # Check if a sentence is complete
        if buffer.endswith(('.', '!', '?')) and len(buffer) > 20:
            sentence = buffer.strip()
            print(f'Queuing TTS: {sentence[:50]}')
            # Synthesize and play immediately (non-blocking)
            audio_path = cached_tts(sentence, voice)
            play_audio_file(audio_path)
            buffer = ''

    # Flush remaining buffer
    if buffer.strip():
        audio_path = cached_tts(buffer.strip(), voice)
        play_audio_file(audio_path)

Forgenerering af almindelige svar

Nogle af agentens svar er forudsigelige: hilsner, fejlmeddelelser og tænkeindikatorer ("Lad mig tjekke det for dig."). Forgenerér deres lyd ved opstart, så den afspilles øjeblikkeligt uden latenstid.

import os

PRE_GENERATED = {
    'greeting':      'Hello! How can I help you today?',
    'thinking':      'Let me look that up for you.',
    'not_found':     'I could not find information on that. Could you rephrase?',
    'error':         'Sorry, something went wrong. Please try again.',
    'goodbye':       'Goodbye! Have a great day.',
    'clarify':       'Could you give me a bit more detail?',
    'working_on_it': 'Working on it, this may take a moment.'
}

pre_gen_cache = {}

def prewarm_responses(voice='nova'):
    for key, text in PRE_GENERATED.items():
        audio_path = cached_tts(text, voice=voice)
        pre_gen_cache[key] = audio_path
    print(f'Pre-generated {len(pre_gen_cache)} common responses')

def instant_response(key):
    path = pre_gen_cache.get(key)
    if path:
        play_audio_file(path)
    else:
        say(PRE_GENERATED.get(key, ''))

# Usage: while LLM is thinking, play a stall message instantly
instant_response('thinking')

Latenstid for lokal TTS kontra skybaseret TTS

Skybaseret TTS tilføjer netværkets tur-retur-tid (~100-300 ms). For korte svar eller hyppige sætninger kan en lokal TTS-motor være hurtigere – på bekostning af stemmekvaliteten.

pyttsx3 er den enkleste TTS-løsning uden internet til Python.

import pyttsx3
import time

# Local TTS with pyttsx3
def local_tts(text, voice_index=0, rate=175):
    engine = pyttsx3.init()
    voices = engine.getProperty('voices')
    if voice_index < len(voices):
        engine.setProperty('voice', voices[voice_index].id)
    engine.setProperty('rate', rate)  # words per minute
    t0 = time.perf_counter()
    engine.say(text)
    engine.runAndWait()
    print(f'Local TTS latency: {(time.perf_counter()-t0)*1000:.0f}ms')

# Comparison (rough benchmarks):
# pyttsx3 (local):     ~50ms start, robotic quality
# OpenAI TTS-1:        ~200-400ms, good quality
# ElevenLabs turbo:    ~150-250ms, excellent quality
# OpenAI TTS-1-hd:     ~400-800ms, best quality

# Recommendation:
# Real-time voice agent -> OpenAI TTS-1 or ElevenLabs turbo
# Quality recording     -> OpenAI TTS-1-hd or ElevenLabs standard

Valg af en hurtigere LLM-model

Valget af model påvirker TTFT markant. GPT-4o-mini er 5-10 gange hurtigere end GPT-4o til de fleste opgaver for stemmeagenter. Brug den mindste model, der opfylder kvalitetskravene.

# Model latency comparison (rough benchmarks for voice agent use case):
MODEL_BENCHMARKS = {
    'gpt-4o-mini':   {'ttft_ms': 300,  'quality': 'good',      'cost': 'very low'},
    'gpt-4o':        {'ttft_ms': 800,  'quality': 'excellent',  'cost': 'medium'},
    'claude-haiku':  {'ttft_ms': 250,  'quality': 'good',       'cost': 'very low'},
    'claude-sonnet': {'ttft_ms': 600,  'quality': 'excellent',  'cost': 'medium'},
    'llama3-8b':     {'ttft_ms': 100,  'quality': 'decent',     'cost': 'free (local)'},
}

# Strategy: use fast model for simple factual queries,
# fall back to powerful model for complex reasoning
def select_model(question):
    # Short, simple questions -> fast model
    if len(question.split()) < 15:
        return 'gpt-4o-mini'
    # Complex, multi-step -> better model
    if any(kw in question.lower() for kw in ['analyze', 'compare', 'explain why', 'write a']):
        return 'gpt-4o'
    return 'gpt-4o-mini'

if __name__ == '__main__':
    for q in ['What time is it in Tokyo?', 'Analyze why sales dropped last quarter and compare to competitors']:
        print(f'{select_model(q)!r} chosen for: "{q}"')

Caching af svar på gentagne spørgsmål

Brugere stiller ofte de samme eller lignende spørgsmål igen og igen. Læg LLM- og TTS-svar for identiske forespørgsler i cache, så de kan leveres øjeblikkeligt ved gentagelser.

import hashlib
import json

RESPONSE_CACHE = {}  # in production: use Redis with TTL

def get_cache_key(user_text):
    # Normalize: lowercase, strip punctuation
    import re
    normalized = re.sub(r'[^a-z0-9 ]', '', user_text.lower()).strip()
    return hashlib.md5(normalized.encode()).hexdigest()

def cached_agent_respond(user_text, voice='nova'):
    key = get_cache_key(user_text)

    if key in RESPONSE_CACHE:
        print('Response cache hit!')
        entry = RESPONSE_CACHE[key]
        play_audio_file(entry['audio_path'])
        return entry['text']

    # Cache miss
    text = agent.respond(user_text)
    audio_path = cached_tts(text, voice=voice)

    RESPONSE_CACHE[key] = {'text': text, 'audio_path': audio_path}
    play_audio_file(audio_path)
    return text

Målsætning for latenstid fra ende til ende

Når alle optimeringer er på plads, bør en velbygget stemmeagent kunne opnå en latenstid på under 1 sekund fra brugerens tale slutter, til agentens svar begynder.

# Target latency budget breakdown (1000ms total):

LATENCY_BUDGET = {
    'silence_detection_end':   0,    # user stops speaking
    'audio_processing':        50,   # RMS check, noise gate
    'transcription_whisper':   400,  # STT API call
    'llm_ttft':                300,  # time to first token (gpt-4o-mini)
    'tts_first_sentence':      200,  # first sentence synthesized
    'playback_start':          1000  # TOTAL: user hears response within 1 second
}

# Optimizations applied:
# - Streaming LLM responses (saves 200-500ms vs waiting for full response)
# - Sentence-by-sentence TTS (play while rest generates)
# - gpt-4o-mini instead of gpt-4o (saves 500ms TTFT)
# - TTS cache for common phrases (saves 200ms on cached phrases)
# - Pre-generated stall messages ('Let me check...' plays instantly)

print('Target: < 1000ms from speech end to first audio byte played')
print('Typical with optimizations: 600-900ms')

Overlapning af asynkron transskription

Mens agenten genererer sit svar og taler, skal du straks begynde at optage brugerens næste inddata. Ved at overlappe proceskædens faser reduceres den døde tid mellem turene.

import threading
import time

class PipelineOverlapAgent:
    def __init__(self):
        self.next_audio = None
        self.recording_thread = None

    def start_background_recording(self):
        def record():
            self.next_audio = record_until_silence()
        self.recording_thread = threading.Thread(target=record, daemon=True)
        self.recording_thread.start()

    def get_recorded_audio(self, timeout=30):
        if self.recording_thread:
            self.recording_thread.join(timeout=timeout)
        audio = self.next_audio
        self.next_audio = None
        return audio

    def conversation_turn(self, audio, sr):
        # Transcribe and run agent
        text = transcribe_file(audio_to_file(audio, sr))
        if not text.strip():
            return

        # Start recording NEXT turn BEFORE speaking
        # (user can start speaking as soon as agent starts)
        response = agent.respond(text)

        # Start next recording while speaking
        self.start_background_recording()

        # Speak current response
        speak_with_interrupt(response)

        # Next audio is already being captured in background
        return self.get_recorded_audio()

Videnstjek

Hvilken enkelt optimering giver typisk den største reduktion i latenstiden for stemmeagenter?

Opsummering: Optimering af latenstid for stemmeagenter

Stakken til optimering af stemmelatenstid: streaming af LLM-svar (start TTS ved sætningsgrænser, efterhånden som tokens ankommer), afspilning af TTS sætning for sætning (afspil, mens syntesen foregår), caching af TTS (øjeblikkelig afspilning af gentagne sætninger), forudgenererede almindelige svar (hilsner og venteformuleringer) og valg af en hurtig LLM-model (gpt-4o-mini til simple forespørgsler).

Mål: under 1 sekund fra brugerens tale slutter, til den første lydbit. Mål hver komponent — transskription udgør ofte 40 % af den samlede latenstid. Lokal TTS bytter kvalitet for hastighed; cloudbaseret TTS med streaming af sætninger giver den bedste balance for de fleste agenter.

Gratis at komme i gang

Lær AI-agenter med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
60
Lektioner
239

Ofte stillede spørgsmål

Er lektionen “Optimering af latenstid for voice-agenter” gratis?

Ja — hele teksten til “Optimering af latenstid for voice-agenter” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af AI-agenter-kurset, skal du opgradere til CoddyKit PRO. AI-agenter-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Optimering af latenstid for voice-agenter”?

Streaming af TTS, opdeling af responses og minimering af latenstiden til første ord. Du øver dig i AI-agenter med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på AI-agenter?

Der kræves ingen tidligere erfaring. AI-agenter på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.

Hvor lang tid tager lektionen “Optimering af latenstid for voice-agenter”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne AI-agenter-lektion?

Ja. Alle AI-agenter-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Speech-to-text med Whisper og Deepgram
  2. Text-to-speech i agentresponses
  3. Opbygning af en voice-samtalesløjfe
  4. Optimering af latenstid for voice-agenter
← Tilbage til AI-agenter