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
137
config/set_persona.py
Normal file
137
config/set_persona.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#!/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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue