From f8f98e83481e7376a235fb305a095552433f79ab Mon Sep 17 00:00:00 2001 From: elvis Date: Sun, 6 Sep 2026 19:21:16 -0300 Subject: Local voice assistant on top of Canary, llama.cpp and qwentts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pipeline en Rust de hilos y canales que une los tres motores: el micrófono alimenta un segmentador con VAD, las intervenciones cerradas van al reconocedor, la transcripción al modelo y cada frase que este cierra sale hacia el sintetizador sin esperar al resto de la respuesta. Dos reglas sostienen el diseño: ninguna etapa bloquea a la anterior —quien va sobrado descarta trabajo en lugar de acumular retraso— y todo lo que viaja por los canales lleva el turno al que pertenece, así que interrumpir es subir el contador y levantar dos banderas de cancelación. Seis crates: core (configuración, eventos, HTTP, telemetría, herramientas), audio (cpal, VAD, anillo de reproducción), asr, llm, tts y app (supervisor de procesos y orquestador). Los motores van como submódulos fijados a un commit, con los cambios locales en vendor/patches. Midiendo el pipeline aparecieron tres cuellos de botella de configuración que valieron más que cualquier cambio de código, todos documentados en docs/RENDIMIENTO.md: - tts-server decodificaba el audio en bloques de 24 s, de modo que el modo «streaming» llegaba de una pieza: 4948 ms -> 585 ms hasta el primer audio. - La plantilla de chat del modelo abre y no lo cierra nunca, sin variable que lo apague: 8630 ms -> 413 ms hasta el primer token, con una copia de la plantilla que deja el bloque cerrado de entrada. - Cualquier indicación de estilo junto a la guía de herramientas hace que este modelo de 2B deje de llamarlas y se invente el dato (8/8 aciertos con la guía sola, 0/8 con la persona de asistente de voz). El turno alterna ahora entre dos instrucciones de sistema. La ejecución de órdenes del sistema queda implementada y apagada, tras cuatro barreras: lista blanca sobre el ejecutable, rutas rechazadas, sin shell que interprete metacaracteres y plazo máximo. 68 pruebas unitarias sin modelos, más seis de integración que se saltan solas si no hay servidores y se turnan la GPU: en paralelo, los dos servidores no caben en 4 GB y miden contención en vez de latencia. Claude-Session: https://claude.ai/code/session_01FNxz5cSdQSscJH9H7b8uGU --- scripts/clonar-voz.sh | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100755 scripts/clonar-voz.sh (limited to 'scripts/clonar-voz.sh') diff --git a/scripts/clonar-voz.sh b/scripts/clonar-voz.sh new file mode 100755 index 0000000..e68215d --- /dev/null +++ b/scripts/clonar-voz.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Extrae los latentes de una voz a partir de una grabación, para que el +# asistente hable con ella. +# +# scripts/clonar-voz.sh grabacion.wav transcripcion.txt [nombre] +# +# Produce assets/voices/.{spk,rvq,txt}, que es lo que el asistente +# registra en tts-server al arrancar. Se hace una vez: en cada arranque se +# mandan los latentes ya extraídos y no la grabación. +set -euo pipefail + +cd "$(dirname "$0")/.." + +WAV="${1:?uso: clonar-voz.sh [nombre]}" +TXT="${2:?falta la transcripción de la grabación}" +NOMBRE="${3:-asistente}" + +CODEC=vendor/qwentts.cpp/build/qwen-codec +TOKENIZER=models/qwen-tokenizer-12hz-Q8_0.gguf + +for f in "$CODEC" "$TOKENIZER" "$WAV" "$TXT"; do + [ -e "$f" ] || { echo "falta: $f" >&2; exit 1; } +done + +SALIDA="assets/voices" +mkdir -p "$SALIDA" + +echo "Extrayendo la voz de $WAV…" +# --talker produce el embedding del hablante (.spk) y los códigos de +# referencia (.rvq); con la transcripción, el servidor activa el clonado ICL, +# que se parece bastante más al original que sólo el embedding. +"$CODEC" --model "$TOKENIZER" --talker \ + --input "$WAV" \ + -o "$SALIDA/$NOMBRE" + +cp "$TXT" "$SALIDA/$NOMBRE.txt" + +echo +echo "Listo:" +ls -la "$SALIDA/$NOMBRE".{spk,rvq,txt} +echo +echo "Apunta config/asistente.toml a esta voz:" +echo " [tts.reference]" +echo " name = \"$NOMBRE\"" +echo " speaker = \"../$SALIDA/$NOMBRE.spk\"" +echo " codes = \"../$SALIDA/$NOMBRE.rvq\"" +echo " transcript = \"../$SALIDA/$NOMBRE.txt\"" -- cgit v1.2.3