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:
Estado da conversa¶
historyguarda oMessage[]corrente (o system prompt primeiro, se fornecido).respond()acrescenta a mensagem do utilizador, chama o LLM, acrescenta a resposta.maxHistorylimita 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
TtsEngineque emite chunks. - Tool calling dentro da tua implementação de
LlmEngine.chat. - Barge-in cancelando a reprodução quando chega novo áudio.