Spaces:
Sleeping
Sleeping
File size: 6,425 Bytes
bf9f7d1 02b6489 bf9f7d1 02b6489 bf9f7d1 02b6489 bf9f7d1 02b6489 bf9f7d1 02b6489 bf9f7d1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | """Aplica `data/rag_alcance.json` a un índice RAG ya construido, sin reingerir.
Dos pasadas, las mismas que hace la ingesta:
1. **Descartar** índices alfabéticos, sumarios y preliminares.
2. **Reetiquetar** `especie` en las secciones que no son de perro ni gato.
Reingerir cuesta OCR sobre cientos de MB de PDF; esto sólo toca metadatos y filas, deja los
vectores calculados intactos y corre en segundos. Ver `app/rag/alcance_corpus.py` para por qué
hace falta (el filtro de especie estaba inerte: todos los chunks con `especie` vacía).
uv run --group rag python ../scripts/curar_indice.py # simulacro, no escribe
uv run --group rag python ../scripts/curar_indice.py --aplicar # escribe
Tras aplicarlo hay que republicar el índice (`make publish-index`) para que las builds y las
demás máquinas se lo lleven; si no, sólo queda arreglado en local.
"""
from __future__ import annotations
import argparse
import json
import sys
from collections import Counter
from pathlib import Path
RAIZ = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(RAIZ / "backend"))
from app.config import obtener_config # noqa: E402
from app.rag.alcance_corpus import ( # noqa: E402
cargar_descartes,
cargar_rangos,
debe_descartarse,
especie_de,
)
def actualizar_manifiesto(ruta: Path, n_fragmentos: int) -> None:
"""Deja `manifest.json` de acuerdo con lo que hay realmente en la tabla."""
destino = ruta / "manifest.json"
if not destino.exists():
print("⚠ Sin manifest.json: no se puede dejar constancia del recuento.")
return
datos = json.loads(destino.read_text(encoding="utf-8"))
antes = datos.get("n_fragmentos")
datos["n_fragmentos"] = n_fragmentos
datos["curado"] = "data/rag_alcance.json"
destino.write_text(json.dumps(datos, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"manifest.json actualizado: n_fragmentos {antes} → {n_fragmentos}")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--aplicar", action="store_true",
help="escribe los cambios; sin esta bandera sólo informa de qué haría",
)
parser.add_argument("--indice", type=Path, help="ruta del índice (por defecto, la de config)")
args = parser.parse_args()
import lancedb
cfg = obtener_config()
ruta = args.indice or cfg.rag_index_dir
if not ruta.exists():
print(f"❌ No hay índice en {ruta}. Usa `make fetch-index`.")
return 1
descartes, rangos = cargar_descartes(), cargar_rangos()
if not descartes and not rangos:
print("❌ data/rag_alcance.json no declara nada: no habría qué hacer.")
return 1
print(f"Índice: {ruta}")
print(f"\nDescartes ({len(descartes)}):")
for d in descartes:
print(f" · {d.libro_empieza_por[:42]}… pp. {d.desde}–{d.hasta} ({d.motivo[:58]})")
print(f"Rangos de especie ({len(rangos)}):")
for r in rangos:
print(f" · {r.libro_empieza_por[:42]}… pp. {r.desde}–{r.hasta} → {r.especie}")
db = lancedb.connect(str(ruta))
tabla = db.open_table("literatura")
df = tabla.to_pandas()
total = len(df)
fuera = df.apply(lambda f: debe_descartarse(f["libro"], f["pagina"], f["texto"]), axis=1)
df_quedan = df[~fuera].copy()
nueva = [especie_de(f.libro, f.pagina, "") for f in df_quedan.itertuples()]
reetiquetados = sum(
1 for antes, ahora in zip(df_quedan["especie"], nueva, strict=True) if antes != ahora
)
print(f"\nchunks: {total}")
print(f" a descartar (índices/sumarios/prelim.): {int(fuera.sum())}")
print(f" quedan: {len(df_quedan)}")
print(f" cambian de especie: {reetiquetados}")
print(f" especies tras curar: {dict(Counter(nueva))}")
manifiesto = ruta / "manifest.json"
if manifiesto.exists():
declarado = json.loads(manifiesto.read_text(encoding="utf-8")).get("n_fragmentos")
if declarado != len(df_quedan):
print(f"\n⚠ manifest.json declara {declarado} fragmentos y quedarían {len(df_quedan)}.")
if not fuera.any() and not reetiquetados:
if manifiesto.exists() and declarado != len(df_quedan):
if not args.aplicar:
print("Repite con --aplicar para corregir sólo el manifiesto.")
return 0
actualizar_manifiesto(ruta, len(df_quedan))
return 0
print("\nNada que hacer.")
return 0
if not args.aplicar:
print("\nSimulacro. Repite con --aplicar para escribir.")
return 0
# Se reescribe la tabla entera desde el DataFrame: `mode='overwrite'` conserva el esquema y
# los vectores ya calculados, que es justo lo que no queremos recomputar. El índice FTS sí
# se reconstruye después, porque la sobrescritura lo invalida.
df_quedan["especie"] = nueva
db.create_table("literatura", data=df_quedan, mode="overwrite")
tabla = db.open_table("literatura")
try:
tabla.create_fts_index("texto", replace=True)
print("Índice FTS reconstruido.")
except Exception as exc: # noqa: BLE001 - informativo; el denso sigue sirviendo
print(f"⚠ No se pudo reconstruir el FTS ({exc}). Reconstrúyelo antes de confiar en el híbrido.")
# Compactar no es cosmético: LanceDB versiona, así que sobrescribir DEJA los datos viejos en
# disco y el índice CRECE al quitarle filas (medido: 73 MB → 140 MB). Como este artefacto se
# sube al Hub y se hornea en la imagen, publicarlo sin compactar multiplicaría su tamaño.
import datetime
tabla.optimize(cleanup_older_than=datetime.timedelta(0), delete_unverified=True)
tamano = sum(f.stat().st_size for f in ruta.rglob("*") if f.is_file()) / 1e6
print(f"Versiones antiguas purgadas. Tamaño en disco: {tamano:.0f} MB")
# El manifiesto lo escribe la ingesta y aquí acaba de cambiar el número de fragmentos. Si no
# se actualiza, miente: `fetch-index` lo imprime y `publish-index` lo usa como mensaje de
# commit, así que el Hub anunciaría un recuento que no es el del índice que sirve.
actualizar_manifiesto(ruta, len(df_quedan))
print(f"\n✅ {total} → {len(df_quedan)} fragmentos. Recuerda `make publish-index`.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
|