- 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.
127 lines
5.2 KiB
Python
127 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Genera preguntas-gancho para el pack de conocimiento y el catalogo de awesome.
|
|
|
|
./sintetiza_catalogo.py procesa todo (reanudable, ~1-2 h)
|
|
./sintetiza_catalogo.py --muestra 5 ensena 5 y para
|
|
|
|
Misma idea multi-representacion que sintetiza.py, pero sobre el contenido que
|
|
VIENE con el repo (conocimiento/ + awesome lists) en vez de sobre la cosecha de
|
|
COFRE. Por que importa: el indice PUBLICO pre-construido trae los fragmentos en
|
|
crudo pero SIN preguntas-gancho, asi que un clon recien hecho no tiene el salto de
|
|
recall que dan (48->82 % hit@1 medido en la metodologia). Esto lo genera para que
|
|
si venga horneado.
|
|
|
|
Respeta la marca `publico` de cada fragmento: los ganchos heredan esa marca, y
|
|
indexa.py mete en el indice publico solo los publicos (los de awesome-hacking, sin
|
|
licencia, se quedan en el personal).
|
|
|
|
Reanudable: apunta en un .hecho por que fragmento iba. El contenido publico se
|
|
procesa PRIMERO, para que si se corta, lo que se sube ya este.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
AQUI = os.path.dirname(os.path.abspath(__file__))
|
|
RAIZ = os.path.dirname(os.path.dirname(AQUI))
|
|
SALIDA = os.path.join(RAIZ, "datos", "sintesis-catalogo.jsonl")
|
|
MARCA = SALIDA + ".hecho"
|
|
|
|
# Reutiliza el modelo, la llamada a ollama y el reanudado de sintetiza.py.
|
|
sys.path.insert(0, RAIZ) # nucleo/ en el path para saber.*
|
|
from saber import awesome # noqa: E402
|
|
from saber import indexa # noqa: E402 (_conocimiento)
|
|
from saber.enriquece import sintetiza # noqa: E402
|
|
|
|
# Instruccion mas general que la de sintetiza.py (aquello era solo pentest).
|
|
sintetiza.INSTRUCCION = """Eres un generador de preguntas para un buscador de
|
|
herramientas y software. Te doy un fragmento con una lista de programas o tecnicas
|
|
de una categoria, cada uno con una breve descripcion. Devuelve de UNA a TRES
|
|
preguntas, en castellano, que una persona haria por voz buscando algo de esa
|
|
categoria y que este fragmento ayuda a responder.
|
|
|
|
Reglas:
|
|
- Preguntas naturales: "que programa hay para...", "con que puedo montar un...",
|
|
"cual es una alternativa libre a...", "que herramienta sirve para...".
|
|
- Piensa en la NECESIDAD (que quiere hacer), no en nombres concretos.
|
|
- NO respondas, NO copies nombres ni comandos: solo las preguntas.
|
|
- Si el fragmento es ruido (un titulo suelto, sin descripciones), lista vacia.
|
|
|
|
Responde SOLO con JSON: {"preguntas": ["...", "..."]}"""
|
|
|
|
|
|
def _fragmentos():
|
|
"""Conocimiento (publico) primero, luego catalogo publico, y al final el
|
|
personal (awesome-hacking). Cada uno con su marca `publico`."""
|
|
frags = []
|
|
for e in indexa._conocimiento(): # el pack del repo: todo publico
|
|
e = dict(e)
|
|
e["publico"] = True
|
|
frags.append(e)
|
|
for e in awesome.entradas(): # catalogo: ya trae `publico`
|
|
frags.append(e)
|
|
frags.sort(key=lambda d: (0 if d.get("publico") else 1,
|
|
0 if d.get("tipo") == "metodologia" else 1))
|
|
return frags
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument("--muestra", type=int, default=0)
|
|
a = p.parse_args()
|
|
|
|
frags = _fragmentos()
|
|
modelo = sintetiza._modelo()
|
|
npub = sum(1 for f in frags if f.get("publico"))
|
|
print(f" {len(frags)} fragmentos ({npub} publicos) · modelo {modelo}", flush=True)
|
|
|
|
if a.muestra:
|
|
for fr in frags[:a.muestra]:
|
|
print(f"\n ── {fr.get('fichero')} ──")
|
|
for q in sintetiza._pide(fr["texto"], modelo):
|
|
print(f" P: {q}")
|
|
return 0
|
|
|
|
desde = 0
|
|
if os.path.exists(MARCA) and os.path.exists(SALIDA):
|
|
try:
|
|
desde = int(open(MARCA).read().strip()) + 1
|
|
except (ValueError, OSError):
|
|
desde = 0
|
|
modo = "a" if desde else "w"
|
|
hechas = sum(1 for _ in open(SALIDA)) if desde and os.path.exists(SALIDA) else 0
|
|
if desde:
|
|
print(f" reanudando desde {desde} ({hechas} preguntas)", flush=True)
|
|
|
|
total = len(frags)
|
|
with open(SALIDA, modo, encoding="utf-8", buffering=1) as out:
|
|
for i in range(desde, total):
|
|
if i % 25 == 0:
|
|
modelo = sintetiza._modelo()
|
|
fr = frags[i]
|
|
for q in sintetiza._pide(fr["texto"], modelo):
|
|
out.write(json.dumps({
|
|
"tipo": "pregunta",
|
|
"herramienta": fr.get("herramienta"),
|
|
"perfil": fr.get("perfil"),
|
|
"fichero": fr.get("fichero"),
|
|
"tema": fr.get("tema", ""),
|
|
"fuente": fr.get("fuente"),
|
|
"licencia": fr.get("licencia"),
|
|
"publico": bool(fr.get("publico")),
|
|
"texto": fr["texto"],
|
|
"indexar": q,
|
|
}, ensure_ascii=False) + "\n")
|
|
hechas += 1
|
|
with open(MARCA, "w") as m:
|
|
m.write(str(i))
|
|
if (i + 1) % 25 == 0:
|
|
print(f" PROGRESO catalogo hecho={i+1} total={total} "
|
|
f"preguntas={hechas}", flush=True)
|
|
print(f"\n hecho: {hechas} preguntas en {SALIDA}", flush=True)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|