MARL / app.py
openfree's picture
Update app.py
fbd5b4c verified
Raw
History Blame Contribute Delete
59.8 kB
"""
MARL — Model-Agnostic Runtime Middleware for LLMs
"""
print("=" * 50)
print(" MARL Starting...")
print("=" * 50)
import os, sys, time, traceback
# ── Path setup ──
APP_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, APP_DIR)
print(f" APP_DIR: {APP_DIR}")
print(f" CWD: {os.getcwd()}")
print(f" Files: {os.listdir(APP_DIR)}")
# ── marl package check ──
pkg_dir = os.path.join(APP_DIR, "marl")
if os.path.isdir(pkg_dir):
print(f" marl/: {os.listdir(pkg_dir)}")
else:
print(f" ⚠️ marl/ directory NOT FOUND at {pkg_dir}")
# also check cwd
cwd_pkg = os.path.join(os.getcwd(), "marl")
if os.path.isdir(cwd_pkg):
print(f" Found at CWD: {cwd_pkg}")
sys.path.insert(0, os.getcwd())
# ── dependency imports ──
try:
import html as html_mod
print(" ✅ html")
except Exception as e:
print(f" ❌ html: {e}")
try:
import requests
print(f" ✅ requests")
except Exception as e:
print(f" ❌ requests: {e}")
try:
import gradio as gr
print(f" ✅ gradio {gr.__version__}")
except Exception as e:
print(f" ❌ gradio: {e}")
sys.exit(1)
# ── marl import ──
try:
from marl import Marl, MarlConfig, MarlResult
print(" ✅ marl imported")
MARL_OK = True
except Exception as e:
print(f" ❌ marl import failed: {e}")
traceback.print_exc()
MARL_OK = False
# ── load index.html ──
INDEX_HTML = ""
for p in [os.path.join(APP_DIR, "index.html"), "index.html",
"/app/index.html", os.path.join(os.getcwd(), "index.html")]:
try:
with open(p, "r", encoding="utf-8") as f:
INDEX_HTML = f.read()
print(f" ✅ index.html from {p}")
break
except:
continue
if not INDEX_HTML:
print(" ⚠️ index.html not found, inline fallback")
INDEX_HTML = """<style>
.gradio-container{max-width:100%!important;width:100%!important;padding:0 24px!important}
header{display:none!important}
</style>
<div style="text-align:center;padding:28px 0 16px">
<h1 style="margin:0;font-size:2.2em;font-weight:800;color:#6366f1">MARL</h1>
<p style="color:#475569;font-size:13px;margin:6px 0">Model-Agnostic Runtime Middleware for LLMs</p>
<p style="color:#94a3b8;font-size:11px">Intelligence Amplification · Hallucination Reduction · Zero-Change Middleware</p>
<div style="display:flex;justify-content:center;gap:4px;margin:12px 0;font-size:9px;font-family:monospace">
<span style="background:rgba(13,148,136,.08);color:#0d9488;padding:4px 10px;border-radius:10px">🔍 S1 Hypothesis</span>
<span style="color:#ccc">→</span>
<span style="background:rgba(99,102,241,.08);color:#6366f1;padding:4px 10px;border-radius:10px">⚡ S2 Solver</span>
<span style="color:#ccc">→</span>
<span style="background:rgba(217,119,6,.08);color:#d97706;padding:4px 10px;border-radius:10px">🛡 S3 Auditor</span>
<span style="color:#ccc">→</span>
<span style="background:rgba(225,29,72,.08);color:#e11d48;padding:4px 10px;border-radius:10px">🎯 S4 Verifier</span>
<span style="color:#ccc">→</span>
<span style="background:rgba(139,92,246,.08);color:#8b5cf6;padding:4px 10px;border-radius:10px">🧠 S5 Refiner</span>
</div>
</div>"""
print("=" * 50)
# ════════════════════════════════════════════════════════════════
# Pipeline / Model config
# ════════════════════════════════════════════════════════════════
import re
import random
STAGES = {
"S1_Hypothesis": {"label":"S1 · Hypothesis Generator","tag":"Divergent Search","icon":"🔍","color":"#0d9488"},
"S2_Solver": {"label":"S2 · Primary Solver","tag":"Forward Pass","icon":"⚡","color":"#6366f1"},
"S3_Auditor": {"label":"S3 · Consistency Auditor","tag":"Validation Gate","icon":"🛡️","color":"#d97706"},
"S4_Verifier": {"label":"S4 · Adversarial Verifier","tag":"Error Detection","icon":"🎯","color":"#e11d48"},
"S5_Refiner": {"label":"S5 · Metacognitive Refiner","tag":"Self-Correction","icon":"🧠","color":"#8b5cf6"},
}
STAGE_ORDER = ["S1_Hypothesis","S2_Solver","S3_Auditor","S4_Verifier","S5_Refiner"]
# ════════════════════════════════════════════════════════════════
# Showcase Examples — displayed during pipeline wait
# ════════════════════════════════════════════════════════════════
SHOWCASE = [
{"cat":"🎯 Trap Question","q":"Is 0.9999... less than 1?",
"raw":"It approaches 1 infinitely but is less than 1.",
"tag":"S1 trap detection → S4 error confirmed",
"marl":"Mathematically 0.999... = 1 (proven via geometric series + algebraic proof)"},
{"cat":"🎯 Trap Question","q":"Can the Great Wall be seen from space?",
"raw":"It is the only man-made structure visible from space with the naked eye.",
"tag":"S4 detects 'NASA officially denied this'",
"marl":"At 5-8m wide, invisible even from low orbit. Urban legend originating from 18th-century British satire."},
{"cat":"🎯 Trap Question","q":"Was Napoleon short?",
"raw":"He was very short at about 157cm.",
"tag":"S4 detects 'French inch vs British inch confusion'",
"marl":"Actually ~169cm, taller than avg French male (165cm). Product of British propaganda."},
{"cat":"🔬 Precision Question","q":"Does water boil at 100°C?",
"raw":"Yes, water boils at 100°C.",
"tag":"S3 detects 'atmospheric pressure not specified'",
"marl":"100°C at 1 atm. On Mt. Everest summit, water boils at ~70°C."},
{"cat":"💀 Overconfidence","q":"Is Vitamin C effective for preventing colds?",
"raw":"It strengthens immunity and effectively prevents colds. (85%)",
"tag":"S4 detects 'conflicts with Cochrane meta-analysis'",
"marl":"Minimal prevention for general population (8%↓). Only significant for high-intensity athletes (50%↓)."},
{"cat":"💀 Overconfidence","q":"Will quantum computing break all encryption?",
"raw":"When quantum computers become practical, all encryption will be broken.",
"tag":"S4 detects 'symmetric vs asymmetric key distinction missing'",
"marl":"RSA/ECC vulnerable, but AES-256 remains safe. NIST post-quantum standards already published."},
{"cat":"💀 Hard Question","q":"Is GPT-5 an AGI?",
"raw":"It surpasses humans on most benchmarks, so it is close to AGI.",
"tag":"S1 catches 'benchmark ≠ general intelligence' trap",
"marl":"Self-correction ability (ER=0.302) still low. 'Knowing a lot' and 'knowing what you don't know' are different dimensions."},
{"cat":"🧠 Emergent Question","q":"Write a grant proposal leveraging a sports star's IP",
"raw":"Just partner with the team and plan a branded product.",
"tag":"S4 detects 'IP not secured = project termination risk'",
"marl":"Plan A (license) + Plan C (alternative design) in parallel. Attach distributor LOI + demand survey evidence."},
{"cat":"🧠 Emergent Question","q":"Calculate TAM·SAM·SOM for my startup",
"raw":"TAM: $500B, SAM: $10B, SOM: $1M (no sources)",
"tag":"S4 detects 'TAM→SAM→SOM logical disconnection'",
"marl":"Cite IDC report + bottom-up calculation via segment×ARPU. Restructured for investor verifiability."},
{"cat":"🧠 Emergent Question","q":"What is the fastest sort in Python?",
"raw":"QuickSort is the fastest at O(n log n).",
"tag":"S3 detects 'diverges from Python built-in implementation'",
"marl":"sorted() uses TimSort (hybrid), empirically faster than pure QuickSort."},
{"cat":"🔬 Precision Question","q":"What is the height of the Eiffel Tower?",
"raw":"The Eiffel Tower is 324m tall.",
"tag":"S4 detects 'antenna included/excluded not specified'",
"marl":"Structure 300m + broadcast antenna 24m = 324m total. Distinction matters by context."},
{"cat":"🔬 Precision Question","q":"How many light-minutes from Earth to the Sun?",
"raw":"About 8 minutes.",
"tag":"S3 detects 'elliptical orbit variation range missing'",
"marl":"Average 8 min 20 sec. Ranges from 8:10 (perihelion) to 8:27 (aphelion)."},
]
MODELS = {
"OpenAI": {"env":"OPENAI_API_KEY","default":"gpt-5.4",
"list":["gpt-5.4","gpt-5.4-pro","gpt-5.2","gpt-4o","gpt-4o-mini"]},
"Anthropic": {"env":"ANTHROPIC_API_KEY","default":"claude-sonnet-4-6",
"list":["claude-opus-4-6","claude-sonnet-4-6","claude-haiku-4-5-20251001"]},
"Google Gemini": {"env":"GOOGLE_API_KEY","default":"gemini-2.5-pro",
"list":["gemini-2.5-pro","gemini-2.5-flash"]},
"DeepSeek": {"env":"DEEPSEEK_API_KEY","default":"deepseek-chat",
"list":["deepseek-chat","deepseek-reasoner"]},
"xAI (Grok)": {"env":"XAI_API_KEY","default":"grok-3-beta",
"list":["grok-3-beta"]},
"Ollama (Local)": {"env":"","default":"llama3.1",
"list":["llama3.1","llama3.1:70b","qwen3.5:32b","deepseek-r1:8b","phi4:14b"]},
"Custom (OpenAI-compatible)": {"env":"","default":"custom","list":["custom"]},
}
BACKEND_LIST = list(MODELS.keys())
# ════════════════════════════════════════════════════════════════
# MD → HTML
# ════════════════════════════════════════════════════════════════
def _esc(t):
return html_mod.escape(str(t)) if t else ""
def _hex_rgb(h):
try: return f"{int(h[1:3],16)},{int(h[3:5],16)},{int(h[5:7],16)}"
except: return "99,102,241"
def _inline_fmt(text):
t = text
t = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', t)
t = re.sub(r'\*(.+?)\*', r'<em>\1</em>', t)
t = re.sub(r'(\d{1,3})%', r'<span style="font-family:monospace;font-weight:600;color:#6366f1">\1%</span>', t)
return t
def _md2html(text):
if not text: return ""
t = text
code_blocks = {}
def _save_code(m):
k = f"__CODE_{len(code_blocks)}__"
code = html_mod.escape(m.group(2))
code_blocks[k] = f'<pre style="background:rgba(15,23,42,.04);border:1px solid #e2e5f0;border-radius:8px;padding:12px;overflow-x:auto;font-family:monospace;font-size:11px;line-height:1.6;margin:8px 0"><code>{code}</code></pre>'
return k
t = re.sub(r'```(\w*)\n(.*?)```', _save_code, t, flags=re.DOTALL)
t = re.sub(r'`([^`]+)`', r'<code style="background:rgba(99,102,241,.08);padding:1px 5px;border-radius:4px;font-family:monospace;font-size:11px">\1</code>', t)
lines = t.split('\n')
result = []
in_list = False
for line in lines:
s = line.strip()
if s in code_blocks:
if in_list: result.append('</ul>'); in_list = False
result.append(code_blocks[s]); continue
hm = re.match(r'^(#{1,4})\s+(.+)$', s)
if hm:
if in_list: result.append('</ul>'); in_list = False
lvl = len(hm.group(1))
sz = {1:'18px',2:'15px',3:'13px',4:'12px'}[lvl]
result.append(f'<div style="font-size:{sz};font-weight:700;color:#1e293b;margin:14px 0 6px">{_inline_fmt(html_mod.escape(hm.group(2)))}</div>'); continue
if re.match(r'^[-*_]{3,}\s*$', s):
if in_list: result.append('</ul>'); in_list = False
result.append('<hr style="border:none;border-top:1px solid #e2e5f0;margin:12px 0">'); continue
lm = re.match(r'^[-*+]\s+(.+)$', s)
if lm:
if not in_list: result.append('<ul style="margin:6px 0;padding-left:20px">'); in_list = True
result.append(f'<li style="margin:3px 0;line-height:1.7">{_inline_fmt(html_mod.escape(lm.group(1)))}</li>'); continue
nm = re.match(r'^(\d+)[.)]\s+(.+)$', s)
if nm:
if in_list: result.append('</ul>'); in_list = False
result.append(f'<div style="margin:3px 0;padding-left:8px;line-height:1.7"><span style="color:#6366f1;font-weight:600;font-family:monospace;font-size:11px">{nm.group(1)}.</span> {_inline_fmt(html_mod.escape(nm.group(2)))}</div>'); continue
if in_list: result.append('</ul>'); in_list = False
if not s: result.append('<div style="height:6px"></div>'); continue
tm = re.match(r'^\[([A-Z_-]+(?:-\d+)?)\]\s*(.*)', s)
if tm:
tag = tm.group(1); rest = _inline_fmt(html_mod.escape(tm.group(2)))
tc = '#6366f1'
for px, cl in {'BACKTRACK':'#d97706','FIX':'#e11d48','APPLIED':'#16a34a','TRAP':'#e11d48','HALLUCINATION':'#e11d48','NO-FIXES':'#16a34a'}.items():
if tag.startswith(px): tc = cl; break
result.append(f'<div style="margin:6px 0;padding:6px 10px;background:rgba({_hex_rgb(tc)},.06);border-left:3px solid {tc};border-radius:0 6px 6px 0;font-size:12px;line-height:1.7"><span style="font-family:monospace;font-weight:700;color:{tc};font-size:11px">[{html_mod.escape(tag)}]</span> {rest}</div>'); continue
result.append(f'<p style="margin:4px 0;line-height:1.8">{_inline_fmt(html_mod.escape(s))}</p>')
if in_list: result.append('</ul>')
return '\n'.join(result)
# ════════════════════════════════════════════════════════════════
# Answer Cleaner — strip system tags, confidence %, metadata
# ════════════════════════════════════════════════════════════════
def _clean_answer(text):
"""Strip reasoning artifacts from final answer for end-user display."""
if not text:
return ""
t = text
# Remove "--- Corrections ---" section and everything after
t = re.split(r'\n-{2,}\s*Corrections\s*-{2,}', t, maxsplit=1)[0]
lines = t.split('\n')
clean = []
for line in lines:
s = line.strip()
# Skip system tags: [FIX-n], [TRAP-CHECK], [HALLUCINATION], [APPLIED-n], [BACKTRACK-n], [NO-FIXES-NEEDED]
if re.match(r'^\[(?:FIX-\d+|TRAP-CHECK|HALLUCINATION|APPLIED-\d+|BACKTRACK-\d+|NO-FIXES-NEEDED)\]', s):
continue
# Skip stage labels: "S1 · Hypothesis Generator", "S2 · Primary Solver" etc.
if re.match(r'^S[1-5]\s*[·\-]', s):
continue
# Strip inline confidence: "(confidence: 85%)", "(90% confidence)", "Confidence: 85%"
line = re.sub(r'\(?\s*[Cc]onfidence[:\s]*\d{1,3}%\s*\)?', '', line)
line = re.sub(r'\(?\s*\d{1,3}%\s*confidence\s*\)?', '', line)
# Strip standalone confidence lines: "Confidence: 85%" or "**Confidence:** 90%"
if re.match(r'^\s*\*{0,2}[Cc]onfidence\*{0,2}\s*[:]\s*\d{1,3}%', line):
continue
# Strip "## Confidence Adjustments" section headers
if re.match(r'^#{1,4}\s*(?:Confidence|Top-\d+\s+Uncertaint)', s):
continue
# Strip "★ MANDATORY SELF-CHECK" and similar framework directives
if re.match(r'^★', s):
continue
clean.append(line)
# Clean up excess blank lines
result = '\n'.join(clean)
result = re.sub(r'\n{3,}', '\n\n', result).strip()
return result
# ════════════════════════════════════════════════════════════════
# HTML Renderers
# ════════════════════════════════════════════════════════════════
def _stage_html(name, content):
s = STAGES.get(name, {})
c = s.get("color","#6366f1")
body = _md2html(content[:3000] if content else "(no output)")
return f'<div style="border-left:3px solid {c};background:rgba({_hex_rgb(c)},.04);border-radius:0 10px 10px 0;padding:14px 16px;margin:8px 0"><div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span style="font-size:1.1em">{s.get("icon","")}</span><span style="font-family:monospace;font-weight:700;font-size:12px;color:{c}">{s.get("label",name)}</span><span style="font-size:9px;color:#94a3b8;font-family:monospace;background:rgba(0,0,0,.04);padding:2px 8px;border-radius:10px">{s.get("tag","")}</span></div><div style="font-size:12px;line-height:1.7;color:#334155">{body}</div></div>'
def _result_html(content, is_marl=False):
"""Render Raw LLM result (no cleaning needed)."""
if not content:
return '<div style="color:#94a3b8;padding:40px;text-align:center;font-size:12px">Waiting...</div>'
badge = "MARL-Enhanced" if is_marl else "Raw LLM"
bc = "#6366f1" if is_marl else "#64748b"
bg = "rgba(99,102,241,.06)" if is_marl else "#f5f6fa"
body = _md2html(content)
return f'<div style="padding:16px"><div style="margin-bottom:12px"><span style="font-family:monospace;font-size:9px;font-weight:700;background:{bg};color:{bc};padding:3px 10px;border-radius:10px;text-transform:uppercase;letter-spacing:1px">{badge}</span></div><div style="font-size:13px;line-height:1.8;color:#1e293b;word-break:break-word">{body}</div></div>'
def _marl_result_html(raw_answer, trace_dict):
"""Render MARL result: clean answer + embedded reasoning toggle with S1~S5 trace."""
clean = _clean_answer(raw_answer)
if not clean:
return '<div style="color:#94a3b8;padding:40px;text-align:center;font-size:12px">Waiting...</div>'
body = _md2html(clean)
# Build S1~S5 trace HTML for toggle
trace_parts = ""
if trace_dict:
trace_parts = ''.join(_stage_html(n, trace_dict[n]) for n in STAGE_ORDER if n in trace_dict)
toggle_html = ""
if trace_parts:
toggle_html = f'''
<details style="margin-top:16px;border:1px solid #e2e5f0;border-radius:10px;overflow:hidden">
<summary style="cursor:pointer;padding:10px 16px;background:rgba(99,102,241,.03);font-family:monospace;font-size:11px;font-weight:600;color:#6366f1;user-select:none;display:flex;align-items:center;gap:6px">
🔍 View Reasoning Process — S1→S2→S3→S4→S5
</summary>
<div style="padding:12px 16px;border-top:1px solid #e2e5f0;background:#fafbfc">
{trace_parts}
</div>
</details>'''
return f'''<div style="padding:16px">
<div style="margin-bottom:12px">
<span style="font-family:monospace;font-size:9px;font-weight:700;background:rgba(99,102,241,.06);color:#6366f1;padding:3px 10px;border-radius:10px;text-transform:uppercase;letter-spacing:1px">MARL-Enhanced</span>
</div>
<div style="font-size:13px;line-height:1.8;color:#1e293b;word-break:break-word">{body}</div>
{toggle_html}
</div>'''
def _trace_html(trace_dict):
if not trace_dict:
return '<div style="color:#94a3b8;padding:30px;text-align:center">Run MARL to see pipeline trace</div>'
return ''.join(_stage_html(n, trace_dict[n]) for n in STAGE_ORDER if n in trace_dict)
def _pipeline_anim(phase="marl", msg=""):
"""Animated pipeline + rotating showcase Before/After cards."""
stages = [
("S1", "Hypothesis", "🔍", "#0d9488"),
("S2", "Solver", "⚡", "#6366f1"),
("S3", "Auditor", "🛡️", "#d97706"),
("S4", "Verifier", "🎯", "#e11d48"),
("S5", "Synthesizer", "🧠", "#8b5cf6"),
]
if phase == "raw":
return f'''<div style="padding:30px 20px;text-align:center">
<div style="font-size:13px;color:#475569;margin-bottom:16px">{_esc(msg) if msg else "Generating Raw LLM response..."}</div>
<div style="display:inline-block;width:200px;height:4px;background:#e2e5f0;border-radius:4px;overflow:hidden">
<div style="width:40%;height:100%;background:linear-gradient(90deg,#6366f1,#0d9488);border-radius:4px;animation:rawPulse 1.5s ease-in-out infinite"></div>
</div>
<style>@keyframes rawPulse{{0%,100%{{width:30%}}50%{{width:70%}}}}</style>
</div>'''
# ── Stage pills with sequential glow ──
pills = []
arrows = []
ns = len(stages)
for i, (sid, name, icon, color) in enumerate(stages):
delay = i * 1.8
rgb = _hex_rgb(color)
pills.append(f'''<div style="display:flex;flex-direction:column;align-items:center;gap:4px;animation:stageGlow {ns*1.8}s ease-in-out infinite;animation-delay:{delay}s;opacity:0.35">
<div style="width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:18px;background:rgba({rgb},.1);border:2px solid rgba({rgb},.2)">{icon}</div>
<div style="font-family:monospace;font-size:10px;font-weight:700;color:{color}">{sid}</div>
<div style="font-size:9px;color:#94a3b8;white-space:nowrap">{name}</div>
</div>''')
if i < ns - 1:
arrows.append(f'<div style="color:#cbd5e1;font-size:14px;margin-top:-16px;animation:arrowPulse {ns*1.8}s ease-in-out infinite;animation-delay:{delay+0.9}s;opacity:0.3">→</div>')
interleaved = []
for i, pill in enumerate(pills):
interleaved.append(pill)
if i < len(arrows):
interleaved.append(arrows[i])
stage_html = "\n".join(interleaved)
sub = _esc(msg) if msg else "Thinking, questioning, correcting, rewriting..."
# ── Showcase cards — CSS-only rotation ──
shuffled = list(SHOWCASE)
random.shuffle(shuffled)
nc = min(len(shuffled), 8) # show up to 8 cards
dur_each = 5 # seconds per card
total_dur = nc * dur_each
cards_html = ""
card_kf = ""
for idx in range(nc):
ex = shuffled[idx]
pct_start = (idx / nc) * 100
pct_show = pct_start + 2
pct_hide = ((idx + 1) / nc) * 100 - 2
pct_end = ((idx + 1) / nc) * 100
cards_html += f'''<div class="sc-card sc-card-{idx}" style="position:absolute;inset:0;opacity:0;animation:sc{idx} {total_dur}s ease-in-out infinite">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:10px">
<span style="font-size:9px;font-weight:700;color:#6366f1;background:rgba(99,102,241,.08);padding:2px 8px;border-radius:6px;font-family:monospace">{_esc(ex['cat'])}</span>
</div>
<div style="font-size:13px;font-weight:700;color:#1e293b;margin-bottom:12px;line-height:1.4">"{_esc(ex['q'])}"</div>
<div style="display:flex;gap:10px">
<div style="flex:1;padding:10px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:8px">
<div style="font-size:9px;font-weight:700;color:#e11d48;margin-bottom:4px;font-family:monospace">❌ Non-MARL</div>
<div style="font-size:11px;color:#7f1d1d;line-height:1.5">{_esc(ex['raw'])}</div>
</div>
<div style="flex:1;padding:10px 12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px">
<div style="font-size:9px;font-weight:700;color:#16a34a;margin-bottom:4px;font-family:monospace">✅ MARL</div>
<div style="font-size:11px;color:#14532d;line-height:1.5">{_esc(ex['marl'])}</div>
</div>
</div>
<div style="margin-top:8px;font-size:9px;color:#6366f1;font-family:monospace;font-weight:600">🔍 {_esc(ex['tag'])}</div>
</div>\n'''
card_kf += f"@keyframes sc{idx}{{" \
f"0%,{pct_start:.1f}%{{opacity:0;transform:translateY(8px)}}" \
f"{pct_show:.1f}%{{opacity:1;transform:translateY(0)}}" \
f"{pct_hide:.1f}%{{opacity:1;transform:translateY(0)}}" \
f"{pct_end:.1f}%,100%{{opacity:0;transform:translateY(-8px)}}}}\n"
return f'''<div style="padding:24px 16px;text-align:center">
<div style="display:flex;align-items:center;justify-content:center;gap:10px;flex-wrap:nowrap;margin-bottom:16px">
{stage_html}
</div>
<div style="font-size:12px;color:#475569;margin-bottom:6px">{sub}</div>
<div style="display:flex;gap:4px;justify-content:center;margin-bottom:20px">
<div style="width:6px;height:6px;border-radius:50%;background:#6366f1;animation:dotBounce 1.4s ease-in-out infinite"></div>
<div style="width:6px;height:6px;border-radius:50%;background:#0d9488;animation:dotBounce 1.4s ease-in-out .2s infinite"></div>
<div style="width:6px;height:6px;border-radius:50%;background:#8b5cf6;animation:dotBounce 1.4s ease-in-out .4s infinite"></div>
</div>
<div style="text-align:left;max-width:560px;margin:0 auto">
<div style="font-size:9px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:1.5px;margin-bottom:10px;font-family:monospace">💡 Real cases caught by MARL</div>
<div style="position:relative;min-height:180px">
{cards_html}
</div>
</div>
<style>
@keyframes stageGlow{{
0%,100%{{opacity:0.3;transform:scale(1)}}
{100/ns:.0f}%{{opacity:1;transform:scale(1.1)}}
{200/ns:.0f}%{{opacity:0.3;transform:scale(1)}}
}}
@keyframes arrowPulse{{
0%,100%{{opacity:0.2;color:#cbd5e1}}
{100/ns:.0f}%{{opacity:1;color:#6366f1}}
{200/ns:.0f}%{{opacity:0.2;color:#cbd5e1}}
}}
@keyframes dotBounce{{
0%,100%{{transform:translateY(0)}}
50%{{transform:translateY(-6px)}}
}}
{card_kf}
</style>
</div>'''
def _status(state, msg, model, color):
dot = "●" if state == "Running" else "✓"
return f'<div style="display:flex;align-items:center;gap:10px;padding:10px 16px;background:rgba({_hex_rgb(color)},.06);border:1px solid rgba({_hex_rgb(color)},.15);border-radius:10px;font-family:monospace;font-size:11px"><span style="color:{color};font-weight:700">{dot} {state}</span><span style="color:#475569">{_esc(model)}</span><span style="color:#94a3b8">·</span><span style="color:#64748b">{_esc(msg)}</span></div>'
# ════════════════════════════════════════════════════════════════
# Build Marl
# ════════════════════════════════════════════════════════════════
def _on_backend(backend):
reg = MODELS.get(backend, {})
ml, dv, ek = reg.get("list",[]), reg.get("default",""), reg.get("env","")
return gr.Dropdown(choices=ml, value=dv), gr.Textbox(placeholder=f"ENV: {ek}" if ek else "API Key")
def _build(backend, api_key, model, base_url):
if not MARL_OK:
return None, "❌ marl package failed to load. Check Space logs."
cfg = MarlConfig(include_trace=True, return_final_only=True)
reg = MODELS.get(backend, {})
model = model or reg.get("default","")
ek = reg.get("env","")
k = api_key or (os.getenv(ek,"") if ek else "")
try:
if backend == "OpenAI":
if not k: return None, "❌ OPENAI_API_KEY required"
return Marl.from_openai(k, model, cfg), "✅"
elif backend == "Anthropic":
if not k: return None, "❌ ANTHROPIC_API_KEY required"
return Marl.from_anthropic(k, model, cfg), "✅"
elif backend == "Google Gemini":
k = k or os.getenv("GEMINI_API_KEY","")
if not k: return None, "❌ GOOGLE_API_KEY required"
return Marl.from_openai_compatible("https://generativelanguage.googleapis.com/v1beta/openai", k, model, cfg), "✅"
elif backend == "DeepSeek":
if not k: return None, "❌ DEEPSEEK_API_KEY required"
return Marl.from_openai_compatible("https://api.deepseek.com/v1", k, model, cfg), "✅"
elif backend == "xAI (Grok)":
if not k: return None, "❌ XAI_API_KEY required"
return Marl.from_openai_compatible("https://api.x.ai/v1", k, model, cfg), "✅"
elif backend == "Ollama (Local)":
return Marl.from_ollama(model, base_url or "http://localhost:11434", cfg), "✅"
elif backend == "Custom (OpenAI-compatible)":
if not base_url: return None, "❌ Base URL required"
return Marl.from_openai_compatible(base_url, api_key or "", model or "default", cfg), "✅"
except Exception as e:
return None, f"❌ Build error: {e}"
return None, "❌ Unsupported"
# ════════════════════════════════════════════════════════════════
# A/B Test (Streaming)
# ════════════════════════════════════════════════════════════════
def run_ab_test(prompt, backend, api_key, model, base_url, budget, mode_sel, etype_sel):
if not prompt.strip():
yield ('<div style="color:#e11d48;padding:12px">❌ Enter a prompt</div>',"","","")
return
ml, st = _build(backend, api_key, model, base_url)
if not ml:
yield (f'<div style="color:#e11d48;padding:12px">{_esc(st)}</div>',"","","")
return
ml.config.budget_scale = float(budget)
# Set mode
_MODE_MAP = {"🔬 Insight": "insight", "🎨 Emergence": "emergence"}
_ETYPE_MAP = {"🔧 Invent": "invent", "✨ Create": "create", "🍳 Recipe": "recipe", "💊 Pharma": "pharma", "🧬 Genomics": "genomics", "🧪 Chemistry": "chemistry", "🌍 Ecology": "ecology", "⚖️ Law": "law", "📄 Document": "document"}
ml.config.mode = _MODE_MAP.get(mode_sel, "insight")
ml.config.emergence_type = _ETYPE_MAP.get(etype_sel, "invent")
mode_label = f"{mode_sel}" + (f" · {etype_sel}" if "Emergence" in mode_sel else "")
# Show both animations simultaneously
yield (_status("Running",f"{mode_label} · Running Raw LLM + MARL in parallel...",model,"#6366f1"),
_pipeline_anim("raw", "Generating Raw LLM response..."),
_pipeline_anim("marl", "Running MARL pipeline..."),
"")
# ── Parallel execution ──
from concurrent.futures import ThreadPoolExecutor
t0 = time.time()
with ThreadPoolExecutor(max_workers=2) as pool:
future_raw = pool.submit(ml.call_fn, prompt, "Answer thoroughly.", 4096, 0.6)
future_marl = pool.submit(ml.run, prompt)
raw = future_raw.result()
r = future_marl.result()
t_total = time.time() - t0
yield (_status("Complete",f"Parallel complete {t_total:.1f}s · {len(r.fixes)} corrections",model,"#16a34a"),
_result_html(raw,False), _marl_result_html(r.answer, r.trace), _trace_html(r.trace))
def run_marl_only(prompt, backend, api_key, model, base_url, budget, mode_sel, etype_sel):
if not prompt.strip():
yield ('<div style="color:#e11d48;padding:12px">❌ Enter a prompt</div>',"","","")
return
ml, st = _build(backend, api_key, model, base_url)
if not ml:
yield (f'<div style="color:#e11d48;padding:12px">{_esc(st)}</div>',"","","")
return
ml.config.budget_scale = float(budget)
_MODE_MAP = {"🔬 Insight": "insight", "🎨 Emergence": "emergence"}
_ETYPE_MAP = {"🔧 Invent": "invent", "✨ Create": "create", "🍳 Recipe": "recipe", "💊 Pharma": "pharma", "🧬 Genomics": "genomics", "🧪 Chemistry": "chemistry", "🌍 Ecology": "ecology", "⚖️ Law": "law", "📄 Document": "document"}
ml.config.mode = _MODE_MAP.get(mode_sel, "insight")
ml.config.emergence_type = _ETYPE_MAP.get(etype_sel, "invent")
mode_label = f"{mode_sel}" + (f" · {etype_sel}" if "Emergence" in mode_sel else "")
yield (_status("Running",f"{mode_label} · MARL pipeline...",model,"#6366f1"),
"",_pipeline_anim("marl", "Running MARL pipeline..."),"")
t0=time.time(); r=ml.run(prompt); t_marl=time.time()-t0
yield (_status("Complete",f"MARL {t_marl:.1f}s · {len(r.fixes)} corrections",model,"#16a34a"),
"", _marl_result_html(r.answer, r.trace), _trace_html(r.trace))
# ════════════════════════════════════════════════════════════════
# Gradio App
# ════════════════════════════════════════════════════════════════
def create_app():
init_m = MODELS["OpenAI"]["list"]
with gr.Blocks(title="MARL — Model-Agnostic Runtime Middleware") as app:
gr.HTML(INDEX_HTML)
with gr.Tabs():
with gr.Tab("⚡ Playground"):
with gr.Row():
backend = gr.Dropdown(label="Backend", choices=BACKEND_LIST, value="OpenAI", scale=2)
api_key = gr.Textbox(label="API Key", type="password", placeholder="Enter your API key (required)",
value=os.getenv("OPENAI_API_KEY",""), scale=3)
with gr.Row():
model = gr.Dropdown(label="Model", choices=init_m, value="gpt-5.4",
allow_custom_value=True, scale=3)
base_url = gr.Textbox(label="Base URL (Custom/Ollama)",
placeholder="http://localhost:11434", scale=2)
budget = gr.Slider(0.3, 3.0, value=1.0, step=0.1, label="Budget Scale", scale=1)
with gr.Row():
mode = gr.Radio(["🔬 Insight", "🎨 Emergence"],
value="🔬 Insight", label="Mode", scale=2)
etype = gr.Radio(["🔧 Invent", "✨ Create", "🍳 Recipe", "💊 Pharma", "🧬 Genomics", "🧪 Chemistry", "🌍 Ecology", "⚖️ Law", "📄 Document"],
value="🔧 Invent", label="Emergence Engine", scale=2, visible=False)
def _on_mode(m):
return gr.Radio(visible="Emergence" in m)
mode.change(fn=_on_mode, inputs=[mode], outputs=[etype])
backend.change(fn=_on_backend, inputs=[backend], outputs=[model, api_key])
prompt = gr.Textbox(label="Prompt", placeholder="Enter your question or task...", lines=3)
EXAMPLES = [
("🔬", "Is 0.9999... less than 1? Prove your answer with two different mathematical approaches.",
"🔬 Insight", "🔧 Invent"),
("🔬", "A startup claims their AI detects cancer with 99.9% accuracy from a selfie. As a medical advisor, evaluate this claim — what critical information is missing?",
"🔬 Insight", "🔧 Invent"),
("🔧", "Invent a device that allows dementia patients to live safely at home alone. Fuse sensors, AI, and UX — under $50/month. Identify the top 3 failure modes.",
"🎨 Emergence", "🔧 Invent"),
("🔧", "Design a building material that detects its own cracks and self-heals. What existing material science makes this feasible vs. science fiction?",
"🎨 Emergence", "🔧 Invent"),
("✨", "Write a single movie logline that would make both A24 and Marvel want to bid. Explain why the concept bridges arthouse and blockbuster.",
"🎨 Emergence", "✨ Create"),
("✨", "A museum wants to create an exhibit where visitors experience 'the feeling of forgetting.' Design the concept — what do they see, hear, and feel?",
"🎨 Emergence", "✨ Create"),
("🍳", "Can you truly replicate Korean beef bulgogi taste using only plant-based ingredients? Analyze the Maillard reaction chemistry and propose the closest possible recipe.",
"🎨 Emergence", "🍳 Recipe"),
("🍳", "A Michelin chef claims instant ramen can never be fine dining. Prove them wrong — design one dish that could change their mind, with the chemistry behind each choice.",
"🎨 Emergence", "🍳 Recipe"),
("📄", "Our company's turnover rate hit 30% this year. The CEO blames salary, but HR says it's culture. Analyze both hypotheses with data-driven counter-arguments.",
"🎨 Emergence", "📄 Document"),
("📄", "Write a policy brief arguing BOTH sides of whether governments should ban deepfake technology. Which side has the stronger evidence?",
"🎨 Emergence", "📄 Document"),
("💊", "Viagra was originally a heart drug. Identify ONE existing approved drug and build a rigorous case for repositioning it to treat Alzheimer's. Include mechanism, evidence gaps, and risks.",
"🎨 Emergence", "💊 Pharma"),
("💊", "A pharma company claims their new Alzheimer's drug reverses cognitive decline by 40%. What hidden assumptions in their clinical trial design should an FDA reviewer challenge?",
"🎨 Emergence", "💊 Pharma"),
("🧬", "BRCA-PARP synthetic lethality revolutionized cancer therapy. Propose ONE new synthetic lethality pair with biological rationale for why simultaneous inhibition would selectively kill cancer cells.",
"🎨 Emergence", "🧬 Genomics"),
("🧬", "A preprint claims gut microbiome directly causes Parkinson's disease. Evaluate the causal claim — what would a definitive study need to prove this beyond correlation?",
"🎨 Emergence", "🧬 Genomics"),
("🧪", "Is it physically possible to combine graphene-level strength with rubber-level flexibility in a single material? Analyze the trade-offs and propose the most feasible architecture.",
"🎨 Emergence", "🧪 Chemistry"),
("🧪", "A startup claims they can convert spent lithium batteries into solid-state battery materials at 90% efficiency. What are the thermodynamic limits they're likely ignoring?",
"🎨 Emergence", "🧪 Chemistry"),
("🌍", "An island nation is sinking due to climate change. They have $10M. Should they invest in sea walls, coral restoration, or relocation? Analyze the trade-offs with a 50-year horizon.",
"🎨 Emergence", "🌍 Ecology"),
("🌍", "Invasive lionfish are destroying Caribbean reefs. Can this threat be turned into a profitable industry? Analyze the ecological risks of commercializing an invasive species.",
"🎨 Emergence", "🌍 Ecology"),
("⚖️", "A self-driving car kills a pedestrian. Under EU law, the manufacturer is liable. Under US law, the software developer is. Under Korean law, it's unclear. Design a framework that resolves all three.",
"🎨 Emergence", "⚖️ Law"),
("⚖️", "An AI generates a novel that becomes a bestseller. The AI was trained on copyrighted books. Who owns the copyright? Analyze under common law vs. civil law and propose a new doctrine.",
"🎨 Emergence", "⚖️ Law"),
]
gr.HTML('<div style="font-size:10px;font-weight:700;color:#8b90b0;text-transform:uppercase;letter-spacing:1.5px;font-family:var(--mono);margin:12px 0 6px">💡 EXAMPLES — click to auto-fill prompt & mode</div>')
with gr.Row():
ex_btns = []
for i in range(5):
icon, text = EXAMPLES[i][0], EXAMPLES[i][1]
ex_btns.append(gr.Button(f"{icon} {text[:42]}...", size="sm", scale=1, min_width=60))
with gr.Row():
for i in range(5, 10):
icon, text = EXAMPLES[i][0], EXAMPLES[i][1]
ex_btns.append(gr.Button(f"{icon} {text[:42]}...", size="sm", scale=1, min_width=60))
with gr.Row():
for i in range(10, 15):
icon, text = EXAMPLES[i][0], EXAMPLES[i][1]
ex_btns.append(gr.Button(f"{icon} {text[:42]}...", size="sm", scale=1, min_width=60))
with gr.Row():
for i in range(15, 20):
icon, text = EXAMPLES[i][0], EXAMPLES[i][1]
ex_btns.append(gr.Button(f"{icon} {text[:42]}...", size="sm", scale=1, min_width=60))
for i, btn in enumerate(ex_btns):
_, ex_prompt, ex_mode, ex_etype = EXAMPLES[i]
is_emergence = "Emergence" in ex_mode
btn.click(fn=lambda p=ex_prompt, m=ex_mode, e=ex_etype, v=is_emergence: (p, m, gr.Radio(value=e, visible=v)),
outputs=[prompt, mode, etype])
with gr.Row():
ab_btn = gr.Button("⚡ A/B Test · Raw LLM vs MARL", variant="primary", size="lg", scale=3)
marl_btn = gr.Button("🧠 MARL Only", variant="secondary", size="lg", scale=2)
status = gr.HTML()
gr.HTML('<div style="display:flex;gap:12px;margin:12px 0"><div style="flex:1;text-align:center;font-family:JetBrains Mono,monospace;font-size:10px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:#64748b;padding:10px;background:#f5f6fa;border-radius:10px 10px 0 0;border:1px solid #e2e5f0;border-bottom:2px solid #e2e5f0">🤖 A · Raw LLM</div><div style="flex:1;text-align:center;font-family:JetBrains Mono,monospace;font-size:10px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:#6366f1;padding:10px;background:rgba(99,102,241,.04);border-radius:10px 10px 0 0;border:1px solid rgba(99,102,241,.12);border-bottom:2px solid #6366f1">🧠 B · MARL-Enhanced</div></div>')
with gr.Row():
raw_out = gr.HTML()
marl_out = gr.HTML()
with gr.Accordion("📊 Pipeline Trace — 5-Stage Agent Outputs", open=False):
trace_out = gr.HTML()
ins = [prompt, backend, api_key, model, base_url, budget, mode, etype]
outs = [status, raw_out, marl_out, trace_out]
ab_btn.click(fn=run_ab_test, inputs=ins, outputs=outs)
marl_btn.click(fn=run_marl_only, inputs=ins, outputs=outs)
with gr.Tab("📦 Integration Guide"):
gr.HTML('''<div style="font-family:Sora,sans-serif;max-width:920px;margin:0 auto;padding:20px;color:#0f172a;line-height:1.7">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:24px">
<div style="background:var(--surface,#fff);border:1px solid #e2e5f0;border-radius:16px;padding:20px;box-shadow:0 1px 3px rgba(15,23,42,.04)">
<div style="font-size:8px;font-family:JetBrains Mono,monospace;color:#6366f1;text-transform:uppercase;letter-spacing:2px;font-weight:700;margin-bottom:6px">Quick Start</div>
<pre style="margin:0;font-family:JetBrains Mono,monospace;font-size:14px;color:#6366f1;background:#f5f6fa;padding:10px;border-radius:10px;border:1px solid #e2e5f0"><code>pip install marl-middleware</code></pre>
<div style="margin-top:6px;font-size:9px;color:#94a3b8">Linux x86_64 / Python 3.12 · Other OS → Docker</div>
</div>
<div style="background:var(--surface,#fff);border:1px solid #e2e5f0;border-radius:16px;padding:20px;box-shadow:0 1px 3px rgba(15,23,42,.04)">
<div style="font-size:8px;font-family:JetBrains Mono,monospace;color:#0d9488;text-transform:uppercase;letter-spacing:2px;font-weight:700;margin-bottom:6px">Docker (All Platforms)</div>
<pre style="margin:0;font-family:JetBrains Mono,monospace;font-size:14px;color:#0d9488;background:#f5f6fa;padding:10px;border-radius:10px;border:1px solid #e2e5f0"><code>docker run -p 8080:8080 vidraft/marl</code></pre>
<div style="margin-top:6px;font-size:9px;color:#94a3b8">Mac · Windows · Linux — works everywhere</div>
</div>
</div>
<div style="background:var(--surface,#fff);border:1px solid #e2e5f0;border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(15,23,42,.04)">
<div style="font-size:10px;font-family:JetBrains Mono,monospace;font-weight:700;color:#6366f1;text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px">⚡ 1-LINE INTEGRATION</div>
<p style="color:#475569;font-size:11px;margin-bottom:10px">Add <b>one line</b> to any OpenAI-compatible app:</p>
<pre style="background:#f5f6fa;padding:14px;border-radius:10px;font-family:JetBrains Mono,monospace;font-size:11px;line-height:1.8;border:1px solid #e2e5f0;overflow-x:auto"><code><span style="color:#94a3b8"># Before</span>
client = OpenAI(api_key=<span style="color:#d97706">"sk-..."</span>)
<span style="color:#94a3b8"># After — just add base_url</span>
client = OpenAI(api_key=<span style="color:#d97706">"sk-..."</span>, <span style="color:#e11d48">base_url=</span><span style="color:#d97706">"http://localhost:8080/v1"</span>)</code></pre>
</div>
<div style="background:var(--surface,#fff);border:1px solid #e2e5f0;border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(15,23,42,.04)">
<div style="font-size:10px;font-family:JetBrains Mono,monospace;font-weight:700;color:#6366f1;text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px">🎨 9 EMERGENCE MODES</div>
<p style="color:#475569;font-size:11px;margin-bottom:10px">Append <code style="background:rgba(99,102,241,.06);padding:2px 6px;border-radius:4px;color:#6366f1;font-family:JetBrains Mono,monospace;font-size:10px">::mode</code> to any model name:</p>
<div style="overflow-x:auto">
<table style="width:100%;border-collapse:separate;border-spacing:0;font-size:11px;border-radius:10px;overflow:hidden;border:1px solid #e2e5f0">
<tr style="background:#f5f6fa"><th style="text-align:left;padding:8px 10px;font-family:JetBrains Mono,monospace;font-size:8px;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px">model</th><th style="text-align:left;padding:8px 10px;font-family:JetBrains Mono,monospace;font-size:8px;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px">Mode</th><th style="text-align:left;padding:8px 10px;font-family:JetBrains Mono,monospace;font-size:8px;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px">Seeds</th></tr>
<tr><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">gpt-5.2</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">🔬 Insight (default)</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">Fact-check · Strategy</td></tr>
<tr style="background:#fafbfe"><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">::invent</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">🔧 Invent</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">4,318 tech items</td></tr>
<tr><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">::create</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">✨ Create</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">493 seeds (11 categories)</td></tr>
<tr style="background:#fafbfe"><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">::recipe</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">🍳 Recipe</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">131 methods · textures</td></tr>
<tr><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">::pharma</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">💊 Pharma</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">172 targets · mechanisms</td></tr>
<tr style="background:#fafbfe"><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">::genomics</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">🧬 Genomics</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">104 genes · pathways</td></tr>
<tr><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">::chemistry</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">🧪 Chemistry</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">135 elements · properties</td></tr>
<tr style="background:#fafbfe"><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">::ecology</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">🌍 Ecology</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">105 species · ecosystems</td></tr>
<tr><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">::law</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">⚖️ Law</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">59 jurisdictions</td></tr>
<tr style="background:#fafbfe"><td style="padding:7px 10px;border-top:1px solid #e2e5f0"><code style="color:#6366f1;font-weight:600;font-family:JetBrains Mono,monospace;font-size:10px">::document</code></td><td style="padding:7px 10px;border-top:1px solid #e2e5f0">📄 Document</td><td style="padding:7px 10px;border-top:1px solid #e2e5f0;color:#94a3b8;font-size:10px">71 principles</td></tr>
</table>
</div>
<p style="color:#94a3b8;font-size:9px;margin-top:6px;font-family:JetBrains Mono,monospace">Replace gpt-5.2 with any model — claude-sonnet, deepseek-v3, llama3, etc.</p>
</div>
<div style="background:var(--surface,#fff);border:1px solid #e2e5f0;border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(15,23,42,.04)">
<div style="font-size:10px;font-family:JetBrains Mono,monospace;font-weight:700;color:#6366f1;text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px">🐙 PYTHON SDK</div>
<pre style="background:#f5f6fa;padding:14px;border-radius:10px;font-family:JetBrains Mono,monospace;font-size:11px;line-height:1.8;border:1px solid #e2e5f0;overflow-x:auto"><code><span style="color:#94a3b8"># OpenAI</span>
<span style="color:#6366f1">from</span> marl <span style="color:#6366f1">import</span> Marl, MarlConfig
ml = Marl.from_openai(<span style="color:#d97706">"sk-..."</span>, config=MarlConfig(
mode=<span style="color:#d97706">"emergence"</span>, emergence_type=<span style="color:#d97706">"create"</span>
))
result = ml.run(<span style="color:#d97706">"Generate 10 movie loglines"</span>)
<span style="color:#94a3b8"># Anthropic</span>
ml = Marl.from_anthropic(<span style="color:#d97706">"sk-ant-..."</span>)
<span style="color:#94a3b8"># Ollama (local)</span>
ml = Marl.from_ollama(<span style="color:#d97706">"llama3.1"</span>)
<span style="color:#94a3b8"># Any OpenAI-compatible</span>
ml = Marl.from_openai(<span style="color:#d97706">"sk-..."</span>, <span style="color:#d97706">"gpt-5.4"</span>)</code></pre>
</div>
<div style="background:var(--surface,#fff);border:1px solid #e2e5f0;border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(15,23,42,.04)">
<div style="font-size:10px;font-family:JetBrains Mono,monospace;font-weight:700;color:#6366f1;text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px">🦞 OPENCLAW INTEGRATION</div>
<div style="display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:start">
<div style="background:linear-gradient(135deg,#6366f1,#4f46e5);color:#fff;width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:12px;font-family:JetBrains Mono,monospace;flex-shrink:0">1</div>
<div><b style="font-size:11px;color:#0f172a">Install MARL</b><pre style="background:#f5f6fa;padding:8px 12px;border-radius:8px;font-family:JetBrains Mono,monospace;font-size:11px;margin:6px 0;border:1px solid #e2e5f0"><code style="color:#0d9488">docker run -p 8080:8080 vidraft/marl</code></pre></div>
<div style="background:linear-gradient(135deg,#6366f1,#4f46e5);color:#fff;width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:12px;font-family:JetBrains Mono,monospace;flex-shrink:0">2</div>
<div><b style="font-size:11px;color:#0f172a">Set config.json</b><pre style="background:#f5f6fa;padding:8px 12px;border-radius:8px;font-family:JetBrains Mono,monospace;font-size:11px;margin:6px 0;border:1px solid #e2e5f0"><code>{ <span style="color:#6366f1">"llm"</span>: { <span style="color:#6366f1">"baseURL"</span>: <span style="color:#d97706">"http://localhost:8080/v1"</span>, <span style="color:#6366f1">"model"</span>: <span style="color:#d97706">"gpt-5.2::create"</span> } }</code></pre></div>
<div style="background:linear-gradient(135deg,#6366f1,#4f46e5);color:#fff;width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:12px;font-family:JetBrains Mono,monospace;flex-shrink:0">3</div>
<div><b style="font-size:11px;color:#0f172a">Chat naturally</b><div style="color:#475569;font-size:10px;margin-top:4px">"Analyze this with MARL" · "Use MARL pharma mode for drug repositioning"</div></div>
</div>
</div>
<div style="background:var(--surface,#fff);border:1px solid #e2e5f0;border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(15,23,42,.04)">
<div style="font-size:10px;font-family:JetBrains Mono,monospace;font-weight:700;color:#6366f1;text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px">🏗️ ARCHITECTURE</div>
<pre style="background:#f5f6fa;padding:14px;border-radius:10px;font-family:JetBrains Mono,monospace;font-size:10px;line-height:1.7;border:1px solid #e2e5f0;overflow-x:auto"><code><span style="color:#0d9488">┌─ Your App ─────────────────────────────────────────┐</span>
│ OpenClaw / Cursor / Custom App / Any LLM Client │
│ client = OpenAI(<span style="color:#e11d48">base_url=</span><span style="color:#d97706">"http://MARL:8080/v1"</span>) │
<span style="color:#0d9488">└────────────────────┬───────────────────────────────┘</span>
│ HTTP (OpenAI API format)
<span style="color:#6366f1">┌─ MARL Middleware ──────────────────────────────────┐</span>
│ S1 Hypothesis → S2 Solver → S3 Auditor │
│ → S4 Verifier → S5 Synthesizer │
│ <span style="color:#d97706">9 Emergence Engines · 5,538 Seeds</span> │
│ <span style="color:#16a34a">FINAL Bench: MA=0.694 vs ER=0.302 (70%+ ↑)</span> │
<span style="color:#6366f1">└────────────────────┬───────────────────────────────┘</span>
│ API call (×5)
<span style="color:#94a3b8">┌─ Any LLM ──────────────────────────────────────────┐</span>
│ OpenAI · Anthropic · Gemini · DeepSeek · Ollama │
<span style="color:#94a3b8">└────────────────────────────────────────────────────┘</span></code></pre>
</div>
<div style="background:var(--surface,#fff);border:1px solid #e2e5f0;border-radius:16px;padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(15,23,42,.04)">
<div style="font-size:10px;font-family:JetBrains Mono,monospace;font-weight:700;color:#6366f1;text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px">📡 SUPPORTED BACKENDS</div>
<div style="overflow-x:auto">
<table style="width:100%;border-collapse:separate;border-spacing:0;font-size:11px;border-radius:10px;overflow:hidden;border:1px solid #e2e5f0">
<tr style="background:#f5f6fa"><th style="text-align:left;padding:8px 10px;font-family:JetBrains Mono,monospace;font-size:8px;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px">Backend</th><th style="text-align:left;padding:8px 10px;font-family:JetBrains Mono,monospace;font-size:8px;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px">Models</th></tr>
<tr style="background:rgba(99,102,241,.06)"><td style="padding:8px 10px;border-top:1px solid #e2e5f0;font-weight:700;color:#6366f1">⭐ OpenAI (Default)</td><td style="padding:8px 10px;border-top:1px solid #e2e5f0;font-weight:600">GPT-5.4, GPT-5.4-pro, GPT-5.2, GPT-4o</td></tr>
<tr style="background:#fafbfe"><td style="padding:8px 10px;border-top:1px solid #e2e5f0">Anthropic</td><td style="padding:8px 10px;border-top:1px solid #e2e5f0">Claude Opus 4.6, Sonnet 4.6, Haiku 4.5</td></tr>
<tr><td style="padding:8px 10px;border-top:1px solid #e2e5f0">Google Gemini</td><td style="padding:8px 10px;border-top:1px solid #e2e5f0">Gemini 2.5 Pro / Flash</td></tr>
<tr style="background:#fafbfe"><td style="padding:8px 10px;border-top:1px solid #e2e5f0">DeepSeek</td><td style="padding:8px 10px;border-top:1px solid #e2e5f0">V3 / R1</td></tr>
<tr><td style="padding:8px 10px;border-top:1px solid #e2e5f0">xAI</td><td style="padding:8px 10px;border-top:1px solid #e2e5f0">Grok-3</td></tr>
<tr style="background:#fafbfe"><td style="padding:8px 10px;border-top:1px solid #e2e5f0">Ollama</td><td style="padding:8px 10px;border-top:1px solid #e2e5f0">Llama, Mistral, Phi, Qwen</td></tr>
<tr><td style="padding:8px 10px;border-top:1px solid #e2e5f0">Custom</td><td style="padding:8px 10px;border-top:1px solid #e2e5f0">Any OpenAI-compatible endpoint</td></tr>
</table>
</div>
</div>
<div style="text-align:center;padding:16px;background:#f5f6fa;border:1px solid #e2e5f0;border-radius:10px">
<p style="font-size:11px;color:#6366f1;margin:0;font-weight:700;font-family:JetBrains Mono,monospace">MARL · Model-Agnostic Runtime Middleware</p>
<p style="font-size:9px;color:#94a3b8;margin:4px 0 0;font-family:JetBrains Mono,monospace">pip install marl-middleware · Apache 2.0 · <b style="color:#475569">VIDRAFT.net</b></p>
</div>
</div>''')
gr.HTML('<div style="text-align:center;padding:20px 0 8px;border-top:1px solid #e2e5f0;margin-top:16px"><p style="font-family:JetBrains Mono,monospace;font-size:8px;color:#94a3b8;letter-spacing:1px"><b style="color:#6366f1">MARL</b> · Model-Agnostic Runtime Middleware · S1→S2→S3→S4→S5 · Apache 2.0 · <b style="color:#475569">VIDRAFT.net</b></p></div>')
return app
print(" Creating Gradio app...")
try:
app = create_app()
print(" ✅ App created successfully")
except Exception as e:
print(f" ❌ App creation failed: {e}")
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
print(" 🚀 Launching on 0.0.0.0:7860 ...")
try:
app.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)
except TypeError:
# ssr_mode not supported in this gradio version
app.launch(server_name="0.0.0.0", server_port=7860)