- sintetiza_catalogo.py genera preguntas en castellano (indexacion multi- representacion) para el pack de conocimiento y el catalogo de awesome lists. - indexa.py hornea los ganchos: al indice publico solo los de fragmentos publicos (~1856); el personal ademas rehornea la sintesis nocturna de COFRE (privada), para que reconstruir no pise ese trabajo. - saber-publico.jsonl pasa a 2555 entradas con los vectores de las preguntas: un clon recien hecho consulta con recall alto sin correr sintesis. - eval del indice personal: hit@1 82%, hit@5 97%, sim 0.927.
7 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 catálogo de awesome lists (saber/awesome.py) |
sí — solo lo redistribuible (CC0, CC BY-SA), ver ATRIBUCION |
El índice público pre-construido (datos/saber-publico.jsonl) |
sí — glosario + pack + catálogo + preguntas-gancho, 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.
El catálogo: awesome lists
Para el "¿qué existe para hacer X?" —no cómo, sino qué herramienta o software hay—
awesome.py indexa awesome lists curadas: parsea sus listas (markdown y
reStructuredText), las limpia de enlaces y badges, y agrupa cada categoría en un
fragmento ("Analytics: GoatCounter — …, Plausible — …"). Cada fuente lleva su
licencia: al índice público solo van las redistribuibles —awesome
(sindresorhus, CC0) y awesome-selfhosted (CC BY-SA 3.0, con atribución en
ATRIBUCION.md)—. awesome-hacking, sin licencia explícita, se
indexa solo en el índice personal si se clona en ~/COFRE/CODERS/. Para
ampliar el catálogo, se clona otra lista ahí y se reindexa.
Las preguntas-gancho, ya horneadas
El salto de recall (48→82 % hit@1) viene de la indexación multi-representación:
por cada fragmento, preguntas en castellano que lo encuentran. Generarlas cuesta
horas de modelo local, así que enriquece/sintetiza_catalogo.py las produce para
el pack y el catálogo, y el índice público las trae ya calculadas. Un clon
recién hecho consulta con esa calidad sin correr ni un minuto de síntesis: sobre
el saber-publico.jsonl hay ~1.900 preguntas-gancho además de los fragmentos en
crudo. Las de fragmentos no públicos (p. ej. awesome-hacking) se quedan fuera,
como el resto de su contenido.