0Pricing
AI Agents · Lekcja

Optymalizacja opóźnień agentów głosowych

Strumieniowanie TTS, dzielenie odpowiedzi na fragmenty i minimalizowanie opóźnienia do pierwszego słowa.

Optymalizacja opóźnień agentów głosowych to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.

Dlaczego opóźnienie ma znaczenie w rozmowie głosowej

Na czacie tekstowym 3-sekundowe opóźnienie jest akceptowalne. W rozmowie głosowej wszystko powyżej 1,5 sekundy wydaje się nienaturalne i zakłóca jej płynność.

Na opóźnienie głosowe składają się trzy główne elementy: czas transkrypcji (STT), czas przetwarzania przez LLM (TTFT + generowanie) oraz czas syntezy TTS. Optymalizacja każdego z nich łącznie przekłada się na znacznie lepsze doświadczenie użytkownika.

Pomiar budżetu opóźnienia

Przed rozpoczęciem optymalizacji należy zmierzyć każdy komponent. Proszę dodać do pętli pomiary czasu, aby ustalić, na co faktycznie jest przeznaczany czas.

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}%)')

Strumieniowe TTS: odtwarzanie podczas generowania

Największą pojedynczą poprawę opóźnienia zapewnia strumieniowe TTS. Zamiast czekać na zsyntezowanie całego dźwięku, można rozpocząć odtwarzanie pierwszego fragmentu w ciągu około 200 ms, podczas gdy reszta jest generowana jednocześnie.

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()

Strumieniowe TTS zdanie po zdaniu

Najbardziej praktyczne podejście do strumieniowania polega na podzieleniu odpowiedzi LLM na zdania, a następnie ich syntezowaniu i odtwarzaniu jedno po drugim. Odtwarzanie pierwszego zdania rozpoczyna się w ciągu około 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

TTFT: optymalizacja czasu do pierwszego tokenu

TTFT (Time to First Token) to opóźnienie między wysłaniem żądania do LLM a odebraniem pierwszego tokenu odpowiedzi. Optymalizacja TTFT oznacza, że użytkownicy szybciej usłyszą początek odpowiedzi.

Należy używać strumieniowych odpowiedzi LLM i przekazywać tokeny do TTS, gdy tylko zostanie wykryta granica zdania.

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)

Wstępne generowanie typowych odpowiedzi

Niektóre odpowiedzi agenta są przewidywalne: powitania, komunikaty o błędach i sygnały namysłu („Let me check that for you.”). Należy wygenerować ich dźwięk z wyprzedzeniem podczas uruchamiania aplikacji, aby można je było odtwarzać natychmiast, bez żadnego opóźnienia.

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')

Opóźnienie lokalnego TTS a TTS w chmurze

TTS w chmurze dodaje czas przesyłania danych w obie strony (~100–300 ms). W przypadku krótkich odpowiedzi lub często używanych fraz lokalny silnik TTS może być szybszy — kosztem jakości głosu.

pyttsx3 to najprostsze działające offline rozwiązanie TTS dla języka 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

Wybór szybszego modelu LLM

Wybór modelu znacząco wpływa na TTFT. W przypadku większości zadań agentów głosowych GPT-4o-mini jest od 5 do 10 razy szybszy niż GPT-4o. Należy używać najmniejszego modelu, który spełnia wymagania jakościowe.

# 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}"')

Buforowanie odpowiedzi dla powtarzających się pytań

Użytkownicy często wielokrotnie zadają te same lub podobne pytania. Należy buforować odpowiedzi LLM i TTS dla identycznych zapytań, aby przy ponownym zadaniu pytania udostępniać je natychmiast.

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

Docelowe opóźnienie end-to-end

Po zastosowaniu wszystkich optymalizacji dobrze zaprojektowany agent głosowy powinien osiągać czas poniżej 1 sekundy od zakończenia wypowiedzi użytkownika do rozpoczęcia odpowiedzi agenta.

# 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')

Nakładanie asynchronicznej transkrypcji

Gdy agent generuje i wypowiada odpowiedź, należy natychmiast rozpocząć nagrywanie kolejnej wypowiedzi użytkownika. Nakładanie na siebie etapów potoku ogranicza przestoje między kolejnymi turami rozmowy.

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()

Sprawdzenie wiedzy

Która pojedyncza optymalizacja zazwyczaj zapewnia największe zmniejszenie opóźnienia w przypadku agentów głosowych?

Podsumowanie: optymalizacja opóźnienia agentów głosowych

Stos optymalizacji opóźnienia w komunikacji głosowej: strumieniowanie odpowiedzi LLM (uruchamianie TTS na granicach zdań w miarę napływania tokenów), odtwarzanie TTS zdanie po zdaniu (odtwarzanie podczas syntezy), buforowanie TTS (natychmiastowe odtwarzanie powtarzających się fraz), wstępnie wygenerowane typowe odpowiedzi (powitania, frazy podtrzymujące rozmowę) oraz wybór szybkiego modelu LLM (gpt-4o-mini dla prostych zapytań).

Cel: mniej niż 1 sekunda od zakończenia wypowiedzi użytkownika do pierwszego bajtu dźwięku. Należy mierzyć każdy komponent — transkrypcja często stanowi 40% całkowitego opóźnienia. Lokalne TTS zapewnia szybkość kosztem jakości; chmurowe TTS ze strumieniowaniem zdań stanowi lepszy kompromis dla większości agentów.

Często zadawane pytania

Czy lekcja „Optymalizacja opóźnień agentów głosowych” jest bezpłatna?

Tak — pełny tekst „Optymalizacja opóźnień agentów głosowych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.

Co nauczysz się w „Optymalizacja opóźnień agentów głosowych”?

Strumieniowanie TTS, dzielenie odpowiedzi na fragmenty i minimalizowanie opóźnienia do pierwszego słowa. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Agents?

Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Optymalizacja opóźnień agentów głosowych”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?

Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Speech-to-Text z Whisper i Deepgram
  2. Text-to-Speech w odpowiedziach agenta
  3. Tworzenie pętli konwersacji głosowej
  4. Optymalizacja opóźnień agentów głosowych
← Powrót do AI Agents