Spaces:
Sleeping
Publicar el índice curado y evitar que el Hub acumule ficheros huérfanos
Browse filesAl publicar el índice curado (6772 → 6201 fragmentos) aparecieron dos fallos en
la ruta de publicación:
- `upload_folder` no borraba lo que ya no existe en local. LanceDB versiona por
ficheros, así que al compactar desaparecen los datos viejos y el remoto se los
quedaba: el repo tenía 2 ficheros de datos y 2 índices FTS huérfanos, 73 MB
para servir 33 MB — que además se hornean en la imagen. Ahora el remoto es un
espejo del local (`delete_patterns="*"`).
- `manifest.json` lo escribe la ingesta, pero `curar_indice.py` cambia el número
de filas después. Publicaba un recuento desfasado sin romper nada visible, que
es lo peligroso: el mensaje de commit del Hub y `fetch-index` anunciaban 6772
fragmentos sobre un índice de 6201. `curar_indice.py` lo reconcilia y
`publish-index` aborta si no cuadra.
Verificado descargando el índice publicado en limpio: 13 ficheros, 33 MB, los 51
tests de alcance en verde y la eval de recuperación en 0.814/0.941/0.912.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- scripts/curar_indice.py +32 -0
- scripts/hub.py +29 -0
|
@@ -19,6 +19,7 @@ demás máquinas se lo lleven; si no, sólo queda arreglado en local.
|
|
| 19 |
from __future__ import annotations
|
| 20 |
|
| 21 |
import argparse
|
|
|
|
| 22 |
import sys
|
| 23 |
from collections import Counter
|
| 24 |
from pathlib import Path
|
|
@@ -35,6 +36,20 @@ from app.rag.alcance_corpus import ( # noqa: E402
|
|
| 35 |
)
|
| 36 |
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
def main() -> int:
|
| 39 |
parser = argparse.ArgumentParser()
|
| 40 |
parser.add_argument(
|
|
@@ -83,7 +98,19 @@ def main() -> int:
|
|
| 83 |
print(f" cambian de especie: {reetiquetados}")
|
| 84 |
print(f" especies tras curar: {dict(Counter(nueva))}")
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
if not fuera.any() and not reetiquetados:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
print("\nNada que hacer.")
|
| 88 |
return 0
|
| 89 |
if not args.aplicar:
|
|
@@ -111,6 +138,11 @@ def main() -> int:
|
|
| 111 |
tamano = sum(f.stat().st_size for f in ruta.rglob("*") if f.is_file()) / 1e6
|
| 112 |
print(f"Versiones antiguas purgadas. Tamaño en disco: {tamano:.0f} MB")
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
print(f"\n✅ {total} → {len(df_quedan)} fragmentos. Recuerda `make publish-index`.")
|
| 115 |
return 0
|
| 116 |
|
|
|
|
| 19 |
from __future__ import annotations
|
| 20 |
|
| 21 |
import argparse
|
| 22 |
+
import json
|
| 23 |
import sys
|
| 24 |
from collections import Counter
|
| 25 |
from pathlib import Path
|
|
|
|
| 36 |
)
|
| 37 |
|
| 38 |
|
| 39 |
+
def actualizar_manifiesto(ruta: Path, n_fragmentos: int) -> None:
|
| 40 |
+
"""Deja `manifest.json` de acuerdo con lo que hay realmente en la tabla."""
|
| 41 |
+
destino = ruta / "manifest.json"
|
| 42 |
+
if not destino.exists():
|
| 43 |
+
print("⚠ Sin manifest.json: no se puede dejar constancia del recuento.")
|
| 44 |
+
return
|
| 45 |
+
datos = json.loads(destino.read_text(encoding="utf-8"))
|
| 46 |
+
antes = datos.get("n_fragmentos")
|
| 47 |
+
datos["n_fragmentos"] = n_fragmentos
|
| 48 |
+
datos["curado"] = "data/rag_alcance.json"
|
| 49 |
+
destino.write_text(json.dumps(datos, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
| 50 |
+
print(f"manifest.json actualizado: n_fragmentos {antes} → {n_fragmentos}")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
def main() -> int:
|
| 54 |
parser = argparse.ArgumentParser()
|
| 55 |
parser.add_argument(
|
|
|
|
| 98 |
print(f" cambian de especie: {reetiquetados}")
|
| 99 |
print(f" especies tras curar: {dict(Counter(nueva))}")
|
| 100 |
|
| 101 |
+
manifiesto = ruta / "manifest.json"
|
| 102 |
+
if manifiesto.exists():
|
| 103 |
+
declarado = json.loads(manifiesto.read_text(encoding="utf-8")).get("n_fragmentos")
|
| 104 |
+
if declarado != len(df_quedan):
|
| 105 |
+
print(f"\n⚠ manifest.json declara {declarado} fragmentos y quedarían {len(df_quedan)}.")
|
| 106 |
+
|
| 107 |
if not fuera.any() and not reetiquetados:
|
| 108 |
+
if manifiesto.exists() and declarado != len(df_quedan):
|
| 109 |
+
if not args.aplicar:
|
| 110 |
+
print("Repite con --aplicar para corregir sólo el manifiesto.")
|
| 111 |
+
return 0
|
| 112 |
+
actualizar_manifiesto(ruta, len(df_quedan))
|
| 113 |
+
return 0
|
| 114 |
print("\nNada que hacer.")
|
| 115 |
return 0
|
| 116 |
if not args.aplicar:
|
|
|
|
| 138 |
tamano = sum(f.stat().st_size for f in ruta.rglob("*") if f.is_file()) / 1e6
|
| 139 |
print(f"Versiones antiguas purgadas. Tamaño en disco: {tamano:.0f} MB")
|
| 140 |
|
| 141 |
+
# El manifiesto lo escribe la ingesta y aquí acaba de cambiar el número de fragmentos. Si no
|
| 142 |
+
# se actualiza, miente: `fetch-index` lo imprime y `publish-index` lo usa como mensaje de
|
| 143 |
+
# commit, así que el Hub anunciaría un recuento que no es el del índice que sirve.
|
| 144 |
+
actualizar_manifiesto(ruta, len(df_quedan))
|
| 145 |
+
|
| 146 |
print(f"\n✅ {total} → {len(df_quedan)} fragmentos. Recuerda `make publish-index`.")
|
| 147 |
return 0
|
| 148 |
|
|
@@ -43,11 +43,35 @@ def _asegurar_repo(api: HfApi, repo_id: str) -> None:
|
|
| 43 |
)
|
| 44 |
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
def publicar_indice() -> None:
|
| 47 |
if not DIR_INDICE.exists():
|
| 48 |
sys.exit(f"ERROR: no existe {DIR_INDICE}. Ejecuta 'make ingest' primero.")
|
| 49 |
manifiesto = json.loads((DIR_INDICE / "manifest.json").read_text(encoding="utf-8"))
|
| 50 |
hash_corpus = manifiesto.get("hash_corpus", "desconocido")
|
|
|
|
| 51 |
api = HfApi()
|
| 52 |
_asegurar_repo(api, REPO_INDICE)
|
| 53 |
api.upload_folder(
|
|
@@ -55,6 +79,11 @@ def publicar_indice() -> None:
|
|
| 55 |
repo_type="dataset",
|
| 56 |
folder_path=str(DIR_INDICE),
|
| 57 |
commit_message=f"Índice RAG · corpus {hash_corpus} · {manifiesto.get('n_fragmentos')} fragmentos",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
)
|
| 59 |
print(f"OK: índice publicado en {REPO_INDICE} (corpus {hash_corpus})")
|
| 60 |
|
|
|
|
| 43 |
)
|
| 44 |
|
| 45 |
|
| 46 |
+
def _comprobar_manifiesto(manifiesto: dict) -> None:
|
| 47 |
+
"""Aborta si `manifest.json` no coincide con la tabla que se va a publicar.
|
| 48 |
+
|
| 49 |
+
El manifiesto lo escribe la ingesta, pero `curar_indice.py` cambia el número de filas
|
| 50 |
+
después. Publicar uno desfasado no rompe nada visible —y por eso es peligroso—: el mensaje
|
| 51 |
+
de commit del Hub y lo que imprime `fetch-index` anuncian un recuento que no es el del
|
| 52 |
+
índice que sirve, y a partir de ahí nadie sabe qué hay publicado.
|
| 53 |
+
"""
|
| 54 |
+
try:
|
| 55 |
+
import lancedb
|
| 56 |
+
except ImportError:
|
| 57 |
+
print("AVISO: sin lancedb no se puede verificar el manifiesto; se publica a ciegas.")
|
| 58 |
+
return
|
| 59 |
+
tabla = lancedb.connect(str(DIR_INDICE)).open_table("literatura")
|
| 60 |
+
reales = tabla.count_rows()
|
| 61 |
+
declarados = manifiesto.get("n_fragmentos")
|
| 62 |
+
if declarados != reales:
|
| 63 |
+
sys.exit(
|
| 64 |
+
f"ABORTADO: manifest.json declara {declarados} fragmentos y la tabla tiene {reales}.\n"
|
| 65 |
+
f"Ejecuta 'make curar-indice ARGS=--aplicar' para reconciliarlo antes de publicar."
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
def publicar_indice() -> None:
|
| 70 |
if not DIR_INDICE.exists():
|
| 71 |
sys.exit(f"ERROR: no existe {DIR_INDICE}. Ejecuta 'make ingest' primero.")
|
| 72 |
manifiesto = json.loads((DIR_INDICE / "manifest.json").read_text(encoding="utf-8"))
|
| 73 |
hash_corpus = manifiesto.get("hash_corpus", "desconocido")
|
| 74 |
+
_comprobar_manifiesto(manifiesto)
|
| 75 |
api = HfApi()
|
| 76 |
_asegurar_repo(api, REPO_INDICE)
|
| 77 |
api.upload_folder(
|
|
|
|
| 79 |
repo_type="dataset",
|
| 80 |
folder_path=str(DIR_INDICE),
|
| 81 |
commit_message=f"Índice RAG · corpus {hash_corpus} · {manifiesto.get('n_fragmentos')} fragmentos",
|
| 82 |
+
# El remoto debe ser un ESPEJO del local, no una acumulación. LanceDB versiona por
|
| 83 |
+
# ficheros: al compactar desaparecen los datos viejos, y sin esto se quedarían en el Hub
|
| 84 |
+
# para siempre. Medido: el repo tenía 2 ficheros de datos y 2 índices FTS huérfanos de
|
| 85 |
+
# versiones anteriores — 73 MB para servir 33 MB, que además se hornean en la imagen.
|
| 86 |
+
delete_patterns="*",
|
| 87 |
)
|
| 88 |
print(f"OK: índice publicado en {REPO_INDICE} (corpus {hash_corpus})")
|
| 89 |
|