"""Publicación y descarga de los artefactos RAG en repos privados del Hub. Se usa la API de Python (`huggingface_hub`) y no el CLI `hf` a propósito: en la versión instalada (1.16.1) el CLI arrastra una incompatibilidad typer/click que hace que `ctx.exit()` propague un `click.exceptions.Exit(0)` como traceback y **devuelva código 1 aunque la operación haya ido bien**. Eso es inservible dentro de un Makefile, donde un exit≠0 aborta la cadena. Artefactos: - índice (~70 MB): derivado de libros con licencia, contiene su texto troceado → repo PRIVADO. - libros (~226 MB): los PDF originales → repo PRIVADO, sólo necesarios para reingerir. Uso: python scripts/hub.py publish-index python scripts/hub.py fetch-index python scripts/hub.py publish-books """ from __future__ import annotations import argparse import json import sys from pathlib import Path from huggingface_hub import HfApi, snapshot_download RAIZ = Path(__file__).resolve().parents[1] DIR_INDICE = RAIZ / "instance" / "rag_index" DIR_LIBROS = RAIZ / "books" REPO_INDICE = "blackmistcode/morphos-rag-index" REPO_LIBROS = "blackmistcode/morphos-books" def _asegurar_repo(api: HfApi, repo_id: str) -> None: """Crea el repo si falta. `private=True` sólo aplica en la creación: si el repo ya existe NO lo vuelve privado, así que se comprueba explícitamente y se aborta si es público.""" api.create_repo(repo_id=repo_id, repo_type="dataset", private=True, exist_ok=True) if not api.dataset_info(repo_id).private: sys.exit( f"ABORTADO: {repo_id} es PÚBLICO. Contiene material con licencia; hazlo privado " f"antes de subir nada." ) def publicar_indice() -> None: if not DIR_INDICE.exists(): sys.exit(f"ERROR: no existe {DIR_INDICE}. Ejecuta 'make ingest' primero.") manifiesto = json.loads((DIR_INDICE / "manifest.json").read_text(encoding="utf-8")) hash_corpus = manifiesto.get("hash_corpus", "desconocido") api = HfApi() _asegurar_repo(api, REPO_INDICE) api.upload_folder( repo_id=REPO_INDICE, repo_type="dataset", folder_path=str(DIR_INDICE), commit_message=f"Índice RAG · corpus {hash_corpus} · {manifiesto.get('n_fragmentos')} fragmentos", ) print(f"OK: índice publicado en {REPO_INDICE} (corpus {hash_corpus})") def descargar_indice() -> None: DIR_INDICE.mkdir(parents=True, exist_ok=True) snapshot_download( repo_id=REPO_INDICE, repo_type="dataset", local_dir=str(DIR_INDICE), ) manifiesto = DIR_INDICE / "manifest.json" if manifiesto.exists(): m = json.loads(manifiesto.read_text(encoding="utf-8")) print(f"OK: índice descargado · corpus {m.get('hash_corpus')} · {m.get('n_fragmentos')} fragmentos") else: print("AVISO: descargado sin manifest.json; el índice puede estar incompleto.") def publicar_libros() -> None: pdfs = sorted(DIR_LIBROS.glob("*.pdf")) if not pdfs: sys.exit(f"ERROR: no hay PDFs en {DIR_LIBROS}.") api = HfApi() _asegurar_repo(api, REPO_LIBROS) api.upload_folder( repo_id=REPO_LIBROS, repo_type="dataset", folder_path=str(DIR_LIBROS), allow_patterns=["*.pdf"], commit_message=f"Corpus veterinario con licencia ({len(pdfs)} PDF)", ) print(f"OK: {len(pdfs)} libros publicados en {REPO_LIBROS}") def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("accion", choices=["publish-index", "fetch-index", "publish-books"]) args = parser.parse_args() { "publish-index": publicar_indice, "fetch-index": descargar_indice, "publish-books": publicar_libros, }[args.accion]() if __name__ == "__main__": main()