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
110
nucleo/saber/enriquece/README.md
Normal file
110
nucleo/saber/enriquece/README.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue