Skip to content

Motores próprios & arquitetura

flowchart LR
  A(["áudio"]) --> STT["SttEngine<br/>(whisper.cpp)"]
  STT --> T["texto"]
  T --> LLM["LlmEngine<br/>(Ollama)"]
  LLM --> R["resposta"]
  R --> TTS["TtsEngine<br/>(Piper)"]
  TTS --> O(["áudio out"])
  P["VoicePipeline<br/>(detém o histórico)"] -. liga .-> STT & LLM & TTS

Design

O whispa é propositadamente fino. O núcleo é o VoicePipeline, que depende apenas de três interfaces:

interface SttEngine { transcribe(audio: Uint8Array | string): Promise<string>; }
interface LlmEngine { chat(messages: Message[]): Promise<string>; }
interface TtsEngine { synthesize(text: string): Promise<Uint8Array>; }

Tudo o resto — whisper.cpp, Ollama, Piper — é um adaptador que implementa uma destas. Isto mantém o loop puro e totalmente testável com mocks, e permite misturar motores livremente.

Escrever o teu próprio motor

Qualquer fornecedor funciona — um STT na cloud, um LLM local diferente, um TTS alojado — desde que satisfaça a interface:

import type { LlmEngine, Message } from 'whispa';

class MeuLlm implements LlmEngine {
  async chat(messages: Message[]): Promise<string> {
    // chama o teu modelo, devolve o texto do assistente
    return '...';
  }
}

Depois encaixa-o:

new VoicePipeline({ stt, llm: new MeuLlm(), tts });

Estado da conversa

  • history guarda o Message[] corrente (o system prompt primeiro, se fornecido).
  • respond() acrescenta a mensagem do utilizador, chama o LLM, acrescenta a resposta.
  • maxHistory limita os turnos retidos (o system prompt é sempre mantido).
  • reset() limpa tudo exceto o system prompt.

Estender o loop

Adições comuns que podes acrescentar por cima sem tocar no núcleo:

  • Wake word / VAD antes de chamar turn().
  • TTS em streaming implementando um TtsEngine que emite chunks.
  • Tool calling dentro da tua implementação de LlmEngine.chat.
  • Barge-in cancelando a reprodução quando chega novo áudio.