E' nata SONIA. La mia AI locale 100% offline, 13Gb RAM. Non serve GPU dedicata
Le assistenti vocali locali che vale la pena provare chiedevano finora un'hardware che molti portatili non hanno: una scheda NVIDIA con CUDA, oppure un Mac di fascia alta con tanta RAM. Sonia (github.com/grigio/sonia-assistant) parte dall'idea opposta: si adatta al computer che hai già, anche senza scheda video dedicata, e costruisce attorno a quello un assistente che ascolta, parla ed esegue azioni.
Le motivazioni
Sonia è un'assistente vocale fullduplex locale e lo cala su macchine più ordinarie: meno GPU, meno RAM. Il README fissa il limite a 15 GB di RAM e zero GPU dedicata. L'ASR (speech-to-text) usa qualsiasi dispositivo Vulkan disponibile, una iGPU integrata basta, e in alternativa si passa a ASR_BACKEND=cpu. TTS, classificatore di fine turno e VAD girano solo su CPU.
L'altro vantaggio è la disponibilità: il modello principale consigliato è Qwen3.5-4B, da 4B in su, quantizzato Q4_K_M pesa circa 2.7 GiB. Non serve una macchina da guerra per avere un assistente che risponde a voce in tempo reale.
Resta un solo pezzo fuori dal perimetro del progetto: l'endpoint LLM, un llama-server già avviato e compatibile con l'API OpenAI, gestito da te con il tuo unit systemd. Tutto il resto, microfono compreso, resta sulla tua macchina. Non c'è cloud, non c'è login, non c'è account da creare.
Come è strutturato
Un processo unico in TypeScript (Bun) tiene insieme il loop audio da 32 ms, le code e la macchina a stati. Attorno a lui ci sono tre server audio.cpp: ASR su :8091, TTS su :8092, Smart Turn su :8093. Il VAD di Silero gira in-process. La UI vive su :8095, accessibile dal browser o dalla app GTK4, e ci sono anche le modalità --cli (microfono e casse via PipeWire) e --text (input scritto).
Le features che danno valore
Full-duplex con barge-in. Il microfono non si chiude mai mentre Sonia parla. La interrompi a metà frase e lei ferma, riprende o cancella la risposta. Il riconoscimento è echo-aware: i backchannel e la voce stessa di Sonia non bastano a interrompere, la tua parola vera sì. Sugli speaker del portatile serve l'ec canceller di PipeWire (scripts/install-echo-cancel.sh), con le cuffie il problema non si pone.
Rilevamento del fine turno a più livelli. I transcript parziali alimentano la riga 🎤 … in diretta. La fine del turno la decidono Silero VAD, le pause di silenzio e il classificatore Smart Turn; solo quando la pausa è ambigua si invia all'LLM un prompt di 4 token con esito LISTEN o REPLY. Il risultato è che Sonia capisce quando hai finito senza che tu debba premere un pulsante.
Function calling con tool gate. A disposizione ci sono data e ora, timer, watch, note, skills e shell, con fino a 6 round di tool per turno. Il tool gate ricontrolla ogni chiamata contro le tue parole: un modello da 4B non spara tool a caso. Per la shell c'è CMD_MODE=off|ask|auto, dove auto è la modalità non presidiata che rifiuta comandi metacaratteri e path sensibili (.ssh, chiavi, .env) invece di chiedere conferma.
Timer, watch e banner attività. Ogni timer in scadenza, comando in esecuzione e watch attivo compare in un banner sopra la chat, con il countdown e una ✕ che ferma quel solo task. Un watch ripete un comando read-only a intervalli (minimo 5 secondi) e può restare silenzioso finché l'output non contiene un testo che hai indicato.
Skills esterne, non del progetto. Sonia non possiede le skill: SKILLS_DIR punta alla cartella che usi già, che sia quella di pi, di opencode o un'altra. Ogni skill è attivabile per turno dalla drawer della UI. Chi non vuole scrivere istruzioni da zero può quindi riutilizzare il proprio pacchetto SKILL.md già funzionante.
Rewind e chat persistente. Cliccando su una riga si cancella tutto ciò che viene dopo: turni successivi e relativo contesto, sia nel transcript che nella history del modello, su entrambe le gambe LLM. La riga cliccata e i task pending restano. La chat vive in data/chat.json, quindi un riavvio riprende da dove eri arrivato.
Bilinguismo IT/EN. Il modello specchia la lingua che parli, turno per turno. LOCALE=it|en localizza UI, frasi pronunciate e risultati dei tool, e un timer suona nella lingua di chi l'ha impostato.
Due gambe LLM intercambiabili. Quella di default è l'endpoint compatibile OpenAI; in alternativa, con LLM_VIA_PI=1, si può usare un eseguibile pi già installato, commutabile a runtime. In quest'ultima modalità i tool dell'harness sono disattivati.
Modelli in ballo
| Modello | Ruolo | Dove gira |
|---|---|---|
| Qwen3.5-4B-MTP (Q4_K_M) | LLM principale, tool, giudice di turno, tool gate | llama-server esterno, Vulkan |
| Voxtral Mini 4B Realtime | ASR streaming | audio.cpp, Vulkan (o CPU) |
| Pocket TTS | TTS streaming, 5 pacchetti linguistici, 26 voci | CPU |
| Smart Turn v3.2 | classificatore di fine turno | CPU |
| Silero VAD v5 | rilevamento attività vocale | CPU, in-process |
I modelli vocali si scaricano con bun run models-download (circa 3.3 GiB, scaricabile a pezzi e riprendibile). Il .gguf della LLM non lo scarica il repo: va caricato sul tuo llama-server.
Cosa mettere in conto
I prerequisiti sono Bun, un llama-server attivo su :8080, la build una tantum di vendor/audio.cpp con cmake, Ninja e Vulkan SDK (senza Vulkan si compila con -DENGINE_ENABLE_VULKAN=OFF e ASR_BACKEND=cpu), Chromium per la UI e PipeWire per la modalità CLI. Il README spiega anche perché conviene compilarlo invece di usare i binari ufficiali: sono costruiti con -march=native su runner AVX-512 e muoiono con SIGILL su CPU Intel di dodicesima generazione in poi.
Sulla sicurezza il progetto è esplicito: tutto è localhost per design, la WebSocket accetta connessioni solo dal proprio origin, e i comandi dentro SKILLS_DIR sono considerati fidati perché Sonia li relayà soltanto. Prima di attivare una skill nuova vale la pena leggere cosa dice al modello e quali CLI chiama, tenendo CMD_MODE=ask finché non la conosci.