Construir um Pipeline de Assistente de Voz A Capstone da Fase 6
Type: Build
Languages: Python
Prerequisites: Phase 6 · 04, 05, 06, 07, 11; Phase 11 · 09 (Function Calling); Phase 14 · 01 (Agent Loop)
Time: ~120 minutes
O problema
Construa um assistente de ponta a ponta:
- Captura entrada de microfone (16 kHz mono).
- Detecta o início/final da fala do utilizador.
- Transcreve o streaming.
- Passa a transcrição para um LLM que pode chamar ferramentas (timer, tempo, calendário).
- Transmitir texto de LLM para um TTS.
- Reproduza áudio para o usuário.
- Parará se o utilizador interromper a resposta média.
Meta de latência: primeiro byte de áudio TTS dentro de 800 ms do usuário terminando sua declaração em uma CPU de computador portátil. Meta de qualidade: nenhuma palavra perdida, nenhum subtítulo alucinado no silêncio, nenhum vazamento de clonagem de voz, nenhum sucesso de injeção rápida.
O conceito
!Voice assistant pipeline: mic → VAD → STT → LLM+tools → TTS → speaker
Os sete componentes
- Audio capture.Mic → 16 kHz mono → 20 ms. Geralmente
sounddeviceem Python ou em AudioUnit/ALSA/WASAPI nativo em produção. - VAD (Lesson 11).Silero VAD @ limiar 0,5, min fala 250 ms, silêncio pendurado 500 ms. Sinais "começo" e "fim".
- Streaming STT (Lesson 4-5).Whisper-streaming, Parakeet-TDT, ou Deepgram Nova-3 (API). Transcrições parciais + finais.
- LLM with tool calling.GPT-4o / Claude 3.5 / Gemini 2.5 Flash. JSON esquema para ferramentas. Tokens de streaming.
- Streaming TTS (Lesson 7).Kokoro-82M (aberto mais rápido) ou Cartesia Sonic (comercial).
- Playback.- O que é isso? - O que é isso?
- Interruption handler.Se o VAD disparar durante a reprodução do TTS, parar a reprodução, cancelar o LLM, reiniciar o STT.
Os três modos de falha que você vai acertar
- First-word clip.O VAD começa a bater tarde demais, o "hei" do usuário está faltando.
- Mid-response interrupt confusion.LLM continua a gerar após interrupções do usuário; assistente conversa sobre o usuário.
- Silence hallucination.O sussurro diz "Obrigado por assistir" nos quadros silenciosos.
2026 Estacas de referência de produção
| Stack | Latency | License | Notes |
|---|---|---|---|
| LiveKit + Deepgram + GPT-4o + Cartesia | 350-500 ms | commercial API | Industry default 2026 |
| Pipecat + Whisper-streaming + GPT-4o + Kokoro | 500-800 ms | mostly open | DIY-friendly |
| Moshi (full-duplex) | 200-300 ms | CC-BY 4.0 | Single-model; different architecture, lesson 15 |
| Vapi / Retell (managed) | 300-500 ms | commercial | Fastest to launch; limited customization |
| Whisper.cpp + llama.cpp + Kokoro-ONNX | offline | open | Privacy / edge |
Construí-lo
Passo 1: captura de microfone com fragmentação (pseudocód)
pythonimport sounddevice as sd
def mic_stream(chunk_ms=20, sr=16000):
q = queue.Queue()
def cb(indata, frames, time, status):
q.put(indata.copy().flatten())
with sd.InputStream(channels=1, samplerate=sr, blocksize=int(sr * chunk_ms/1000), callback=cb):
while True:
yield q.get()Passo 2: Captura de viradas com porta VAD
pythondef capture_turn(stream, vad, pre_roll_ms=300, silence_ms=500):
buf, pre, triggered = [], collections.deque(maxlen=pre_roll_ms // 20), False
silent = 0
for chunk in stream:
pre.append(chunk)
if vad(chunk):
if not triggered:
buf = list(pre)
triggered = True
buf.append(chunk)
silent = 0
elif triggered:
silent += 20
buf.append(chunk)
if silent >= silence_ms:
return b"".join(buf)Passo 3: streaming STT → LLM → TTS
pythonasync def turn(audio_bytes):
transcript = await stt.transcribe(audio_bytes)
async for token in llm.stream(transcript):
async for audio in tts.stream(token):
await speaker.play(audio)Passo 4: chamada de ferramenta dentro do ciclo de LLM
pythontools = [
{"name": "get_weather", "parameters": {"location": "string"}},
{"name": "set_timer", "parameters": {"seconds": "int"}},
]
async for chunk in llm.stream(user_text, tools=tools):
if chunk.type == "tool_call":
result = dispatch(chunk.name, chunk.args)
continue_streaming(result)
if chunk.type == "text":
await tts.stream(chunk.text)Passo 5: Manutenção de interrupção
pythontts_task = asyncio.create_task(tts_loop())
while True:
chunk = await mic.get()
if vad(chunk):
tts_task.cancel()
await speaker.stop()
await new_turn()
breakUsá-lo
Veja .code/main.pyPara uma simulação executável que conecta todos os sete componentes com modelos de estúdio, para que você possa ver a forma do pipeline mesmo sem hardware.
silero-vad(pip install silero-vad)deepgram-sdkouopenai-whisperopenai(gpt-4o) ouanthropickokorooucartesiasounddevicepara I/O
Encurralagens
- Logging PII forever.O áudio de rotação completa é PII na maioria das jurisdições. 30 dias de retenção, criptografado em repouso.
- No barge-in.Os usuários interromperão, o seu assistente deve parar de falar.
- TTS that blocks.TTS sincrônico bloqueia o ciclo de eventos. Use async ou um fio separado.
- No tool-call error handling.As ferramentas falham. LLM deve recuperar o erro + tentar novamente uma vez, e depois graciosamente degradar.
- Overzealous hallucination filters.O assistente repete "Não posso ajudar com isso". O subfiltro diz qualquer coisa.
- No wake-word option.Sempre ouvir é uma responsabilidade de privacidade. Adicione um portal de despertar (Porcupine ou openWakeWord).
Envia-o
Salva comooutputs/skill-voice-assistant-architect.md. Tendo em conta as limitações orçamentais + de escala + de língua + de conformidade, produzir uma especificação completa da pilha.
Exercícios
- Easy.Corra .
code/main.pySimula uma rotação completa de ponta a ponta com módulos de estúdio e impressões por estágio de latência. - Medium.Substitua o estúdio STT por um modelo real de Whisper num pré-registado
.wav- Meter o WER e a latência de ponta a ponta. - Hard.Adicionar chamada de ferramenta: implementar
get_weather(qualquer API) eset_timer. Enviar o Mestrado em Direito através das ferramentas e verificar que quando o utilizador diz "configurar um temporizador de 5 minutos", a função correta dispara e a resposta falada confirma isso.
Termos-chave
| Term | What people say | What it actually means |
|---|---|---|
| Turn | A user + assistant round-trip | One VAD-bounded user speech + one LLM-TTS response. |
| Barge-in | Interruption | User speaks while assistant talks; assistant stops. |
| Wake word | "Hey assistant" | Short keyword detector; Porcupine, Snowboy, openWakeWord. |
| End-pointing | Turn ending | VAD + min-silence decision that user has finished. |
| Pre-roll | Pre-speech buffer | Keep 200-400 ms of audio before VAD fires to avoid first-word clip. |
| Tool call | Function invocation | LLM emits JSON; runtime dispatches; result feeds back in-loop. |
Mais leitura
- LiveKit — voice agent quickstartReferência de nível de produção.
- Pipecat — voice agent examples Framework amigável para o DIY.
- OpenAI Realtime API o caminho de voz nativa gerenciado.
- Kyutai Moshi Referência duplex completa (Lessão 15).
- Porcupine wake-word- O gating de palavras de despertar.
- Anthropic — tool use guide Chamando a função de LLM.
This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.
Browse the complete course catalog or open this lesson on GitHub.