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
101
config/construir_indice.py
Normal file
101
config/construir_indice.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Construye el indice del RAG sin abrir la interfaz.
|
||||
|
||||
flatpak run --command=python3 io.github.qwersyk.Newelle//master \
|
||||
~/COFRE/CODERS/JARVIS/config/construir_indice.py
|
||||
|
||||
En la app esto es el boton "Update Index" de los ajustes del RAG, que lanza un
|
||||
hilo y va enseñando el progreso en una barra. Desde aqui se llama a lo mismo
|
||||
pero en primer plano, para poder dejarlo corriendo en una terminal y ver si
|
||||
falla, que con 20 000 fragmentos conviene.
|
||||
|
||||
Antes hay que haber preparado el corpus con indexar.py: este script indexa lo
|
||||
que haya en la carpeta de documentos, sin filtrar nada.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, "/app/share/newelle")
|
||||
sys.path.insert(0, os.path.expanduser(
|
||||
"~/.var/app/io.github.qwersyk.Newelle/config/pip"))
|
||||
|
||||
from gi.repository import Gio # noqa: E402
|
||||
|
||||
from newelle.handlers.rag.llamaindex_handler import LlamaIndexHanlder # noqa: E402
|
||||
from newelle.handlers.embeddings.model2vec import Model2VecHandler # noqa: E402
|
||||
|
||||
CONFIG = os.path.expanduser("~/.var/app/io.github.qwersyk.Newelle/config")
|
||||
MODELOS = os.path.join(CONFIG, "models")
|
||||
DOCUMENTOS = os.path.join(MODELOS, "documents")
|
||||
|
||||
|
||||
def main():
|
||||
if not os.path.isdir(DOCUMENTOS):
|
||||
print(f"no existe {DOCUMENTOS}")
|
||||
print("prepara antes el corpus: python3 config/indexar.py --hacerlo")
|
||||
return 1
|
||||
|
||||
ficheros = [f for f in os.listdir(DOCUMENTOS) if not f.startswith(".")]
|
||||
if not ficheros:
|
||||
print("la carpeta de documentos esta vacia")
|
||||
return 1
|
||||
|
||||
tam = sum(os.path.getsize(os.path.join(DOCUMENTOS, f))
|
||||
for f in ficheros) / 1048576
|
||||
print(f"{len(ficheros)} documentos · {tam:.1f} MB")
|
||||
|
||||
settings = Gio.Settings.new("io.github.qwersyk.Newelle")
|
||||
rag = LlamaIndexHanlder(settings, MODELOS)
|
||||
|
||||
if not rag.is_installed():
|
||||
print("faltan dependencias del RAG (llama-index, faiss, tiktoken)")
|
||||
return 1
|
||||
|
||||
# El RAG necesita el modelo de embeddings para vectorizar, y opcionalmente
|
||||
# un LLM secundario para reescribir la consulta. Aqui solo hace falta el
|
||||
# primero: se indexa, no se pregunta.
|
||||
rag.embedding = Model2VecHandler(settings, MODELOS)
|
||||
if not rag.embedding.is_installed():
|
||||
print("instalando model2vec...")
|
||||
rag.embedding.install()
|
||||
# create_index solo le pide load_model() al LLM —para indexar no hace falta
|
||||
# generar nada— asi que un cascaron sirve y evita cargar 3,7 GB de modelo
|
||||
# para vectorizar documentos.
|
||||
class SinModelo:
|
||||
def load_model(self, *a, **k):
|
||||
return True
|
||||
|
||||
rag.llm = SinModelo()
|
||||
rag.secondary_llm = SinModelo()
|
||||
|
||||
trozo = rag.get_setting("chunk_size")
|
||||
print(f"fragmentos de {trozo} tokens · esto tarda, no lo cortes\n")
|
||||
|
||||
ini = time.time()
|
||||
try:
|
||||
rag.create_index()
|
||||
except Exception as e:
|
||||
print(f"\nfallo al indexar: {type(e).__name__}: {e}")
|
||||
return 1
|
||||
|
||||
minutos = (time.time() - ini) / 60
|
||||
_, data_path = rag.get_paths()
|
||||
peso = sum(
|
||||
os.path.getsize(os.path.join(dp, f))
|
||||
for dp, _, fs in os.walk(data_path) for f in fs) / 1048576
|
||||
|
||||
# create_index se traga sus propias excepciones e imprime el error, asi que
|
||||
# terminar sin fallo no significa que haya indice. Se comprueba el disco.
|
||||
if peso < 0.1:
|
||||
print(f"\nno se creo indice ({peso:.1f} MB en {data_path})")
|
||||
print("mira el error de arriba")
|
||||
return 1
|
||||
|
||||
print(f"\nindice construido en {minutos:.1f} min · {peso:.1f} MB")
|
||||
print(f"en {data_path}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue