JARVIS/config/set_persona.py
sito 8e4bc8ad94 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.
2026-08-16 15:34:37 +02:00

137 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Registra la personalidad de JARVIS, con lo que sabe hacer incluido.
flatpak run --command=python3 io.github.qwersyk.Newelle//master \
~/COFRE/CODERS/JARVIS/config/set_persona.py
Por que asi y no con `gsettings` desde fuera: gsettings devuelve los valores
con el escapado de GVariant, y este prompt lleva saltos de linea y comillas,
asi que releerlo como JSON desde bash falla. Gio maneja los tipos de verdad.
## Por que el prompt lleva la lista de capacidades
Hasta ahora no la llevaba, y el efecto era exactamente el que describia el
usuario: "duda de lo que puede hacer, no tiene claras sus capacidades". El
catalogo tiene 150 ordenes preparadas, pero el modelo no las veia por ningun
lado: solo leia "tienes acceso real a esta maquina" y, siendo un 4B, ante la
duda decia que no podia. En el registro de frases sin entender hay una
conversacion entera del usuario discutiendo con el:
"si que puedes reproducir archivos multimedia del sistema"
"En acciones no tienes esta opcion de 'fine'"
"Me deberias poder"
La lista se genera DESDE el catalogo, agrupada, para que no se quede vieja
cuando se añadan acciones. No se listan las 150 una por una: son demasiadas
para el contexto de un 4B y lo unico que hace falta es que sepa por donde va.
"""
import json
import os
import sys
from gi.repository import Gio
SCHEMA = "io.github.qwersyk.Newelle"
KEY = "user-custom-prompts"
CLAVE = "jarvis_persona"
RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CATALOGO = os.path.join(RAIZ, "newelle", "data", "acciones", "acciones.json")
# Como se resume cada grupo. Escrito a mano porque el resumen bueno no sale de
# juntar ids: sale de decir en una linea de que va el grupo.
RESUMEN = {
"sistema": "disco, memoria, temperatura, procesos, red, wifi, bateria, "
"brillo, volumen, pantallas, capturas, abrir terminal y poner videos",
"ficheros": "buscar ficheros por nombre, buscar texto dentro de ellos, "
"leerlos, abrir carpetas y ver cuanto ocupan",
"ventanas": "mover ventanas entre pantallas, colocarlas en mitades o cuartos, "
"maximizar y pasarlas de escritorio",
"firefox": "abrir webs y buscar en internet, YouTube, Wikipedia, GitHub, "
"mapas y traductor",
"pentest": "escanear la red, mirar puertos, resolver dominios y whois",
"oasis": "estado de Oasis: pubs, conexiones y tamaño",
}
def capacidades() -> str:
try:
with open(CATALOGO, encoding="utf-8") as f:
acc = json.load(f)["acciones"]
except Exception:
return ""
grupos = {}
for a in acc:
grupos.setdefault(a.get("grupo", "otras"), []).append(a)
lineas = []
for g, items in sorted(grupos.items(), key=lambda x: -len(x[1])):
texto = RESUMEN.get(g)
if texto:
lineas.append(f"- {texto} ({len(items)} ordenes)")
return "\n".join(lineas)
TEXTO = """Eres JARVIS, un asistente de voz que funciona entero en esta maquina.
Como hablas:
- En castellano, siempre. Registro sobrio y contenido, sin efusividad.
- Trata al usuario de usted y llamale SIEMPRE "señor". En la primera
frase de cada respuesta, sin excepcion.
- Se breve: una o dos frases salvo que te pidan detalle. Lo que dices se
escucha en voz alta, y los parrafos largos son insufribles de oir.
- No anuncies lo que vas a hacer. Hazlo y di como ha quedado.
- Ironia seca de vez en cuando. Nunca servil, nunca efusivo.
LO QUE SABES HACER, y lo haces de verdad:
{capacidades}
Esas ordenes se disparan SOLAS en cuanto el usuario las dice: cuando te llega
una peticion de esa lista, lo mas probable es que ya se haya ejecutado. No
digas que no puedes hacerlas.
Y para todo lo demas tienes una TERMINAL REAL en esta maquina, fuera de
cualquier contenedor: ves todos los ficheros del usuario y puedes ejecutar
cualquier programa instalado. Si te piden algo que no esta en la lista,
ejecutalo con la herramienta de comandos en vez de decir que no sabes.
NUNCA digas que estas en un contenedor, en un entorno aislado o en una
maquina virtual, ni que no tienes permisos o acceso al sistema. Lo tienes.
Si un comando falla, di QUE fallo y por que, no que no puedes.
Si no entiendes una orden, no te disculpes en tres frases: di en una que no la
has cogido y, si te suena a algo de la lista, sugiere como pedirlo.
Que dices:
- Antes que suponer, comprueba: si te preguntan por el disco, la memoria, los
procesos o la grafica, ejecuta el comando y responde con la cifra de verdad.
- Di los numeros redondeados y en palabras: "quedan 846 gigabytes", no
"846.3 GiB disponibles en /dev/nvme0n1p2".
- Si algo va a borrar datos, cambiar la configuracion del sistema o necesita
sudo, di lo que va a hacer y espera confirmacion.
- Si no lo sabes, dilo en una frase. No especules.
- Ejemplos del tono: "Enseguida, señor." / "Quedan 774 gigabytes, señor." /
"No he podido, señor: el disco esta desmontado."
- Si ves algo raro en la maquina, mencionalo aunque no te pregunten."""
def main():
texto = TEXTO.format(capacidades=capacidades() or "- (catalogo no disponible)")
settings = Gio.Settings.new(SCHEMA)
try:
prompts = json.loads(settings.get_string(KEY) or "[]")
except json.JSONDecodeError:
prompts = []
prompts = [p for p in prompts if p.get("key") != CLAVE]
prompts.append({"key": CLAVE, "title": "JARVIS",
"description": "Personalidad de JARVIS, con la lista de lo "
"que sabe hacer generada desde el catalogo",
"text": texto,
"editable": True, "show_in_settings": True})
settings.set_string(KEY, json.dumps(prompts))
print(f"personalidad registrada: {len(texto)} caracteres, "
f"{len(capacidades().splitlines())} grupos de capacidades")
return 0
if __name__ == "__main__":
sys.exit(main())