saber: preguntas-gancho sintetizadas horneadas en el indice publico

- 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.
This commit is contained in:
sito 2026-08-16 19:59:21 +02:00
parent 43335d329f
commit 873a22ea6a
4 changed files with 2043 additions and 6 deletions

View file

@ -165,6 +165,41 @@ def _catalogo(solo_publico=False):
return fragmentos
def _ganchos(solo_publico=False):
"""Las preguntas-gancho sintetizadas, si existen. Multi-representacion: cada
una embebe una pregunta (`indexar`) y muestra el fragmento (`texto`). Es lo
que sube el recall (48->82 % hit@1). Dos fuentes:
- sintesis-catalogo.jsonl: pack del repo + catalogo (enriquece/
sintetiza_catalogo.py). Lleva marca `publico` por fragmento.
- sintesis.jsonl: la sintesis NOCTURNA sobre los apuntes de COFRE
(enriquece/sintetiza.py). Es privada, nunca va al indice publico.
Se incluye aqui para que un `indexa.py` no pise el trabajo de la noche: la
reconstruccion desde cero rehornea ambas.
"""
fuentes = [
(os.path.join(AQUI, "datos", "sintesis-catalogo.jsonl"), None),
(os.path.join(AQUI, "datos", "sintesis.jsonl"), False), # COFRE: privada
]
ganchos = []
for ruta, forzar in fuentes:
if not os.path.exists(ruta):
continue
with open(ruta, encoding="utf-8") as f:
for l in f:
try:
e = json.loads(l)
except json.JSONDecodeError:
continue
pub = e.get("publico") if forzar is None else forzar
if solo_publico and not pub:
continue
e.pop("publico", None)
ganchos.append(e)
return ganchos
def _conocimiento():
"""El pack de metodologia y manuales que viene con el repo (conocimiento/)."""
fragmentos = []
@ -194,7 +229,10 @@ def _guarda_indice(todo, ruta):
modelo = StaticModel.from_pretrained("minishlab/potion-multilingual-128M")
print(" calculando vectores...", flush=True)
vectores = modelo.encode([e["texto"] for e in todo], show_progress_bar=False)
# se embebe `indexar` si existe (la pregunta-gancho sintetizada), si no `texto`.
# busca.py solo lee `texto` y `v`, asi la respuesta mostrada es el fragmento.
vectores = modelo.encode([e.get("indexar") or e["texto"] for e in todo],
show_progress_bar=False)
os.makedirs(os.path.dirname(ruta), exist_ok=True)
with open(ruta, "w", encoding="utf-8") as f:
@ -218,9 +256,10 @@ def construye_publico(solo_cuenta=False):
glo = list(glosario.entradas())
con = _conocimiento()
cat = _catalogo(solo_publico=True) # solo las awesome de licencia redistribuible
todo = glo + con + cat
gan = _ganchos(solo_publico=True) # preguntas-gancho de fragmentos publicos
todo = glo + con + cat + gan
print(f" glosario {len(glo)} + conocimiento {len(con)} + catalogo {len(cat)} "
f"= {len(todo)} entradas publicas")
f"+ ganchos {len(gan)} = {len(todo)} entradas publicas")
if solo_cuenta:
return 0
_guarda_indice(todo, INDICE_PUBLICO)
@ -286,10 +325,14 @@ def construye(solo_cuenta=False, solo_cofre=False):
if catalogo:
print(f" CATALOGO {len(catalogo):5d} fragmentos (awesome lists)")
todo = fichas + fragmentos + sistema + conocimiento + catalogo
ganchos = _ganchos(solo_publico=False)
if ganchos:
print(f" GANCHOS {len(ganchos):5d} preguntas sintetizadas")
todo = fichas + fragmentos + sistema + conocimiento + catalogo + ganchos
print(f"\n total: {len(fichas)} fichas + {len(fragmentos)} apuntes + "
f"{len(sistema)} sistema + {len(conocimiento)} conocimiento + "
f"{len(catalogo)} catalogo = {len(todo)} entradas")
f"{len(catalogo)} catalogo + {len(ganchos)} ganchos = {len(todo)} entradas")
if solo_cuenta:
return 0