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.
101 lines
3.5 KiB
Python
101 lines
3.5 KiB
Python
#!/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())
|