JARVIS: asistente de voz local para Linux
Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin cuentas ni claves. Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper comparten tarjeta y solo caben porque estan dimensionados para ello. El RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve de una cache de frases. Incluye instalador (install.sh), requisitos, y documentacion del stack, del manejo de root y de las acciones. Los apuntes indexados y el diario NO se incluyen: son privados y el .gitignore los bloquea.
This commit is contained in:
commit
8e4bc8ad94
125 changed files with 25033 additions and 0 deletions
108
config/puerta.sh
Executable file
108
config/puerta.sh
Executable file
|
|
@ -0,0 +1,108 @@
|
|||
#!/usr/bin/env bash
|
||||
# La puerta: decide si un cambio automatico se queda o se revierte.
|
||||
#
|
||||
# Es la pieza central del pipeline nocturno. Todo lo que el sistema se cambia a
|
||||
# si mismo pasa por aqui, y lo que no la pasa se deshace ENTERO. Sin esto,
|
||||
# dejar algo modificando codigo sin nadie delante es una forma elaborada de
|
||||
# romper un proyecto que funcionaba.
|
||||
#
|
||||
# Que comprueba, en orden de lo mas barato a lo mas caro (para fallar pronto):
|
||||
# 1. que el Python compile
|
||||
# 2. que el catalogo sea JSON valido y el generador lo reproduzca
|
||||
# 3. que el emparejador no haya perdido ordenes (08)
|
||||
# 4. que las acciones sigan respondiendo (repaso completo)
|
||||
# 5. la suite entera
|
||||
#
|
||||
# puerta.sh rapida solo 1-3, para iterar
|
||||
# puerta.sh completa todo, antes de commitear
|
||||
set -uo pipefail
|
||||
RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
|
||||
MODO=${1:-completa}
|
||||
cd "$RAIZ"
|
||||
|
||||
fallo() { echo "PUERTA: $1" >&2; exit 1; }
|
||||
|
||||
# --- 1. compila el Python -------------------------------------------------
|
||||
python3 -m compileall -q newelle/src config voz/*.py >/dev/null 2>&1 \
|
||||
|| fallo "hay Python que no compila"
|
||||
|
||||
# --- 2. el catalogo es valido y reproducible ------------------------------
|
||||
python3 -c "
|
||||
import json,sys
|
||||
d=json.load(open('newelle/data/acciones/acciones.json'))
|
||||
acc=d['acciones']
|
||||
assert len(acc)>100, f'solo {len(acc)} acciones'
|
||||
ids=[a['id'] for a in acc]
|
||||
assert len(ids)==len(set(ids)), 'ids repetidos'
|
||||
for a in acc:
|
||||
assert a.get('frases'), f\"{a['id']} sin frases\"
|
||||
assert a.get('acuse'), f\"{a['id']} sin acuse\"
|
||||
assert 'señor' in a['acuse'].lower(), f\"{a['id']} no trata de señor\"
|
||||
if a.get('captura') and a.get('comando'):
|
||||
assert '{argumento' in a['comando'], f\"{a['id']} captura y no usa el argumento\"
|
||||
" 2>/dev/null || fallo "el catalogo esta mal formado"
|
||||
|
||||
# El generador es la fuente: si no reproduce el catalogo, alguien edito el
|
||||
# producto y el proximo que regenere pierde el trabajo.
|
||||
cp newelle/data/acciones/acciones.json /tmp/jarvis-puerta-antes.json
|
||||
(cd newelle/data/acciones && python3 _generar.py >/dev/null 2>&1)
|
||||
if ! diff -q /tmp/jarvis-puerta-antes.json newelle/data/acciones/acciones.json >/dev/null 2>&1; then
|
||||
cp /tmp/jarvis-puerta-antes.json newelle/data/acciones/acciones.json
|
||||
fallo "el generador no reproduce el catalogo (se edito el producto)"
|
||||
fi
|
||||
|
||||
# --- 3. el emparejador no ha perdido ordenes ------------------------------
|
||||
python3 - <<'FIN' || fallo "el emparejador ha perdido ordenes que antes acertaba"
|
||||
import importlib.util, sys
|
||||
spec = importlib.util.spec_from_file_location("acc", "newelle/src/utility/acciones.py")
|
||||
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
|
||||
cat = m.Catalogo(["newelle/data/acciones/acciones.json"])
|
||||
|
||||
# El minimo innegociable: si alguna de estas deja de funcionar, el cambio se
|
||||
# revierte pase lo que pase. Son las que el usuario usa de verdad.
|
||||
IMPRESCINDIBLES = [
|
||||
("cuanto espacio queda", "disco_libre"),
|
||||
("cuanta memoria", "memoria"),
|
||||
("que temperatura hay", "temperatura"),
|
||||
("abre una terminal", "terminal_abrir"),
|
||||
("apaga la pantalla", "apagar_pantalla"),
|
||||
("captura de pantalla", "captura_pantalla"),
|
||||
("abre una ventana privada", "ff_firefox_privado"),
|
||||
("pon la ventana a la izquierda", "ventana_izquierda"),
|
||||
("necesito que pongas la pelicula de iron man", "pelicula_abrir"),
|
||||
("podrias abrir una terminal", "terminal_abrir"),
|
||||
]
|
||||
malas = []
|
||||
for frase, esperado in IMPRESCINDIBLES:
|
||||
a, _ = cat.busca(frase)
|
||||
if a is None or a.id != esperado:
|
||||
malas.append(f"{frase!r}→{a.id if a else 'ninguna'} (deberia {esperado})")
|
||||
|
||||
# Y la conversacion no puede engancharse a una accion
|
||||
CHARLA = ["que tal estas", "necesito un abrazo", "quiero contarte una cosa",
|
||||
"cuentame un chiste", "que piensas de la vida", "necesito pensar"]
|
||||
coladas = [c for c in CHARLA if cat.busca(c)[0] is not None]
|
||||
if malas or coladas:
|
||||
for x in malas: print(" perdida:", x, file=sys.stderr)
|
||||
for c in coladas: print(" charla colada:", c, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
FIN
|
||||
|
||||
[ "$MODO" = rapida ] && { echo "PUERTA: rapida OK"; exit 0; }
|
||||
|
||||
# --- 4. las acciones responden -------------------------------------------
|
||||
SALIDA=$(timeout 900 python3 config/repasar_acciones.py 2>&1 | tail -3)
|
||||
echo "$SALIDA" | grep -q "de .* responden bien" || fallo "el repaso de acciones no termino"
|
||||
ROTAS=$(echo "$SALIDA" | grep -oE "fallan: .*" || true)
|
||||
[ -n "$ROTAS" ] && fallo "hay acciones rotas → $ROTAS"
|
||||
|
||||
# --- 5. la suite ----------------------------------------------------------
|
||||
# Se salta la 07, que abre la app y se queda colgada sin nadie delante.
|
||||
SUITE=$(cd pruebas && for t in [0-9][0-9]_*.sh; do
|
||||
case "$t" in 05_*|07_*) continue;; esac
|
||||
timeout 400 "./$t" 2>&1 | tail -1
|
||||
done)
|
||||
FALLOS=$(echo "$SUITE" | grep -oE "[0-9]+ fallan" | awk '{s+=$1} END{print s+0}')
|
||||
[ "${FALLOS:-0}" -gt 0 ] && fallo "la suite tiene $FALLOS comprobaciones en rojo"
|
||||
|
||||
echo "PUERTA: completa OK"
|
||||
Loading…
Add table
Add a link
Reference in a new issue