JARVIS/nucleo/saber/enriquece/README.md
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

110 lines
5.4 KiB
Markdown

# Enriquecer el RAG a nivel de pentesting profesional
El RAG del naranja ya funcionaba, pero se dejaba fuera lo mejor que hay en el
disco. Medido antes de empezar: **de las 72 notas del `OSCP_Vault` —la
metodologia OSCP escrita a mano, con comandos reales— había 0 en el índice.**
`indexa.py` las descartaba por dos motivos a la vez: el nombre (`SQL
injection.md` no lleva "notas" ni "guia") y la profundidad (viven a 5 niveles).
Este pipeline las rescata, multiplica su recuperabilidad con preguntas
generadas, y **mide** que el resultado es mejor antes de tocar nada.
## Correrlo
```bash
./correr_noche.sh # las cuatro fases, y decide si promociona
./correr_noche.sh --sin-promo # igual, pero deja el vivo intacto
```
Es reanudable: cada fase se salta si ya está hecha, así que si se corta,
relanzarlo continúa. La fase larga (síntesis) es reanudable fragmento a
fragmento.
## Las cuatro fases
| | Script | Qué hace | Coste |
|---|---|---|---|
| 1 | `cosecha.py` | rescata la metodología de COFRE que faltaba | segundos, CPU |
| 2 | `sintetiza.py` | preguntas en castellano por cada fragmento | **horas**, modelo local |
| 3 | `reindexa.py` | une todo y calcula vectores → `saber.jsonl.nuevo` | minutos, CPU |
| 4 | `evalua.py` | mide recuperación vivo vs candidato | segundos |
### 1 · Cosecha
Donde `indexa.py` es prudente (solo ficheros que *parecen* documentación, 3
niveles), esto es agresivo con el oro: las carpetas de metodología (OSCP vaults,
apuntes, cheatsheets) enteras, sin límite de profundidad. Fuera de ahí pone un
**tope por herramienta**: si un repo tiene más de 25 documentos, es una base de
datos (exploitdb son 29.925 ficheros), no unos apuntes, y solo se coge su
README. Así el oro no se ahoga en ruido.
### 2 · Síntesis — indexación multi-representación
El punto flaco de los embeddings estáticos: "cómo saco una shell reversa" no se
parece vectorialmente a `bash -i >& /dev/tcp/...`. La solución es **embeber la
pregunta y devolver el fragmento**. Por cada fragmento, el modelo local escribe
las preguntas que responde; se indexa la pregunta, pero lo que se le muestra al
cerebro es el fragmento **literal**.
Clave: el modelo **solo escribe preguntas, nunca reescribe comandos**. Si
inventa una pregunta rara, esa entrada recupera peor y ya está; no corrompe una
respuesta. Y el fragmento crudo sigue en el índice por su lado.
### 3 · Reindexado
Une el índice vivo (comandos Linux, glosario, fichas), la cosecha y la síntesis;
deduplica por prefijo; calcula los vectores con el mismo `model2vec` de siempre.
Escribe a `saber.jsonl.nuevo`. **No toca el vivo.** `busca.py` no cambia.
### 4 · Evaluación
`eval_set.jsonl` son ~45 preguntas de pentesting con las palabras que un
fragmento correcto debe contener. Mide `hit@1`, `hit@5` y similitud media, del
índice vivo contra el candidato. Determinista y reproducible, sin juez-LLM.
## La decisión, y por qué es segura
El índice vivo **solo se sustituye si la evaluación dice que el candidato es
mejor**, y antes se guarda `saber.jsonl.antes-<fecha>`. Si empeora, se queda el
vivo y el candidato espera revisión. Siempre reversible con un `cp`.
## Ficheros que genera (en `../../datos/`)
cosecha.jsonl la metodología rescatada, sin vectores
sintesis.jsonl las preguntas generadas (+ .hecho, el marcador)
saber.jsonl.nuevo el índice candidato, con vectores
informe_rag.txt los números de la evaluación
noche_rag.log el registro de la noche
saber.jsonl.antes-* copia del índice anterior, si se promocionó
## Iteraciones posteriores a la primera noche
- **Troceado que no tira comandos cortos** (`cosecha.py`). El troceado por
encabezado descartaba toda sección de menos de 60 caracteres, y eso perdía
el oro: `## Detección time-based` + `' AND SLEEP(5)-- -` son 50. Ahora una
sección corta se pega a la siguiente y cada fragmento lleva delante el título
de la nota. `SLEEP` pasó de 0 a 13 apariciones en el índice; hit@1 80→82 %.
El salto en la métrica es pequeño porque las preguntas que quedan son
cross-lingual, y de eso no salva el troceado.
- **`experimento_embedder.py`** — mide, sin tocar nada, si un transformer (e5,
bge) supera al model2vec estático. **Resultado (16 ago): NO compensa.**
`intfloat/multilingual-e5-base` empató con el estático (hit@1 86 %, hit@5
95 % los dos): arregla las 2 preguntas que el estático fallaba, pero rompe
otras 2 distintas. A cambio pediría ~50-150 ms por consulta en CPU y una
dependencia de torch en cada búsqueda. Medido antes de cambiar → **el
estático se queda.** El trabajo de recuperación lo hace la búsqueda híbrida
(coseno + bono por palabras), no el embedder, y por eso subir el embedder no
mueve la aguja. Reproducible: `./experimento_embedder.py`.
- **Re-síntesis limpia** sobre el troceado nuevo (`SABER_BASE` en `reindexa.py`
reconstruye desde el índice original en vez de apilar, para no inflar). Da a
los comandos rescatados (SLEEP y demás) su gancho-pregunta en castellano, que
es lo que de verdad ataca las consultas cross-lingual —no el embedder—.
## Nota sobre la tarjeta
La síntesis usa el modelo local por `127.0.0.1:11434`. Fija `qwen3.5:4b-jarvis`
(el naranja, que no inventa comandos). Si dejas el TUI verde abierto, ollama
tiene que cambiar de modelo en cada petición y la noche va más lenta: para la
síntesis más rápida, cierra el verde.