File size: 9,096 Bytes
81f30c1 e53cb42 81f30c1 | 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 153 154 155 | """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()
|