Download app.py from CamiloS1111/ragsolver: direct link, hf CLI and curl.
- Browser
- Download file 11.1 kB
-
https://huggingface.co/CamiloS1111/ragsolver/resolve/main/app.py
- Command line
-
hf download hf://CamiloS1111/ragsolver/app.py
-
curl -L -o app.py https://huggingface.co/CamiloS1111/ragsolver/resolve/main/app.py
11.1 kB
| """Demo Gradio (docs/PLAN_RAG.md Fase 6, tarea 2): pestaña "buscar" (hits con citas, | |
| firma Lean, docs_url, recorte y descripción de figura) y pestaña "resolver" (el sistema | |
| actual: demostrador local en la GPU como etapa 0, verificación con el kernel de Lean). | |
| Local: bash scripts/demo_up.sh (levanta el demostrador en la GPU y esta demo; Lean y el | |
| embedder se precalientan en segundo plano al arrancar, ~5 min) | |
| Space: el índice se descarga con `snapshot_download` si RAGSOLVER_HF_DATASET está definido. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import re | |
| import shutil | |
| import tempfile | |
| import threading | |
| from functools import lru_cache | |
| from pathlib import Path | |
| import gradio as gr | |
| DB_PATH = os.environ.get("RAGSOLVER_DB", "data/index/lancedb") | |
| _LOOPBACK_HOSTS = {"127.0.0.1", "localhost", "::1"} | |
| _STATEMENT_RE = re.compile(r"^\s*(?:theorem|lemma)\s+[A-Za-z_][\w'.]*.*:=\s*by\s+sorry\s*$", re.S) | |
| MINIF2F_HEADER = "open BigOperators Real Nat Topology Rat" | |
| # Problemas que el sistema resolvió en eval-v3: sus muestras del demostrador están en la caché de disco, | |
| # así que en una presentación salen en segundos (la verificación de Lean sí corre en vivo). | |
| EXAMPLES = ["mathd_algebra_67", "imo_1964_p1_1", "imo_1965_p1", "amc12b_2003_p17"] | |
| _LOCK = threading.Lock() | |
| def _examples() -> list[list[str]]: | |
| path = Path("data/eval/problems/eval_v1.jsonl") | |
| if not path.exists(): | |
| return [] | |
| rows = {d["problem_id"]: d for d in (json.loads(l) for l in path.read_text(encoding="utf-8").splitlines() if l.strip())} | |
| return [[rows[i]["informal_statement"], rows[i]["formal_statement"], rows[i].get("header", "")] for i in EXAMPLES if i in rows] | |
| def _ensure_index() -> str: | |
| repo = os.environ.get("RAGSOLVER_HF_DATASET") | |
| if repo and not Path(DB_PATH).exists(): | |
| from huggingface_hub import snapshot_download | |
| local = snapshot_download(repo_id=repo, repo_type="dataset", allow_patterns=["index/**"]) | |
| return str(Path(local) / "index" / "lancedb") | |
| return DB_PATH | |
| def _retriever(): | |
| from ragsolver.rag.retrieve import Retriever | |
| r = Retriever(db_path=_ensure_index()) | |
| r.trace_dir = tempfile.mkdtemp(prefix="ragsolver-demo-") | |
| return r | |
| def _base() -> dict: | |
| from ragsolver.agents.agents import load_agents_config | |
| return load_agents_config() | |
| def _new_checker(): | |
| from ragsolver.lean.verify import LeanChecker | |
| return LeanChecker(imports=_base()["lean"]["imports"], timeout_s=_base()["lean"]["timeout_s"]) | |
| def _checker(): | |
| """Un solo REPL de Lean para toda la sesión: `import Mathlib` tarda ~5 min y no se repite por problema | |
| (el lock evita que el precalentamiento y un primer clic creen dos).""" | |
| with _LOCK: | |
| return _new_checker() | |
| def _prover(): | |
| """El demostrador local si su servidor responde (se vuelve a preguntar en cada problema: puede arrancar después).""" | |
| from ragsolver.prover import local_prover | |
| return local_prover(_base().get("prover") or {}) | |
| def _warmup() -> None: | |
| """Lean (`import Mathlib`) y el embedder en segundo plano, para que el primer problema no espere.""" | |
| try: | |
| _checker().check("theorem _warmup : True := by sorry", "trivial") | |
| print("[demo] Lean listo", flush=True) | |
| r = _retriever() | |
| if getattr(r, "versions", None): | |
| from ragsolver.rag.embed import embed_queries | |
| embed_queries(["warmup"], model_name=r.versions["embedding"]["model"]) | |
| print("[demo] embedder listo", flush=True) | |
| except Exception as e: # sin Lean o sin índice la pestaña Buscar/Resolver lo dirá al usarse | |
| print(f"[demo] precalentamiento omitido: {type(e).__name__}: {e}", flush=True) | |
| def search(query: str, corpus: str, mode: str, k: int, rerank: bool): | |
| from ragsolver.rag.schema import RetrievalRequest | |
| if not query.strip(): | |
| return [], [], "Escribí una consulta." | |
| try: | |
| res = _retriever().search(RetrievalRequest(queries=[query], mode=mode, corpora=[corpus], k=int(k), rerank=rerank, context={"problem_id": "demo", "agent": "demo"})) | |
| except (FileNotFoundError, ValueError) as e: # p.ej. el corpus informal no se publica (licencia) y no existe en el Space | |
| return [], [], f"Corpus `{corpus}` no disponible aquí: {str(e)[:200]}" | |
| rows, images = [], [] | |
| for h in res.hits: | |
| c = h.citation | |
| link = c.locator.get("docs_url") or "" | |
| sig = (h.metadata.get("signature") or "")[:200] if corpus == "mathlib" else (h.metadata.get("context_summary") or "")[:200] | |
| rows.append([c.citation_id, c.label, sig, round(c.scores.fused or 0, 4), round(c.scores.rerank, 3) if c.scores.rerank is not None else "", link, c.snippet[:200]]) | |
| for f in h.figures: | |
| if f.get("image_path") and Path(f["image_path"]).exists(): | |
| images.append((f["image_path"], f"{c.citation_id} · {f['figure_id']}: {(f.get('description') or '')[:150]}")) | |
| return rows, images, f"{len(res.hits)} hits · {res.trace.latency_ms:.0f} ms · 0 llamadas LLM" | |
| def solve(informal: str, formal: str, header: str, config: str, k_max: int): | |
| from ragsolver.agents.agents import Agents | |
| from ragsolver.agents.schemas import Problem | |
| from ragsolver.llm import LLM | |
| from ragsolver.orchestrator.graph import Orchestrator | |
| from ragsolver.orchestrator.run import drafter_agents, effective_config, run_baseline, run_prover_only | |
| if not (shutil.which("lake") or (Path.home() / ".elan" / "bin" / "lake").exists()): | |
| return "El modo **Resolver** necesita Lean 4 + Mathlib y una API key del LLM: sólo está disponible en una instalación local (`docs/install.md`).", "", [], "{}" | |
| host = os.environ.get("GRADIO_SERVER_NAME", "127.0.0.1") | |
| if host not in _LOOPBACK_HOSTS: # SEC-1: Lean ejecuta la salida del LLM sin sandbox (docs/architecture.md "Modelo de amenazas del verificador") | |
| return f"El modo **Resolver** ejecuta código no confiable (Lean sin sandbox) y sólo corre si el servidor escucha en loopback; `GRADIO_SERVER_NAME={host}` lo expone a la red. Cambiá la variable para usar este modo.", "", [], "{}" | |
| formal_statement = formal.strip() or "theorem demo : True := by sorry" | |
| if not _STATEMENT_RE.match(formal_statement): # el usuario escribe el enunciado a mano en la demo: mismo formato que exige Problem | |
| return "El enunciado formal debe empezar con `theorem <nombre>` o `lemma <nombre>` y terminar en `:= by sorry` (docs/user_guide.md §2).", "", [], "{}" | |
| base = _base() | |
| cfg = effective_config(base, config) | |
| cfg["k_max"] = int(k_max) if config not in ("no_critic",) else 1 | |
| problem = Problem(problem_id="demo", informal_statement=informal, formal_statement=formal_statement, header=header.strip()) | |
| trace_dir = tempfile.mkdtemp(prefix="ragsolver-demo-") | |
| agents = Agents(LLM(section="llm"), base, trace_dir=trace_dir) | |
| checker, prover = _checker(), _prover() | |
| if cfg["mode"] == "baseline_prover": | |
| rep = run_prover_only(problem, cfg, checker, prover) | |
| elif cfg["mode"].startswith("baseline"): | |
| rep = run_baseline(cfg["mode"], problem, cfg, agents, "demo", checker) | |
| else: | |
| rep = Orchestrator(agents, _retriever() if cfg["rag"] else None, checker, prover, drafter_agents(base, trace_dir)).solve(problem, cfg, "demo") | |
| cites = [[c.citation_id, c.label, "✔ prueba" if c.used_in_proof else ("boceto" if c.used_in_sketch else "consultada"), c.locator.get("docs_url") or ""] for c in rep.citations] | |
| who = {"prover_root": "demostrador local (etapa 0, 0 API)", "prover_draft": "demostrador local con el borrador de la API (DRAFT)", "agents": "agentes por API"}.get(rep.solved_by, "") | |
| head = f"**{rep.status.upper()}**" + (f" · resuelto por: {who}" if who else "") + f" · {rep.prover_samples} muestras del demostrador · {rep.llm_calls} llamadas LLM · {rep.wall_seconds}s · lemas verificados {rep.n_used_in_proof}/{rep.n_retrieved}" | |
| if prover is None and (cfg.get("prover") or {}).get("enabled"): | |
| head += "\n\n⚠ El demostrador local no responde (`bash scripts/demo_up.sh`): se usó el sistema sin él." | |
| if rep.verdict and not rep.verdict.ok: | |
| head += "\n\nErrores de Lean:\n```\n" + "\n".join(e["message"][:300] for e in rep.verdict.errors[:3]) + "\n```" | |
| return head, rep.lean_code or "(sin código)", cites, json.dumps(rep.model_dump(exclude={"citations", "lean_code", "verdict"}), indent=2, ensure_ascii=False) | |
| with gr.Blocks(title="RagSolver") as app: | |
| gr.Markdown("# RagSolver — recuperación y resolución verificada (Lean 4 + Mathlib v4.28.0-rc1)") | |
| with gr.Tab("Buscar"): | |
| with gr.Row(): | |
| q = gr.Textbox(label="Consulta", placeholder="prove that 2^2001 + 3^2001 is divisible by 7", scale=4) | |
| corpus = gr.Dropdown(["mathlib", "informal"], value="mathlib", label="Corpus") | |
| mode = gr.Dropdown(["nl", "formal", "error", "exemplar"], value="nl", label="Modo") | |
| k = gr.Slider(1, 20, value=8, step=1, label="k") | |
| rerank = gr.Checkbox(value=False, label="rerank (cross-encoder, +2.4 GB RAM)") | |
| btn = gr.Button("Buscar") | |
| status = gr.Markdown() | |
| table = gr.Dataframe(headers=["id", "cita", "firma / resumen", "fused", "rerank", "docs_url", "snippet"], wrap=True) | |
| gallery = gr.Gallery(label="Figuras adjuntas", columns=4, height=260) | |
| btn.click(search, [q, corpus, mode, k, rerank], [table, gallery, status]) | |
| with gr.Tab("Resolver"): | |
| informal = gr.Textbox(label="Enunciado (lenguaje natural)", lines=3, value="For every natural number n, n + 0 = n.") | |
| formal = gr.Textbox(label="Enunciado formal (Lean 4, termina en := by sorry)", lines=3, value="theorem demo (n : ℕ) : n + 0 = n := by sorry") | |
| header = gr.Textbox(label="Cabecera (open …)", value=MINIF2F_HEADER) | |
| with gr.Row(): | |
| config = gr.Dropdown(["full", "dsp", "baseline_prover", "no_rag", "baseline_a"], value="full", label="Configuración") | |
| kmax = gr.Slider(1, 5, value=3, step=1, label="k_max (reintentos de los agentes por API)") | |
| if _examples(): | |
| gr.Examples(_examples(), [informal, formal, header], label="Problemas de eval-v3 (muestras en caché: salen en segundos)") | |
| run_btn = gr.Button("Resolver (demostrador local + kernel de Lean)") | |
| out_head = gr.Markdown() | |
| out_code = gr.Code(language=None, label="Lean 4 (aceptado por el kernel)") | |
| out_cites = gr.Dataframe(headers=["id", "cita", "uso", "docs_url"], wrap=True) | |
| out_json = gr.Code(language="json", label="SolutionReport") | |
| run_btn.click(solve, [informal, formal, header, config, kmax], [out_head, out_code, out_cites, out_json]) | |
| if __name__ == "__main__": | |
| if shutil.which("lake") or (Path.home() / ".elan" / "bin" / "lake").exists(): | |
| threading.Thread(target=_warmup, daemon=True).start() | |
| app.launch(server_name=os.environ.get("GRADIO_SERVER_NAME", "127.0.0.1"), share=False) | |