JakeFake222 commited on
Commit
0f7edc0
Β·
verified Β·
1 Parent(s): 9e74b5e

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +556 -9
app.py CHANGED
@@ -1,13 +1,560 @@
 
 
 
 
 
 
 
1
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- def greet(name):
4
- return f"Hello {name}!"
5
 
6
- with gr.Blocks() as demo:
7
- gr.Markdown("# Test App")
8
- name = gr.Textbox(label="Name")
9
- output = gr.Textbox(label="Output")
10
- btn = gr.Button("Greet")
11
- btn.click(greet, name, output)
 
 
 
 
 
 
 
 
12
 
13
- demo.launch()
 
 
1
+ """
2
+ MAKER Framework - Hugging Face Space
3
+ =====================================
4
+ Reliable AI Agent with Web Search & File Upload
5
+ Based on: https://arxiv.org/abs/2511.09030
6
+ """
7
+
8
  import gradio as gr
9
+ import asyncio
10
+ import json
11
+ import re
12
+ import base64
13
+ from collections import Counter
14
+ from dataclasses import dataclass, field
15
+ from typing import Any, Callable, Optional
16
+ from pathlib import Path
17
+
18
+ # ============================================================================
19
+ # MAKER Core (Embedded)
20
+ # ============================================================================
21
+
22
+ @dataclass
23
+ class VotingConfig:
24
+ k: int = 3
25
+ max_samples: int = 30
26
+ temperature_first: float = 0.0
27
+ temperature_rest: float = 0.1
28
+ parallel_samples: int = 3
29
+
30
+ @dataclass
31
+ class RedFlagConfig:
32
+ max_response_chars: int = 3000
33
+ min_response_length: int = 5
34
+ banned_patterns: list = field(default_factory=lambda: [r"I don't know", r"I cannot"])
35
+
36
+
37
+ class LLMClient:
38
+ """Universal LLM client."""
39
+
40
+ def __init__(self, provider: str, api_key: str, model: str = None):
41
+ self.provider = provider.lower()
42
+ self.api_key = api_key
43
+ self.model = model
44
+ self._client = None
45
+ self._setup_client()
46
+
47
+ def _setup_client(self):
48
+ if self.provider == "openai":
49
+ from openai import AsyncOpenAI
50
+ self._client = AsyncOpenAI(api_key=self.api_key)
51
+ self.model = self.model or "gpt-4o-mini"
52
+ elif self.provider == "anthropic":
53
+ from anthropic import AsyncAnthropic
54
+ self._client = AsyncAnthropic(api_key=self.api_key)
55
+ self.model = self.model or "claude-sonnet-4-20250514"
56
+ elif self.provider == "groq":
57
+ from openai import AsyncOpenAI
58
+ self._client = AsyncOpenAI(api_key=self.api_key, base_url="https://api.groq.com/openai/v1")
59
+ self.model = self.model or "llama-3.3-70b-versatile"
60
+ elif self.provider == "together":
61
+ from openai import AsyncOpenAI
62
+ self._client = AsyncOpenAI(api_key=self.api_key, base_url="https://api.together.xyz/v1")
63
+ self.model = self.model or "meta-llama/Llama-3.3-70B-Instruct-Turbo"
64
+ elif self.provider == "openrouter":
65
+ from openai import AsyncOpenAI
66
+ self._client = AsyncOpenAI(api_key=self.api_key, base_url="https://openrouter.ai/api/v1")
67
+ self.model = self.model or "openai/gpt-4o-mini"
68
+
69
+ async def generate(self, prompt: str, temperature: float = 0.0, max_tokens: int = 1000) -> str:
70
+ if self.provider == "anthropic":
71
+ r = await self._client.messages.create(
72
+ model=self.model, max_tokens=max_tokens,
73
+ messages=[{"role": "user", "content": prompt}]
74
+ )
75
+ return r.content[0].text
76
+ else:
77
+ r = await self._client.chat.completions.create(
78
+ model=self.model,
79
+ messages=[{"role": "user", "content": prompt}],
80
+ temperature=temperature, max_tokens=max_tokens
81
+ )
82
+ return r.choices[0].message.content
83
+
84
+
85
+ class WebSearch:
86
+ """Web search using DuckDuckGo (free)."""
87
+
88
+ @staticmethod
89
+ async def search(query: str, num_results: int = 5) -> list:
90
+ try:
91
+ from duckduckgo_search import DDGS
92
+ results = []
93
+ with DDGS() as ddgs:
94
+ for r in ddgs.text(query, max_results=num_results):
95
+ results.append({
96
+ "title": r.get("title", ""),
97
+ "url": r.get("href", ""),
98
+ "snippet": r.get("body", "")
99
+ })
100
+ return results
101
+ except Exception as e:
102
+ return [{"title": "Error", "url": "", "snippet": str(e)}]
103
+
104
+
105
+ class FileHandler:
106
+ """Handle file uploads."""
107
+
108
+ @staticmethod
109
+ async def load_file(file_path: str) -> dict:
110
+ path = Path(file_path)
111
+ ext = path.suffix.lower()
112
+
113
+ try:
114
+ if ext in {'.txt', '.md', '.json', '.py', '.js', '.html', '.css', '.csv'}:
115
+ content = path.read_text(encoding='utf-8', errors='replace')
116
+ return {"type": "text", "name": path.name, "content": content[:50000]}
117
+
118
+ elif ext == '.pdf':
119
+ try:
120
+ import pymupdf
121
+ doc = pymupdf.open(str(path))
122
+ text = "\n\n".join([page.get_text() for page in doc])
123
+ doc.close()
124
+ return {"type": "pdf", "name": path.name, "content": text[:50000]}
125
+ except ImportError:
126
+ return {"type": "error", "name": path.name, "content": "PDF requires: pip install pymupdf"}
127
+
128
+ elif ext == '.docx':
129
+ try:
130
+ from docx import Document
131
+ doc = Document(str(path))
132
+ text = "\n\n".join([p.text for p in doc.paragraphs])
133
+ return {"type": "docx", "name": path.name, "content": text[:50000]}
134
+ except ImportError:
135
+ return {"type": "error", "name": path.name, "content": "DOCX requires: pip install python-docx"}
136
+
137
+ elif ext in {'.png', '.jpg', '.jpeg', '.gif', '.webp'}:
138
+ content = path.read_bytes()
139
+ b64 = base64.b64encode(content).decode('utf-8')
140
+ return {"type": "image", "name": path.name, "base64": b64}
141
+
142
+ else:
143
+ content = path.read_text(encoding='utf-8', errors='replace')
144
+ return {"type": "text", "name": path.name, "content": content[:50000]}
145
+
146
+ except Exception as e:
147
+ return {"type": "error", "name": path.name, "content": str(e)}
148
+
149
+
150
+ class MAKERAgent:
151
+ """MAKER Framework Agent."""
152
+
153
+ def __init__(self, llm: LLMClient, voting: VotingConfig = None, red_flags: RedFlagConfig = None):
154
+ self.llm = llm
155
+ self.voting = voting or VotingConfig()
156
+ self.red_flags = red_flags or RedFlagConfig()
157
+ self.stats = {"samples": 0, "red_flags": 0, "tool_calls": 0}
158
+
159
+ def _check_red_flags(self, response: str) -> bool:
160
+ if len(response) > self.red_flags.max_response_chars:
161
+ return True
162
+ if len(response) < self.red_flags.min_response_length:
163
+ return True
164
+ for pattern in self.red_flags.banned_patterns:
165
+ if re.search(pattern, response, re.IGNORECASE):
166
+ return True
167
+ return False
168
+
169
+ def _parse_json(self, response: str) -> Optional[dict]:
170
+ response = re.sub(r'^```(?:json)?\s*', '', response.strip())
171
+ response = re.sub(r'\s*```$', '', response)
172
+ try:
173
+ result = json.loads(response)
174
+ return result if isinstance(result, dict) else None
175
+ except:
176
+ return None
177
+
178
+ def _serialize(self, result) -> str:
179
+ if isinstance(result, dict):
180
+ return json.dumps(result, sort_keys=True)
181
+ return str(result)
182
+
183
+ async def execute(self, prompt: str, expected_keys: list = None, use_tools: bool = False,
184
+ file_context: str = None, progress_callback: Callable = None) -> dict:
185
+
186
+ full_prompt = ""
187
+ if file_context:
188
+ full_prompt += f"CONTEXT FROM FILES:\n{file_context}\n\n"
189
+ full_prompt += prompt
190
+
191
+ if use_tools:
192
+ full_prompt += '\n\nTo search web: {"tool": "web_search", "query": "..."}'
193
+ full_prompt += "\n\nRespond with valid JSON only."
194
+
195
+ votes: Counter = Counter()
196
+ results_map = {}
197
+ samples, flagged = 0, 0
198
+ tool_results = []
199
+
200
+ if progress_callback:
201
+ progress_callback(0.1, "Getting first sample...")
202
+
203
+ response = await self.llm.generate(full_prompt, temperature=0.0)
204
+ samples += 1
205
+ self.stats["samples"] += 1
206
+
207
+ # Handle tool calls
208
+ if use_tools:
209
+ parsed = self._parse_json(response)
210
+ if parsed and parsed.get("tool") == "web_search":
211
+ query = parsed.get("query", "")
212
+ if progress_callback:
213
+ progress_callback(0.2, f"Searching: {query}...")
214
+
215
+ search_results = await WebSearch.search(query)
216
+ tool_results.append({"query": query, "results": search_results})
217
+ self.stats["tool_calls"] += 1
218
+
219
+ search_text = "\n".join([f"- {r['title']}: {r['snippet']}" for r in search_results[:5]])
220
+ full_prompt += f"\n\nSEARCH RESULTS:\n{search_text}\n\nNow provide final JSON answer."
221
+ response = await self.llm.generate(full_prompt, temperature=0.0)
222
+ samples += 1
223
+
224
+ # Parse response
225
+ if self._check_red_flags(response):
226
+ flagged += 1
227
+ self.stats["red_flags"] += 1
228
+ else:
229
+ parsed = self._parse_json(response)
230
+ if parsed and (not expected_keys or all(k in parsed for k in expected_keys)):
231
+ key = self._serialize(parsed)
232
+ votes[key] += 1
233
+ results_map[key] = parsed
234
+
235
+ # Voting loop
236
+ round_num = 1
237
+ while samples < self.voting.max_samples:
238
+ if votes:
239
+ top = votes.most_common(2)
240
+ top_count = top[0][1]
241
+ second_count = top[1][1] if len(top) > 1 else 0
242
+ if top_count - second_count >= self.voting.k:
243
+ break
244
+
245
+ round_num += 1
246
+ if progress_callback:
247
+ progress_callback(0.2 + 0.6 * (samples / self.voting.max_samples), f"Voting round {round_num}...")
248
+
249
+ for _ in range(self.voting.parallel_samples):
250
+ if samples >= self.voting.max_samples:
251
+ break
252
+
253
+ response = await self.llm.generate(full_prompt, temperature=self.voting.temperature_rest)
254
+ samples += 1
255
+ self.stats["samples"] += 1
256
+
257
+ if self._check_red_flags(response):
258
+ flagged += 1
259
+ continue
260
+
261
+ parsed = self._parse_json(response)
262
+ if parsed and (not expected_keys or all(k in parsed for k in expected_keys)):
263
+ key = self._serialize(parsed)
264
+ votes[key] += 1
265
+ if key not in results_map:
266
+ results_map[key] = parsed
267
+
268
+ if progress_callback:
269
+ progress_callback(1.0, "Complete!")
270
+
271
+ if votes:
272
+ top_key, top_count = votes.most_common(1)[0]
273
+ return {
274
+ "success": True, "result": results_map[top_key],
275
+ "votes": top_count, "total_samples": samples,
276
+ "red_flagged": flagged, "vote_distribution": dict(votes),
277
+ "tool_results": tool_results
278
+ }
279
+
280
+ return {"success": False, "result": None, "votes": 0, "total_samples": samples,
281
+ "red_flagged": flagged, "vote_distribution": {}, "tool_results": tool_results}
282
+
283
+
284
+ # ============================================================================
285
+ # Custom CSS
286
+ # ============================================================================
287
+
288
+ CUSTOM_CSS = """
289
+ .gradio-container {
290
+ max-width: 1200px !important;
291
+ }
292
+
293
+ .header-title {
294
+ background: linear-gradient(90deg, #6366f1, #8b5cf6, #a855f7);
295
+ -webkit-background-clip: text;
296
+ -webkit-text-fill-color: transparent;
297
+ font-size: 2.5rem !important;
298
+ font-weight: 800 !important;
299
+ text-align: center;
300
+ }
301
+
302
+ .header-sub {
303
+ color: #64748b !important;
304
+ text-align: center;
305
+ margin-bottom: 1.5rem !important;
306
+ }
307
+
308
+ .primary-btn {
309
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%) !important;
310
+ border: none !important;
311
+ font-weight: 600 !important;
312
+ border-radius: 8px !important;
313
+ }
314
+
315
+ .primary-btn:hover {
316
+ transform: translateY(-2px) !important;
317
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4) !important;
318
+ }
319
+ """
320
+
321
+ # ============================================================================
322
+ # State & Functions
323
+ # ============================================================================
324
+
325
+ current_agent = None
326
+ loaded_files = {}
327
+
328
+ def create_agent(provider, api_key, model, k_votes):
329
+ global current_agent
330
+ if not api_key:
331
+ return "❌ Please enter API key"
332
+ try:
333
+ llm = LLMClient(provider, api_key, model if model else None)
334
+ current_agent = MAKERAgent(llm, VotingConfig(k=k_votes))
335
+ return f"βœ… Agent ready: {provider} / {llm.model}"
336
+ except Exception as e:
337
+ return f"❌ Error: {e}"
338
+
339
+ async def run_query_async(prompt, use_search, use_files, expected_keys, progress=gr.Progress()):
340
+ global current_agent, loaded_files
341
+
342
+ if not current_agent:
343
+ return {"error": "Create agent first"}, "❌ No agent", ""
344
+
345
+ file_context = None
346
+ if use_files and loaded_files:
347
+ parts = [f"=== {n} ===\n{i.get('content', '')[:10000]}"
348
+ for n, i in loaded_files.items() if i["type"] != "image"]
349
+ file_context = "\n\n".join(parts) if parts else None
350
+
351
+ keys = [k.strip() for k in expected_keys.split(",") if k.strip()] if expected_keys else None
352
+
353
+ def update_progress(pct, msg):
354
+ progress(pct, desc=msg)
355
+
356
+ try:
357
+ result = await current_agent.execute(prompt, keys, use_search, file_context, update_progress)
358
+
359
+ stats = f"""### Stats
360
+ - **Success**: {'βœ…' if result['success'] else '❌'}
361
+ - **Votes**: {result['votes']}
362
+ - **Samples**: {result['total_samples']}
363
+ - **Red-flagged**: {result['red_flagged']}"""
364
+
365
+ votes = "### Vote Distribution\n" + "\n".join([f"- {v} votes: {k[:80]}..." for k, v in
366
+ sorted(result['vote_distribution'].items(), key=lambda x: -x[1])[:3]]) if result['vote_distribution'] else ""
367
+
368
+ return result['result'], stats, votes
369
+ except Exception as e:
370
+ return {"error": str(e)}, f"❌ {e}", ""
371
+
372
+ def run_query(prompt, use_search, use_files, expected_keys, progress=gr.Progress()):
373
+ return asyncio.run(run_query_async(prompt, use_search, use_files, expected_keys, progress))
374
+
375
+ def handle_files(files):
376
+ global loaded_files
377
+ if not files:
378
+ loaded_files = {}
379
+ return "No files"
380
+
381
+ loaded_files = {}
382
+ results = []
383
+ for f in files:
384
+ info = asyncio.run(FileHandler.load_file(f.name))
385
+ loaded_files[info['name']] = info
386
+ if info['type'] == 'error':
387
+ results.append(f"❌ {info['name']}: {info['content']}")
388
+ elif info['type'] == 'image':
389
+ results.append(f"πŸ–ΌοΈ {info['name']}")
390
+ else:
391
+ results.append(f"βœ… {info['name']} ({len(info.get('content', ''))} chars)")
392
+
393
+ return "\n".join(results)
394
+
395
+
396
+ # ============================================================================
397
+ # UI
398
+ # ============================================================================
399
+
400
+ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(), title="MAKER Agent") as demo:
401
+
402
+ gr.HTML("""
403
+ <div style="text-align: center; padding: 20px 0;">
404
+ <h1 class="header-title">πŸ”§ MAKER Agent</h1>
405
+ <p class="header-sub">Reliable AI with Voting & Red-Flagging | Based on arxiv.org/abs/2511.09030</p>
406
+ </div>
407
+ """)
408
+
409
+ with gr.Tabs():
410
+
411
+ # Setup Tab
412
+ with gr.Tab("βš™οΈ Setup"):
413
+ gr.Markdown("### Configure your LLM provider")
414
+ with gr.Row():
415
+ with gr.Column():
416
+ provider = gr.Dropdown(
417
+ ["openai", "anthropic", "groq", "together", "openrouter"],
418
+ value="openai", label="Provider"
419
+ )
420
+ api_key = gr.Textbox(label="API Key", type="password", placeholder="sk-...")
421
+ model = gr.Textbox(label="Model (optional)", placeholder="Leave blank for default")
422
+
423
+ with gr.Column():
424
+ k_votes = gr.Slider(1, 10, value=3, step=1, label="K (votes needed to win)",
425
+ info="Higher = more reliable but slower")
426
+ gr.Markdown("""
427
+ ### How MAKER Works
428
+ 1. **Voting**: Samples multiple responses, winner needs K votes ahead
429
+ 2. **Red-Flagging**: Discards suspicious outputs (too long, malformed)
430
+ 3. **Tools**: Optional web search for current information
431
+ """)
432
+
433
+ setup_btn = gr.Button("πŸš€ Create Agent", elem_classes="primary-btn")
434
+ setup_status = gr.Markdown("πŸ‘† Enter your API key and click Create Agent to start")
435
+ setup_btn.click(create_agent, [provider, api_key, model, k_votes], setup_status)
436
+
437
+ # Query Tab
438
+ with gr.Tab("πŸ’¬ Query"):
439
+ gr.Markdown("### Ask a question")
440
+ with gr.Row():
441
+ with gr.Column(scale=2):
442
+ prompt = gr.Textbox(
443
+ label="Your Query",
444
+ lines=4,
445
+ placeholder="Ask anything... The agent will use voting to ensure reliable answers.\n\nExample: What are the key factors for startup success? Return as JSON with keys: factors, explanation"
446
+ )
447
+ with gr.Row():
448
+ use_search = gr.Checkbox(label="πŸ” Enable Web Search", info="Search DuckDuckGo for current info")
449
+ use_files = gr.Checkbox(label="πŸ“ Use Uploaded Files", info="Include file content in context")
450
+ expected_keys = gr.Textbox(
451
+ label="Expected JSON keys (optional)",
452
+ placeholder="answer, confidence, sources",
453
+ info="Comma-separated list of required keys in response"
454
+ )
455
+ run_btn = gr.Button("▢️ Run Query", elem_classes="primary-btn")
456
+
457
+ with gr.Column(scale=1):
458
+ gr.Markdown("""### Example Queries
459
+
460
+ **Simple Analysis:**
461
+ ```
462
+ What factors lead to startup success?
463
+ ```
464
+
465
+ **With Web Search:**
466
+ ```
467
+ What are the latest AI news this week?
468
+ ```
469
+
470
+ **With Expected Keys:**
471
+ ```
472
+ Analyze the pros and cons of remote work.
473
+ Expected keys: pros, cons, recommendation
474
+ ```
475
+ """)
476
+
477
+ gr.Markdown("---")
478
+ gr.Markdown("### Results")
479
+
480
+ with gr.Row():
481
+ with gr.Column(scale=2):
482
+ result_json = gr.JSON(label="Response")
483
+ with gr.Column(scale=1):
484
+ stats_md = gr.Markdown("*Run a query to see stats*")
485
+ votes_md = gr.Markdown("")
486
+
487
+ run_btn.click(
488
+ run_query,
489
+ [prompt, use_search, use_files, expected_keys],
490
+ [result_json, stats_md, votes_md]
491
+ )
492
+
493
+ # Files Tab
494
+ with gr.Tab("πŸ“ Files"):
495
+ gr.Markdown("### Upload files for analysis")
496
+ gr.Markdown("Supported formats: PDF, DOCX, TXT, MD, JSON, CSV, PNG, JPG")
497
+
498
+ file_upload = gr.File(
499
+ label="Upload Files",
500
+ file_count="multiple",
501
+ file_types=[".pdf", ".docx", ".txt", ".md", ".json", ".csv", ".png", ".jpg", ".jpeg"]
502
+ )
503
+ file_status = gr.Markdown("*No files uploaded*")
504
+ file_upload.change(handle_files, file_upload, file_status)
505
+
506
+ gr.Markdown("""
507
+ ### How to use files
508
+ 1. Upload your files above
509
+ 2. Go to the **Query** tab
510
+ 3. Check **"Use Uploaded Files"**
511
+ 4. Ask questions about your documents!
512
+ """)
513
+
514
+ # About Tab
515
+ with gr.Tab("ℹ️ About"):
516
+ gr.Markdown("""
517
+ ## About MAKER Framework
518
+
519
+ **MAKER** (Massively Decomposed Agentic Processes) achieves near-zero errors through:
520
+
521
+ | Pillar | Description |
522
+ |--------|-------------|
523
+ | **Maximal Decomposition** | Break tasks into single-step atomic operations |
524
+ | **K-Voting** | Sample multiple times, winner needs K votes ahead |
525
+ | **Red-Flagging** | Discard suspicious outputs (don't try to repair them) |
526
+
527
+ ### Key Insight
528
+
529
+ > *"Reliability is an engineering problem, not a model problem."*
530
+
531
+ Instead of waiting for better models, you can achieve near-zero errors TODAY using smaller, cheaper models with statistical voting.
532
+
533
+ ### Results from the Paper
534
+
535
+ The researchers achieved **1,000,000 steps with ZERO errors** using gpt-4.1-mini!
536
+
537
+ ### Links
538
+
539
+ - πŸ“„ **Paper**: [arxiv.org/abs/2511.09030](https://arxiv.org/abs/2511.09030)
540
+ - πŸŽ₯ **Video Explanation**: [YouTube](https://youtube.com/watch?v=TJ-vWGCosdQ)
541
 
542
+ ### Supported LLM Providers
 
543
 
544
+ | Provider | Example Models |
545
+ |----------|----------------|
546
+ | OpenAI | gpt-4o-mini, gpt-4o |
547
+ | Anthropic | claude-sonnet, claude-opus |
548
+ | Groq | llama-3.3-70b (very fast!) |
549
+ | Together | Llama, Mistral, Qwen |
550
+ | OpenRouter | 100+ models |
551
+ """)
552
+
553
+ gr.HTML("""
554
+ <div style="text-align:center; color:#64748b; padding:20px; border-top: 1px solid #e2e8f0; margin-top: 20px;">
555
+ MAKER Framework | Based on <a href="https://arxiv.org/abs/2511.09030" style="color:#6366f1">arxiv.org/abs/2511.09030</a>
556
+ </div>
557
+ """)
558
 
559
+ if __name__ == "__main__":
560
+ demo.launch()