aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/bootstrap.sh
diff options
context:
space:
mode:
authorelvis <elvis@claros.ar>2026-09-06 19:21:16 -0300
committerelvis <elvis@claros.ar>2026-09-06 19:23:37 -0300
commitf8f98e83481e7376a235fb305a095552433f79ab (patch)
tree6d0ecddb26bef8430a086431de40f8ce9b1039e6 /scripts/bootstrap.sh
downloadasist-p-f8f98e83481e7376a235fb305a095552433f79ab.tar.gz
asist-p-f8f98e83481e7376a235fb305a095552433f79ab.zip
Local voice assistant on top of Canary, llama.cpp and qwentts
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 <think> 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
Diffstat (limited to 'scripts/bootstrap.sh')
-rwxr-xr-xscripts/bootstrap.sh132
1 files changed, 132 insertions, 0 deletions
diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh
new file mode 100755
index 0000000..9ba694e
--- /dev/null
+++ b/scripts/bootstrap.sh
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+# Deja el repositorio en condiciones de ejecutarse: submódulos, parches,
+# binarios y modelos.
+#
+# Es idempotente: se puede volver a lanzar sin miedo. Y no copia modelos —son
+# más de 5 GB—, sino que los enlaza desde donde ya estén.
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+RAIZ="$PWD"
+
+# Repositorios ya clonados en el sistema, para no volver a bajar de la red ni
+# recompilar lo que ya está compilado.
+ESPEJO="${ASIST_ESPEJO:-$HOME/GIT-MIRRO}"
+HF="${ASIST_HF:-$HOME/HF}"
+
+azul() { printf '\033[36m%s\033[0m\n' "$*"; }
+verde() { printf '\033[32m%s\033[0m\n' "$*"; }
+aviso() { printf '\033[33m%s\033[0m\n' "$*" >&2; }
+malo() { printf '\033[31m%s\033[0m\n' "$*" >&2; }
+
+enlazar() { # origen destino descripción
+ local origen="$1" destino="$2" que="$3"
+ if [ ! -e "$origen" ]; then
+ aviso " no está $que: $origen"
+ return 1
+ fi
+ mkdir -p "$(dirname "$destino")"
+ if [ -L "$destino" ] || [ -e "$destino" ]; then
+ rm -rf "$destino"
+ fi
+ ln -s "$origen" "$destino"
+ verde " $que -> $(basename "$origen")"
+}
+
+# ---------------------------------------------------------------------------
+azul "1/5 Submódulos"
+# Si el repositorio está clonado al lado, se usa como referencia: llama.cpp son
+# 406 MB de objetos que no hace falta volver a descargar.
+for nombre in canary-rs qwentts.cpp llama.cpp; do
+ ruta="vendor/$nombre"
+ if [ -f "$ruta/.git" ] || [ -d "$ruta/.git" ]; then
+ continue
+ fi
+ if [ -d "$ESPEJO/$nombre/.git" ]; then
+ git -c protocol.file.allow=always submodule update --init \
+ --reference "$ESPEJO/$nombre" "$ruta"
+ else
+ git submodule update --init "$ruta"
+ fi
+done
+git submodule status | sed 's/^/ /'
+
+# ---------------------------------------------------------------------------
+azul "2/5 Parches y ficheros sueltos"
+# Los parches recogen los cambios locales sobre cada repositorio: sin ellos, el
+# ASR se comporta distinto al que se midió.
+for nombre in canary-rs qwentts.cpp llama.cpp; do
+ parche="$RAIZ/vendor/patches/$nombre.patch"
+ [ -s "$parche" ] || continue
+ (
+ cd "vendor/$nombre"
+ if git apply --check "$parche" 2>/dev/null; then
+ git apply "$parche"
+ verde " $nombre: parche aplicado"
+ elif git apply --reverse --check "$parche" 2>/dev/null; then
+ verde " $nombre: parche ya aplicado"
+ else
+ malo " $nombre: el parche no aplica; revísalo a mano"
+ fi
+ )
+done
+# Ficheros que sólo existían en el árbol de trabajo local y que un parche no
+# puede llevar. canary-rs no compila sin bench_live.rs: su Cargo.toml lo declara.
+if [ -d vendor/extra ]; then
+ for nombre in canary-rs qwentts.cpp; do
+ [ -d "vendor/extra/$nombre" ] || continue
+ cp -rn "vendor/extra/$nombre/." "vendor/$nombre/" 2>/dev/null || true
+ done
+ verde " ficheros sueltos copiados"
+fi
+
+# ---------------------------------------------------------------------------
+azul "3/5 Motores en C++"
+enlazar_o_construir() { # nombre ruta_build orden_de_construccion...
+ local nombre="$1" build="$2"; shift 2
+ if [ -e "vendor/$nombre/$build" ]; then
+ verde " $nombre ya está construido"
+ return
+ fi
+ if [ -e "$ESPEJO/$nombre/$build" ]; then
+ enlazar "$ESPEJO/$nombre/$(dirname "$build")" \
+ "$RAIZ/vendor/$nombre/$(dirname "$build")" "build de $nombre"
+ return
+ fi
+ aviso " $nombre sin construir. Construyendo (esto tarda)…"
+ ( cd "vendor/$nombre" && "$@" )
+}
+enlazar_o_construir llama.cpp build/bin/llama-server \
+ bash -c 'cmake -B build -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=ON && cmake --build build -j --target llama-server'
+enlazar_o_construir qwentts.cpp build/tts-server \
+ bash -c './buildcuda.sh'
+
+# ---------------------------------------------------------------------------
+azul "4/5 Modelos"
+# Se enlazan, no se copian: son más de 5 GB entre todos.
+enlazar "$HF/Qwen/Qwen3.5-2B.Q5_K_M.gguf" "$RAIZ/models/Qwen3.5-2B.Q5_K_M.gguf" "modelo del LLM" || true
+enlazar "$HF/Qwen/mmproj-BF16.gguf" "$RAIZ/models/mmproj-BF16.gguf" "proyector multimodal" || true
+for m in qwen-talker-1.7b-base-Q8_0.gguf qwen-tokenizer-12hz-Q8_0.gguf; do
+ enlazar "$ESPEJO/qwentts.cpp/models/$m" "$RAIZ/models/$m" "$m" || true
+done
+if [ ! -d vendor/canary-rs/models/canary-180m-flash-onnx ] \
+ && [ -d "$ESPEJO/canary-rs/models/canary-180m-flash-onnx" ]; then
+ enlazar "$ESPEJO/canary-rs/models/canary-180m-flash-onnx" \
+ "$RAIZ/vendor/canary-rs/models/canary-180m-flash-onnx" "modelo Canary" || true
+fi
+
+# ---------------------------------------------------------------------------
+azul "5/5 Voz de referencia"
+# El asistente habla con una voz clonada. Si no hay una preparada, se dice cómo
+# hacerla en vez de fallar: el resto ya funciona con una voz del modelo.
+if [ -f assets/voices/asistente.spk ]; then
+ verde " ya hay una voz en assets/voices/"
+else
+ aviso " sin voz clonada. Prepárala con:"
+ aviso " scripts/clonar-voz.sh <grabacion.wav> <transcripcion.txt>"
+ aviso " o quita la sección [tts.reference] de config/asistente.toml para"
+ aviso " usar una voz del propio modelo."
+fi
+
+echo
+azul "Listo. Comprueba con: cargo run --release -- check"