CoolFace
Apppublic

build-small-hackathon/QED

sourceHugging Facemitupdated 3mo agoView on Hugging Face
7likes
front.py573 linesDownload Raw Back to root
1import urllib.parse2import gradio as gr3import requests4 5MODAL_ENDPOINT = "https://no-name13--lean-proof-agent-fastapi-app.modal.run"6 7# ── Example theorems ──────────────────────────────────────────────────────────8# Lead with add_comm_nat (induction / multi-step) so the agent loop is visible.9# Keep zero_add as the simple contrast. False theorem always last.10EXAMPLES = [11    (12        "theorem add_comm_nat : ∀ n m : Nat, n + m = m + n",13        "∀ n m · n + m = m + n — addition is commutative",14        "provable",15    ),16    (17        "theorem reverse_reverse : ∀ (α : Type) (l : List α), l.reverse.reverse = l",18        "∀ α l · reverse(reverse l) = l — reversing a list twice is identity",19        "provable",20    ),21    (22        "theorem zero_add : ∀ n : Nat, 0 + n = n",23        "∀ n · 0 + n = n — zero is the left identity for addition",24        "provable",25    ),26    (27        "theorem currying : ∀ P Q R : Prop, (P ∧ Q → R) ↔ (P → Q → R)",28        "∀ P Q R · (P∧Q→R) ↔ (P→Q→R) — currying",29        "provable",30    ),31    (32        "theorem de_morgan_and : ∀ P Q : Prop, ¬(P ∧ Q) ↔ ¬P ∨ ¬Q",33        "∀ P Q · ¬(P∧Q) ↔ ¬P∨¬Q — De Morgan's law",34        "provable",35    ),36    # FALSE theorem — the stuck detector concludes "not provable" within a few steps.37    (38        "theorem cannot_prove : ∀ n : Nat, n + 1 = n",39        "∀ n · n+1 = n — FALSE: the agent correctly cannot prove this ✗",40        "unprovable",41    ),42]43 44EXAMPLE_LOOKUP = {lean.strip(): (desc, kind) for lean, desc, kind in EXAMPLES}45 46TACTIC_EXPLANATIONS = {47    "intro": "introduces a universally quantified variable or hypothesis into the local context",48    "induction": "applies structural induction — splits into base case (zero) and inductive step (succ)",49    "simp": "simplifies the goal using a library of known equalities and lemmas",50    "rfl": "closes the goal when both sides are definitionally equal",51    "omega": "decision procedure for linear arithmetic — automatically solves goals about integers/naturals",52    "rw": "rewrites the goal using an equation",53    "exact": "closes the goal by providing an exact proof term",54    "apply": "applies a lemma whose conclusion matches the goal",55    "constructor": "splits a conjunction (∧) or iff (↔) goal into its two parts",56    "cases": "case-splits on a hypothesis or value",57    "norm_num": "solves numeric goals like 2 + 2 = 4 or 3 ∣ 6",58    "contradiction": "closes the goal when context contains P and ¬P (a direct contradiction)",59    "assumption": "closes the goal when it matches a hypothesis in the local context exactly",60    "tauto": "closes propositional tautologies automatically",61}62 63# ── Dark theme — mathematical / formal-proof terminal aesthetic ───────────────64# Wraps in try/except so a theme API mismatch never breaks the app.65CUSTOM_CSS = """66/* ── Base ───────────────────────────────────────────────── */67body, .gradio-container { background: #0d1117 !important; color: #c9d1d9 !important; }68.contain, .gap, .row { background: #0d1117 !important; }69 70/* ── Panels ─────────────────────────────────────────────── */71.block.padded, .block {72    background: #161b22 !important;73    border: 1px solid #30363d !important;74    border-radius: 8px !important;75}76.form { background: #161b22 !important; }77 78/* ── All text — catch-all first, then specifics ──────────── */79* { color: #c9d1d9; }80 81/* ── Headings ────────────────────────────────────────────── */82h1, h2, h3, h4 { color: #e6edf3 !important; }83h1 { border-bottom: 1px solid #21262d; padding-bottom: 6px; }84 85/* ── Labels (component titles, slider/checkbox labels) ────── */86label, .label-wrap, .label-wrap span,87span.text-gray-500, span.text-sm,88.block > .label-wrap > span { color: #b0bec5 !important; }89 90/* ── Body text, lists, paragraphs ───────────────────────── */91p, li, em, span { color: #c9d1d9 !important; }92a { color: #58a6ff !important; }93strong, b { color: #e6edf3 !important; }94 95/* ── Slider ──────────────────────────────────────────────── */96input[type=range] { accent-color: #2ea043 !important; }97.range-input span, .range-input output,98input[type=range] + span, .slider-container span { color: #c9d1d9 !important; }99 100/* ── Checkbox ────────────────────────────────────────────── */101input[type=checkbox] { accent-color: #2ea043 !important; }102.checkbox-label, .checkbox-label span { color: #c9d1d9 !important; }103 104/* ── Inputs ──────────────────────────────────────────────── */105textarea, input[type=text], input[type=number] {106    background: #0d1117 !important;107    color: #e6edf3 !important;108    border: 1px solid #30363d !important;109    border-radius: 6px !important;110    font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace !important;111}112textarea::placeholder { color: #484f58 !important; }113 114/* ── Primary button ──────────────────────────────────────── */115button.primary {116    background: #1a3a1a !important;117    border: 1px solid #2ea043 !important;118    color: #3fb950 !important;119    font-weight: bold !important;120    letter-spacing: 0.05em !important;121    transition: all 0.15s ease !important;122}123button.primary:hover {124    background: #2ea043 !important;125    color: #fff !important;126    box-shadow: 0 0 10px #2ea04355 !important;127}128 129/* ── Secondary / example buttons ────────────────────────── */130button.secondary, button[variant=secondary] {131    background: #21262d !important;132    border: 1px solid #30363d !important;133    color: #c9d1d9 !important;134    font-size: 0.8em !important;135    transition: all 0.12s ease !important;136}137button.secondary:hover { border-color: #3fb950 !important; color: #3fb950 !important; }138 139/* ── Code blocks ─────────────────────────────────────────── */140code {141    background: #161b22 !important;142    color: #79c0ff !important;143    border: 1px solid #30363d !important;144    border-radius: 4px !important;145    padding: 2px 5px !important;146}147pre {148    background: #161b22 !important;149    border: 1px solid #30363d !important;150    border-radius: 6px !important;151}152pre code { color: #c9d1d9 !important; border: none !important; padding: 0 !important; }153 154/* ── Markdown output ─────────────────────────────────────── */155.output-markdown, .output-markdown * { color: #c9d1d9 !important; }156.output-markdown h1, .output-markdown h2,157.output-markdown h3 { color: #e6edf3 !important; }158.output-markdown strong, .output-markdown b { color: #e6edf3 !important; }159.output-markdown a { color: #58a6ff !important; }160.output-markdown code { color: #79c0ff !important; }161.output-markdown hr { border-color: #30363d !important; }162 163/* ── Description italic below theorem input ──────────────── */164.prose em, .prose i, em, i { color: #8fb8d8 !important; }165"""166 167try:168    _theme = gr.themes.Base(169        primary_hue=gr.themes.colors.green,170        neutral_hue=gr.themes.colors.zinc,171        font=[gr.themes.GoogleFont("JetBrains Mono"), "ui-monospace", "monospace"],172        font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "ui-monospace", "monospace"],173    )174except Exception:175    _theme = None176 177 178# ── Verdict banner — three states, dark-theme colours ────────────────────────179 180def build_verdict_banner(success: bool, stuck: bool, english_desc: str = "") -> str:181    font = "font-family:'JetBrains Mono','Fira Code',ui-monospace,monospace;"182 183    if success:184        desc_html = (185            f'<div style="font-size:1.0em;color:#7ee787;margin:6px 0 10px;">'186            f'<em>{english_desc}</em></div>'187        ) if english_desc else ""188        return (189            f'<div style="background:#0d2818;border:2px solid #2ea043;border-radius:8px;'190            f'padding:16px 22px;margin:10px 0;{font}">'191            f'<div style="font-size:1.8em;font-weight:bold;color:#3fb950;letter-spacing:2px;margin-bottom:6px;">'192            f'&#10003; FORMALLY VERIFIED</div>'193            f'{desc_html}'194            f'<div style="font-size:0.82em;color:#7ee787;line-height:1.75;'195            f'border-top:1px solid #1a4a1a;padding-top:10px;margin-top:8px;">'196            f'The AI proposed proof tactics. <strong style="color:#a0e8a0;">Lean 4\'s formal kernel</strong> '197            f'checked every logical step against its axioms and accepted the proof.<br><br>'198            f'<strong style="color:#3fb950;">This cannot be faked.</strong> '199            f'Unlike a chatbot saying "yes, that\'s true," Lean\'s kernel rejects any gap in reasoning '200            f'— no exceptions, no hallucinations. The proof below is machine-checked mathematics.'201            f'</div></div>'202        )203 204    if stuck:205        desc_html = (206            f'<div style="font-size:1.0em;color:#e3b341;margin:6px 0 10px;">'207            f'<em>{english_desc}</em></div>'208        ) if english_desc else ""209        return (210            f'<div style="background:#1c1400;border:2px solid #d29922;border-radius:8px;'211            f'padding:16px 22px;margin:10px 0;{font}">'212            f'<div style="font-size:1.7em;font-weight:bold;color:#e3b341;letter-spacing:1px;margin-bottom:6px;">'213            f'&#9888; NOT PROVABLE AS STATED</div>'214            f'{desc_html}'215            f'<div style="font-size:0.82em;color:#c9a227;line-height:1.75;'216            f'border-top:1px solid #3a2800;padding-top:10px;margin-top:8px;">'217            f'The agent detected it was stuck: the same goal state recurred with no progress.<br><br>'218            f'<strong style="color:#e3b341;">This is a deliberate conclusion, not a failure.</strong> '219            f'The claim as written cannot be proven — it may be mathematically false, '220            f'or require axioms and tactics outside the current mode. '221            f'Recognising when something is unprovable is part of what a formal proof agent does.'222            f'</div></div>'223        )224 225    return (226        f'<div style="background:#1c0a0a;border:2px solid #c62828;border-radius:8px;'227        f'padding:16px 22px;margin:10px 0;{font}">'228        f'<div style="font-size:1.6em;font-weight:bold;color:#f85149;margin-bottom:8px;">'229        f'&#10007; SEARCH INCOMPLETE</div>'230        f'<div style="font-size:0.82em;color:#c9d1d9;line-height:1.7;">'231        f'The agent exhausted its step budget without completing the proof. '232        f'Partial progress is shown below — try increasing the step limit or picking a simpler theorem.'233        f'</div></div>'234    )235 236 237# ── SVG proof tree — DO NOT MODIFY ───────────────────────────────────────────238 239def build_proof_tree_svg(steps: list, tactics: list, success: bool,240                         stuck: bool = False, claim: str = "") -> str:241    if not steps:242        return ""243 244    NODE_W, NODE_H = 300, 44245    STEP_H = 140246    SVG_W = 820247    CX = SVG_W // 2248 249    n = len(steps)250    title_h = 72 if claim else 52251    SVG_H = title_h + (n + (1 if (success or stuck) else 0)) * STEP_H + NODE_H + 20252 253    def esc(s):254        return str(s).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')255 256    def trunc(s, maxn=36):257        s = str(s).replace('\n', ' ').strip()258        return s[:maxn] + '…' if len(s) > maxn else s259 260    out = []261    out.append(f'<svg xmlns="http://www.w3.org/2000/svg" width="{SVG_W}" height="{SVG_H}">')262    out.append(f'<rect width="{SVG_W}" height="{SVG_H}" fill="#030c04" rx="8"/>')263    out.append('<defs>')264    for mid, col in [('ahg', '#00e639'), ('ahr', '#f38ba8'), ('ahgr', '#6c7086'), ('aha', '#e6a817')]:265        out.append(266            f'<marker id="{mid}" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">'267            f'<polygon points="0 0,8 3,0 6" fill="{col}"/></marker>'268        )269    out.append('</defs>')270 271    if success:272        title, tc = "Proof Tree — ✓ Verified", "#00e639"273    elif stuck:274        title, tc = "Proof Tree — ⚠ Concluded: Not Provable", "#e6a817"275    else:276        title, tc = "Proof Tree — ✗ Incomplete", "#f38ba8"277 278    out.append(279        f'<text x="{CX}" y="28" text-anchor="middle" '280        f'font-family="monospace" font-size="14" font-weight="bold" fill="{tc}">{esc(title)}</text>'281    )282 283    if claim:284        out.append(285            f'<text x="{CX}" y="47" text-anchor="middle" '286            f'font-family="monospace" font-size="10" fill="#70b870">'287            f'Claim: {esc(trunc(claim, 74))}</text>'288        )289 290    legend_y = title_h - 10291    for lx, col, label in [(20, '#00e639', 'successful path'), (168, '#f38ba8', 'failed attempt')]:292        out.append(f'<line x1="{lx}" y1="{legend_y}" x2="{lx+20}" y2="{legend_y}" stroke="{col}" stroke-width="2"/>')293        out.append(294            f'<text x="{lx+24}" y="{legend_y+4}" font-family="monospace" font-size="9" fill="{col}">{label}</text>'295        )296 297    for i, step in enumerate(steps):298        cy = title_h + i * STEP_H + NODE_H // 2299        chosen = step.get('chosen', '')300        candidates = step.get('candidates', [])301        status = step.get('status', '')302        goal = step.get('goal', '')303        failed = [c for c in candidates if c != chosen]304 305        on_path = bool(chosen) and status != 'all_failed'306        if on_path and success:307            bg, border = '#001a00', '#00e639'308        elif status == 'all_failed':309            bg, border = '#1a0000', '#f38ba8'310        else:311            bg, border = '#050f05', '#2a5a2a'312 313        nx, ny = CX - NODE_W // 2, cy - NODE_H // 2314        out.append(315            f'<rect x="{nx}" y="{ny}" width="{NODE_W}" height="{NODE_H}" '316            f'rx="5" fill="{bg}" stroke="{border}" stroke-width="1.5"/>'317        )318        out.append(319            f'<text x="{CX}" y="{cy+5}" text-anchor="middle" '320            f'font-family="monospace" font-size="10" fill="#98c898">'321            f'{esc(trunc(f"Step {i}: {goal}"))}</text>'322        )323 324        for j, fc in enumerate(failed):325            bx = CX + NODE_W // 2 + 55 + j * 75326            by = cy + STEP_H // 3327            out.append(328                f'<line x1="{CX+NODE_W//2}" y1="{cy}" x2="{bx}" y2="{by}" '329                f'stroke="#f38ba8" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#ahr)"/>'330            )331            mx = (CX + NODE_W // 2 + bx) // 2 + 3332            my = (cy + by) // 2 - 3333            out.append(334                f'<text x="{mx}" y="{my}" font-family="monospace" font-size="8" fill="#f38ba8">'335                f'{esc(trunc(fc, 18))}</text>'336            )337            out.append(f'<circle cx="{bx}" cy="{by}" r="7" fill="#1a0000" stroke="#f38ba8" stroke-width="1"/>')338            out.append(339                f'<text x="{bx}" y="{by+4}" text-anchor="middle" '340                f'font-family="monospace" font-size="9" fill="#f38ba8">✗</text>'341            )342 343        next_cy = title_h + (i + 1) * STEP_H + NODE_H // 2344        if on_path:345            color, mid = ('#00e639', 'ahg') if success else ('#6c7086', 'ahgr')346            out.append(347                f'<line x1="{CX}" y1="{cy+NODE_H//2}" x2="{CX}" y2="{next_cy-NODE_H//2}" '348                f'stroke="{color}" stroke-width="2" marker-end="url(#{mid})"/>'349            )350            ly = (cy + NODE_H // 2 + next_cy - NODE_H // 2) // 2351            out.append(352                f'<text x="{CX+6}" y="{ly}" font-family="monospace" font-size="9" fill="{color}">'353                f'{esc(trunc(chosen, 28))}</text>'354            )355        elif status == 'all_failed':356            out.append(357                f'<line x1="{CX}" y1="{cy+NODE_H//2}" x2="{CX}" y2="{cy+NODE_H//2+28}" '358                f'stroke="#f38ba8" stroke-width="1.5" stroke-dasharray="4,3"/>'359            )360 361    if success or stuck:362        cy = title_h + n * STEP_H + NODE_H // 2363        nx, ny = CX - NODE_W // 2, cy - NODE_H // 2364        if success:365            node_fill, node_stroke = '#002800', '#00e639'366            node_text, node_col = '✓ QED — Goals accomplished!', '#00ff41'367        else:368            node_fill, node_stroke = '#201000', '#e6a817'369            node_text, node_col = '⚠ Concluded: not provable as stated', '#f5c842'370        out.append(371            f'<rect x="{nx}" y="{ny}" width="{NODE_W}" height="{NODE_H}" '372            f'rx="5" fill="{node_fill}" stroke="{node_stroke}" stroke-width="2"/>'373        )374        out.append(375            f'<text x="{CX}" y="{cy+5}" text-anchor="middle" '376            f'font-family="monospace" font-size="11" font-weight="bold" fill="{node_col}">'377            f'{esc(node_text)}</text>'378        )379 380    out.append('</svg>')381    return f'<div style="overflow-x:auto;padding:8px">{"".join(out)}</div>'382 383 384# ── Step walkthrough ──────────────────────────────────────────────────────────385 386def explain_tactic(tactic: str) -> str:387    for key, explanation in TACTIC_EXPLANATIONS.items():388        if tactic.strip().startswith(key):389            return f"*`{key}` — {explanation}*"390    return ""391 392 393def make_playground_url(theorem_stmt: str, tactics: list) -> str:394    proof_lines = [f"{theorem_stmt} := by"] + [395        f"  {line}" for t in tactics for line in t.strip().split("\n")396    ]397    code = "\n".join(proof_lines)398    return "https://live.lean-lang.org/#code=" + urllib.parse.quote(code)399 400 401def format_steps(steps: list, tactics: list, stuck: bool = False,402                 theorem_stmt: str = "") -> str:403    if not steps:404        return ""405    out = ["### Agent loop: propose → verify → learn\n"]406    for s in steps:407        goal = s['goal']408        chosen = s.get('chosen', '')409        candidates = s.get('candidates', [])410        status = s.get('status', '')411        error = s.get('error', '')412        step_num = s['step']413 414        out.append(f"---\n**Step {step_num}** — current goal:")415        out.append(f"```\n{goal}\n```")416 417        if not candidates:418            out.append("⚠️ *LLM endpoint warming up — no candidates available at this step*")419        else:420            rejected = [c for c in candidates if c != chosen]421            out.append(f"\U0001f916 **LLM proposed:** `{'`, `'.join(candidates)}`")422            for r in rejected:423                out.append(f"- ❌ `{r}` — Lean kernel rejected")424            if error and rejected:425                short_err = error.replace('\n', ' ')[:120]426                out.append(f"- \U0001f4e2 *Kernel error fed back to agent:* `{short_err}`")427            if chosen and status != 'all_failed':428                exp = explain_tactic(chosen)429                out.append(f"- ✅ `{chosen}` — Lean kernel accepted")430                if exp:431                    out.append(f"  {exp}")432                out.append(f"- *Result:* `{status}`")433            elif status == 'all_failed':434                out.append("- ❌ All candidates rejected — feeding errors back, trying next step")435 436        out.append("")437 438    if stuck:439        out.append(440            "---\n⚠️ **Search concluded** — the same goal state recurred with no progress.\n"441            "The claim could not be proven. It may be mathematically false, "442            "or require axioms and tactics outside the current mode."443        )444    elif tactics:445        proof_lines = ["by"] + [f"  {line}" for t in tactics for line in t.strip().split("\n")]446        proof_block = "\n".join(proof_lines)447        out.append("---\n### Complete proof\n")448        out.append("```lean4")449        out.append(proof_block)450        out.append("```")451        out.append("\n---\n### Verify it yourself")452        if theorem_stmt:453            url = make_playground_url(theorem_stmt, tactics)454            out.append(455                f"[**▶ Open in Lean 4 web playground ↗**]({url})\n\n"456                "The proof is pre-filled and ready to run. "457                "The kernel outputs **\"Goals accomplished!\"** or rejects it if anything is wrong. "458                "**It cannot be convinced. It cannot be fooled.**"459            )460        else:461            out.append(462                "Paste the proof above into [live.lean-lang.org ↗](https://live.lean-lang.org/). "463                "The kernel outputs **\"Goals accomplished!\"** — or rejects it if anything is wrong."464            )465 466    return "\n".join(out)467 468 469# ── Main proof handler ────────────────────────────────────────────────────────470 471def prove_theorem(theorem: str, max_steps: int, use_fallbacks: bool):472    if not theorem.strip():473        yield "Please enter a theorem statement.", "", ""474        return475 476    yield "⏳ Sending to proof agent on Modal…", "", ""477 478    lookup = EXAMPLE_LOOKUP.get(theorem.strip())479    english_desc = lookup[0] if lookup else ""480 481    try:482        resp = requests.post(483            f"{MODAL_ENDPOINT}/prove",484            json={485                "theorem": theorem,486                "max_steps": max_steps,487                "use_fallbacks": use_fallbacks,488                "show_reasoning": True,   # always run live loop, skip cache read489            },490            timeout=280,491        )492        data = resp.json()493    except requests.exceptions.Timeout:494        yield "❌ Request timed out. Try a simpler theorem or fewer max steps.", "", ""495        return496    except Exception as e:497        yield f"❌ Error contacting proof agent: {e}", "", ""498        return499 500    warmup_note = ""501    msg = data.get("message", "")502    if "warming up" in msg.lower() or "unavailable" in msg.lower():503        warmup_note = "⚠️ LLM endpoint warming up (cold start) — proof attempted with fallback tactics only.\n\n"504 505    success = data["success"]506    stuck = data.get("stuck", False)507 508    banner = build_verdict_banner(success, stuck, english_desc)509    details = format_steps(510        data["steps"], data["tactics"],511        stuck=stuck, theorem_stmt=theorem.strip()512    )513    svg_html = build_proof_tree_svg(514        data["steps"], data["tactics"], success, stuck=stuck, claim=english_desc515    )516    yield warmup_note + banner, details, svg_html517 518 519# ── UI ────────────────────────────────────────────────────────────────────────520 521_blocks_kwargs: dict = dict(title="Q.E.D", css=CUSTOM_CSS)522if _theme is not None:523    _blocks_kwargs["theme"] = _theme524 525with gr.Blocks(**_blocks_kwargs) as demo:526    gr.Markdown("""527# ⊢ Q.E.D528**∀ theorem → ∃ proof** — LLM-guided formal verification, powered by Modal.529 530Enter a theorem in Lean 4 syntax (∀ ∃ ¬ ∧ ∨ → ↔ ℕ ℤ α all supported).531The agent proposes tactics, Lean's kernel verifies each step, and kernel errors feed back into the next proposal.532Watch it **prove** a true theorem — or **correctly conclude** a false one is unprovable.533""")534 535    with gr.Row():536        with gr.Column(scale=2):537            theorem_input = gr.Textbox(538                label="Theorem statement",539                placeholder="theorem my_thm : ∀ n : Nat, 0 + n = n",540                value=EXAMPLES[0][0],541                lines=3,542            )543            desc_display = gr.Markdown(544                value=f"*{EXAMPLES[0][1]}*",545                label="",546            )547            with gr.Row():548                max_steps = gr.Slider(5, 30, value=20, step=1, label="Max steps")549                use_fallbacks = gr.Checkbox(value=True, label="Use fallback tactics (ω, simp…)")550            prove_btn = gr.Button("⊢ Prove", variant="primary")551 552        with gr.Column(scale=1):553            gr.Markdown("**Examples** — click to load\n\n*∀ provable ones first, then a false one ↓*")554            for lean_stmt, english_desc, kind in EXAMPLES:555                btn = gr.Button(english_desc, size="sm")556                btn.click(557                    fn=lambda lean=lean_stmt, d=english_desc: (lean, f"*{d}*"),558                    outputs=[theorem_input, desc_display],559                )560 561    banner_out = gr.HTML(label="Verdict")562    steps_out = gr.Markdown(label="Agent loop walkthrough")563    tree_out = gr.HTML(label="Proof search tree")564 565    prove_btn.click(566        fn=prove_theorem,567        inputs=[theorem_input, max_steps, use_fallbacks],568        outputs=[banner_out, steps_out, tree_out],569    )570 571demo.queue()572demo.launch()573