Download scripts/publish_hf.py from CamiloS1111/ragsolver: direct link, hf CLI and curl.
- Browser
- Download file 9.1 kB
-
https://huggingface.co/CamiloS1111/ragsolver/resolve/main/scripts/publish_hf.py
- Command line
-
hf download hf://CamiloS1111/ragsolver/scripts/publish_hf.py
-
curl -L -o publish_hf.py https://huggingface.co/CamiloS1111/ragsolver/resolve/main/scripts/publish_hf.py
9.1 kB
| """Fase 6, tarea 1 — publicar el índice y los corpus redistribuibles en HF Hub. | |
| Sube: data/index/lancedb/{mathlib,figures}.lance (y `informal` sólo si TODOS sus | |
| documentos son redistribuibles), data/corpus/mathlib*.jsonl, data/eval/{results, | |
| problems, premises_gold.jsonl, informal_gold.jsonl}, configs/ y la dataset card. | |
| NO sube: PDFs, recortes de figuras ni texto de documentos con `redistributable: false` | |
| (docs/PLAN_RAG.md §12, licencias) — de ellos sólo van los metadatos. | |
| SEC-2 (docs/AUDIT_2026-09-23.md, docs/PLAN_CORRECCIONES.md Fase 2): `results.jsonl` | |
| de cada corrida lleva `Citation.snippet` con hasta 300 caracteres del corpus | |
| informal en las citas `corpus=informal` — si algún documento no es redistribuible, | |
| `eval/runs/` se sube desde una copia temporal saneada (snippet vaciado), nunca | |
| desde el original. Antes de subir, la misma comprobación por contenido de | |
| `scripts/check_no_leaked_content.py` corre sobre la copia; si encuentra algo, | |
| el publish se aborta (ni siquiera en `--dry-run` se ignora el resultado). | |
| Uso: HF_TOKEN=... python scripts/publish_hf.py --repo <usuario>/ragsolver-index [--dry-run] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import shutil | |
| import sys | |
| import tempfile | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| from check_no_leaked_content import check as _check_leaks # noqa: E402 | |
| CARD = """--- | |
| license: apache-2.0 | |
| tags: [lean4, mathlib, retrieval, rag, theorem-proving, olympiad] | |
| --- | |
| # RagSolver — índice de recuperación (Mathlib v4.28.0-rc1) | |
| Índice LanceDB (vector + BM25) sobre las declaraciones de Mathlib informalizadas por | |
| [LeanSearch v2](https://huggingface.co/datasets/FrenzyMath/lsv2-mathlib-v4.28.0-rc1-jsonl) | |
| (Apache 2.0), embebidas con Qwen3-Embedding-0.6B, más los conjuntos de evaluación del proyecto. | |
| Construido con https://github.com/camiloAndres11/RagSolver (`docs/reproduce.md`); código, documentación y demo en [`{space}`](https://huggingface.co/{space}). | |
| - `index/lancedb/mathlib.lance`: {n_mathlib} declaraciones ({scope}). | |
| - `index/lancedb/mathlib_full.lance`: el corpus completo ({n_full} declaraciones) con índice ANN IVF_SQ | |
| (recall@10 0.989 respecto de la búsqueda exacta; `RAGSOLVER_MATHLIB_TABLE=mathlib_full` para usarlo). | |
| - `index/lancedb/proofs.lance`: {n_proofs} pruebas Lean 4 verificadas de | |
| [Lean-workbook-proofs](https://huggingface.co/datasets/Goedel-LM/Lean-workbook-proofs) (MIT), embebidas por enunciado, sin los | |
| enunciados de miniF2F-v2 (config `rag_proofs`). | |
| - `eval/runs/`: resultados por problema × configuración, resúmenes y trazas JSONL por agente de las | |
| corridas del informe ({runs}). | |
| - `eval/prover_cache.jsonl`: las muestras de Goedel-Prover-V2-8B por (mensajes, semilla, parámetros), para repetir | |
| las corridas con el demostrador exactamente. | |
| - `corpus/`: JSONL normalizado (`MathlibChunk`, ver `docs/interfaces.md`). | |
| - `eval/`: MathlibQR/MPR, premisas gold de miniF2F (desde pruebas verificadas de DeepSeek-Prover-V2), gold informal, resultados. | |
| - Mathlib: Apache 2.0. miniF2F-v2: Apache 2.0. El corpus informal de ejemplo (PDF de olimpiadas, Univ. de Sonora 2002) **no se redistribuye**: sólo metadatos; | |
| por eso faltan `pdf_set.jsonl` (sus 32 enunciados formalizados a mano) y la corrida `eval-v4-pdf`, y las citas a ese | |
| corpus van con el `snippet` vacío. | |
| """ | |
| # Corridas del informe; `eval-v4-pdf` no: sus problemas y trazas salen del PDF (SEC-2). | |
| RUNS = ("eval-v1", "eval-v1-full", "f5-ablations", "eval-v2", "eval-v3", "eval-v4-unsolved", "eval-v4b-unsolved") | |
| NOT_REDISTRIBUTABLE = ("pdf_set.jsonl",) | |
| def _sanitize_run_dir(src: Path, dst: Path) -> None: | |
| """Copia `src` (una corrida en `data/eval/runs/<run_id>/`) a `dst` con el | |
| `snippet` de las citas `corpus=informal` vaciado en `results.jsonl` — el | |
| único vector de fuga conocido (verificado: las trazas no incluyen texto de | |
| fuente, sólo ids/scores/consultas). `src` nunca se modifica.""" | |
| shutil.copytree(src, dst) | |
| for path in (dst / "results.jsonl", dst / "errors.jsonl"): | |
| if not path.exists(): | |
| continue | |
| rows = [json.loads(l) for l in path.read_text(encoding="utf-8").splitlines() if l.strip()] | |
| for r in rows: | |
| for c in r.get("citations", []): | |
| if c.get("corpus") == "informal": | |
| c["snippet"] = "" | |
| path.write_text("".join(json.dumps(r, ensure_ascii=False) + "\n" for r in rows), encoding="utf-8") | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--repo", required=True) | |
| ap.add_argument("--space", default="", help="<usuario>/ragsolver, el repositorio del código (sólo para el enlace de la tarjeta)") | |
| ap.add_argument("--dry-run", action="store_true") | |
| args = ap.parse_args() | |
| docs = [json.loads(p.read_text(encoding="utf-8")) for p in Path("data/parsed").glob("*/document.json")] | |
| informal_ok = docs and all(d.get("redistributable") for d in docs) | |
| n_mathlib = sum(1 for _ in open("data/corpus/mathlib_sample.jsonl", encoding="utf-8")) if Path("data/corpus/mathlib_sample.jsonl").exists() else 0 | |
| scope = "muestra de evaluación" if n_mathlib < 100000 else "corpus completo" | |
| uploads = [("data/index/lancedb/mathlib.lance", "index/lancedb/mathlib.lance"), ("data/index/lancedb/mathlib_full.lance", "index/lancedb/mathlib_full.lance"), ("data/index/lancedb/proofs.lance", "index/lancedb/proofs.lance")] | |
| n_full = n_proofs = 0 | |
| if Path("data/index/lancedb/mathlib_full.lance").exists(): | |
| import lancedb | |
| n_full = lancedb.connect("data/index/lancedb").open_table("mathlib_full").count_rows() | |
| n_proofs = lancedb.connect("data/index/lancedb").open_table("proofs").count_rows() | |
| if informal_ok: # texto y figuras del corpus informal sólo si su licencia lo permite | |
| uploads += [("data/index/lancedb/informal.lance", "index/lancedb/informal.lance"), ("data/index/lancedb/figures.lance", "index/lancedb/figures.lance")] | |
| files = [("data/corpus/mathlib_sample.jsonl", "corpus/mathlib_sample.jsonl"), ("data/eval/MathlibQR.json", "eval/MathlibQR.json"), ("data/eval/MathlibMPR.json", "eval/MathlibMPR.json"), ("data/eval/premises_gold.jsonl", "eval/premises_gold.jsonl"), ("data/eval/informal_gold.jsonl", "eval/informal_gold.jsonl"), ("data/eval/prover_cache.jsonl", "eval/prover_cache.jsonl")] | |
| dirs = [("data/eval/results", "eval/results"), ("data/eval/problems", "eval/problems"), ("configs", "configs"), ("docs/interfaces", "docs/interfaces")] | |
| tmp = Path(tempfile.mkdtemp(prefix="ragsolver-publish-")) if not informal_ok else None | |
| runs = [r for r in RUNS if (Path("data/eval/runs") / r).exists()] | |
| for r in runs: | |
| src = Path("data/eval/runs") / r | |
| if not src.exists(): | |
| continue | |
| if informal_ok: # el documento es redistribuible: no hace falta sanear | |
| dirs.append((str(src), f"eval/runs/{r}")) | |
| else: | |
| sanitized = tmp / r | |
| _sanitize_run_dir(src, sanitized) | |
| dirs.append((str(sanitized), f"eval/runs/{r}")) | |
| card = CARD.format(n_mathlib=n_mathlib, scope=scope, n_full=n_full, n_proofs=n_proofs, runs=", ".join(f"`{r}`" for r in runs), space=args.space) | |
| # SEC-2: gate de contenido antes de subir nada, `--dry-run` incluido — sobre | |
| # lo que de verdad se va a subir (copias saneadas cuando corresponda). | |
| upload_files = [Path(src) for src, _ in files if Path(src).exists()] | |
| upload_files += [f for src, _ in uploads + dirs if Path(src).exists() and Path(src).is_dir() for f in Path(src).rglob("*") if f.is_file() and f.name not in NOT_REDISTRIBUTABLE] | |
| leaks = _check_leaks(upload_files) | |
| if leaks: | |
| if tmp is not None: | |
| shutil.rmtree(tmp, ignore_errors=True) | |
| raise SystemExit(f"SEC-2: {len(leaks)} archivo(s) con texto del corpus informal en lo que se iba a subir — publish abortado:\n" + "\n".join(f" {f}: {len(h)} coincidencia(s)" for f, h in leaks.items())) | |
| print(json.dumps({"repo": args.repo, "informal_redistributable": informal_ok, "folders": [d for _, d in uploads + dirs], "files": [f for _, f in files], "content_check": "OK"}, indent=2)) | |
| if args.dry_run: | |
| if tmp is not None: | |
| shutil.rmtree(tmp, ignore_errors=True) | |
| return | |
| from huggingface_hub import HfApi | |
| api = HfApi(token=os.environ.get("HF_TOKEN")) | |
| api.create_repo(args.repo, repo_type="dataset", exist_ok=True) | |
| api.upload_file(path_or_fileobj=card.encode(), path_in_repo="README.md", repo_id=args.repo, repo_type="dataset") | |
| for src, dst in uploads + dirs: | |
| if Path(src).exists(): | |
| api.upload_folder(folder_path=src, path_in_repo=dst, repo_id=args.repo, repo_type="dataset", ignore_patterns=[f"*{n}" for n in NOT_REDISTRIBUTABLE]) | |
| for src, dst in files: | |
| if Path(src).exists(): | |
| api.upload_file(path_or_fileobj=src, path_in_repo=dst, repo_id=args.repo, repo_type="dataset") | |
| if tmp is not None: | |
| shutil.rmtree(tmp, ignore_errors=True) | |
| print("ok") | |
| if __name__ == "__main__": | |
| main() | |