- manuales.py cosecha el --help de las herramientas que no traen man page (ffuf, sqlmap, suite impacket, netexec, hydra...) y lo escribe como markdown en conocimiento/09 - Manuales/. Es la referencia de opciones que la man page no da; complementa el cheatsheet de invocaciones comunes. - indexa.py --publico construye saber-publico.jsonl: glosario + pack de conocimiento con los vectores ya calculados, 100% publico y portable. - busca.py usa ese indice publico como fallback cuando no hay indice personal, asi un clon recien hecho consulta al instante sin reindexar nada. - .gitignore deja pasar saber-publico.jsonl; el personal (saber.jsonl) sigue fuera. - docs/rag.md y el README del pack documentan ambas cosas.
5.5 KiB
El RAG
JARVIS responde desde los apuntes del usuario, no desde lo que el modelo
recuerde. Para 668 herramientas de pentesting, recordar sería inventar. Este
documento explica el método y los resultados. El índice personal (los
apuntes de cada quien) no se publica; sí se publica un índice público
pre-construido —glosario de Linux más el pack de metodología y manuales, con
los vectores ya calculados (nucleo/datos/saber-publico.jsonl)— para que un clon
recién hecho consulte al instante, sin repetir el trabajo de indexar. El código
que lo construye está entero en nucleo/saber/.
Cómo funciona
Embeddings estáticos (model2vec, potion-multilingual-128M) sobre los
fragmentos de los apuntes, con una búsqueda híbrida: similitud coseno más un
bono por palabras exactas y por nombrar la herramienta. Los estáticos son
rápidos —un vector por token, precalculado, sin red neuronal en la consulta— a
cambio de ser más flojos en lo semántico; el bono por palabras compensa esa
debilidad. No hace falta una base de datos vectorial: con unos miles de
vectores, un producto escalar en numpy arranca antes y no añade dependencias.
El RAG entra de dos formas:
- Pasivo: en cada pregunta se le ponen delante al modelo los fragmentos más parecidos. Siempre dispara.
- Agéntico:
buscar_en_apunteses una herramienta que el modelo invoca cuando lo decide, reformulando la consulta con sus palabras y buscando varias veces. Salva los casos donde el usuario dice una cosa y el apunte está escrito de otra.
El pipeline de enriquecimiento
En nucleo/saber/enriquece/, cuatro fases, reanudable y no destructivo:
apuntes ──cosecha──> fragmentos ──sintetiza──> preguntas
│ │
└───── reindexa ──────────┘
│
índice + vectores
│
evalúa (hit@k) ──promociona SOLO si mejora
- Cosecha: rescata la metodología que un indexado ingenuo se deja. En el caso real, de 72 notas de metodología escritas a mano, 0 estaban en el índice: el filtro por nombre y por profundidad las descartaba. Un tope por herramienta evita que un repo a granel (una base de exploits con decenas de miles de ficheros) ahogue lo bueno.
- Síntesis — indexación multi-representación: por cada fragmento se generan las preguntas que responde, y se embebe la pregunta, se devuelve el fragmento. Así una consulta coloquial ("cómo saco una shell reversa") se compara pregunta-contra-pregunta, no contra un comando técnico en otro idioma. El modelo solo escribe preguntas, nunca reescribe comandos: si inventa una pregunta rara, esa entrada recupera peor y ya está.
- Reindexa: une todo, deduplica y calcula los vectores.
- Evalúa: mide la recuperación con un conjunto de preguntas de prueba
(
enriquece/eval_set.jsonl). Determinista, sin juez-LLM.
Resultados, medidos
Rescatar la metodología y añadir los ganchos-pregunta, sobre el mismo conjunto de evaluación:
| antes | después | |
|---|---|---|
| hit@1 | 48 % | 82 % |
| hit@5 | 77 % | 95 % |
| similitud media | 0.689 | 0.802 |
Un resultado negativo, útil
¿Y si se cambia el embedder estático por un transformer de verdad (e5, bge)?
Medido: no compensa. intfloat/multilingual-e5-base empató con el estático
(hit@1 86 %, hit@5 95 %), arreglando dos consultas y rompiendo otras dos, a
cambio de 50-150 ms por consulta y una dependencia de torch en cada búsqueda.
El trabajo de recuperación lo hace la búsqueda híbrida, no el embedder. Se
reproduce con enriquece/experimento_embedder.py.
Por qué el índice no está en el repositorio
saber.jsonl contiene el TEXTO de los apuntes indexados, con rutas y
conocimiento personal; diario.jsonl es lo que se le dice al asistente. Son del
usuario. El .gitignore los bloquea. Lo reproducible —el método, el código y la
forma de medirlo— sí está; el corpus lo pone cada quien con sus notas.
Qué se publica y qué no, en concreto
| En el repo | |
|---|---|
El código: indexa, busca, glosario, sistema, manuales, enriquece/ |
sí |
El eval (enriquece/eval_set.jsonl) |
sí |
El glosario de tareas de Linux en castellano (glosario.py) |
sí (genérico) |
El pack de metodología y manuales (saber/conocimiento/) |
sí — limpio y genérico |
El índice público pre-construido (datos/saber-publico.jsonl) |
sí — glosario + pack, con vectores |
El índice personal (saber.jsonl) y sus copias |
no — los apuntes indexados |
El diario (diario.jsonl) |
no — las conversaciones |
Lo derivado (cosecha.jsonl, sintesis.jsonl) |
no — salen de los apuntes |
Así que un equipo recién clonado consulta al instante, sin dato privado y sin
reindexar: el saber-publico.jsonl ya trae el glosario y el pack de metodología
—enumeración, explotación web, shells, privesc, Active Directory, cheatsheets— más
los manuales de las herramientas que no traen man page (`saber/conocimiento/09
- Manuales/
, cosechados conmanuales.pyde su--help). Cuando quiera sumar sus propias notas y las *man pages* de su máquina, correindexa.pyy pasa a usar su índice personal. Los apuntes privados del usuario (susaber.jsonl`, su diario) siguen fuera; el pack los complementa.