Update app.py
Browse files
app.py
CHANGED
|
@@ -19,20 +19,16 @@ import sys
|
|
| 19 |
import asyncio
|
| 20 |
import json
|
| 21 |
import zipfile
|
|
|
|
| 22 |
from pathlib import Path
|
| 23 |
-
from typing import Dict, Any, Optional, Tuple, AsyncGenerator
|
|
|
|
| 24 |
from datetime import datetime
|
| 25 |
|
| 26 |
import gradio as gr
|
| 27 |
import plotly.graph_objects as go
|
| 28 |
import networkx as nx
|
| 29 |
|
| 30 |
-
# Optional: upload generated ZIPs back into the Space repo
|
| 31 |
-
try:
|
| 32 |
-
from huggingface_hub import HfApi
|
| 33 |
-
except Exception:
|
| 34 |
-
HfApi = None # Safe fallback if not installed
|
| 35 |
-
|
| 36 |
# Add project root to path
|
| 37 |
sys.path.insert(0, str(Path(__file__).parent))
|
| 38 |
|
|
@@ -44,29 +40,12 @@ from ui.voice_interface import voice
|
|
| 44 |
|
| 45 |
# Load environment variables
|
| 46 |
from dotenv import load_dotenv
|
| 47 |
-
load_dotenv()
|
| 48 |
|
|
|
|
| 49 |
|
| 50 |
-
#
|
| 51 |
-
#
|
| 52 |
-
#
|
| 53 |
-
|
| 54 |
-
def to_jsonable(obj: Any) -> Any:
|
| 55 |
-
"""
|
| 56 |
-
Recursively convert objects to JSON-serializable equivalents.
|
| 57 |
-
- Path -> str
|
| 58 |
-
- datetime -> isoformat
|
| 59 |
-
- dict/list/tuple/set -> recurse
|
| 60 |
-
"""
|
| 61 |
-
if isinstance(obj, Path):
|
| 62 |
-
return str(obj)
|
| 63 |
-
if isinstance(obj, datetime):
|
| 64 |
-
return obj.isoformat()
|
| 65 |
-
if isinstance(obj, dict):
|
| 66 |
-
return {k: to_jsonable(v) for k, v in obj.items()}
|
| 67 |
-
if isinstance(obj, (list, tuple, set)):
|
| 68 |
-
return [to_jsonable(v) for v in obj]
|
| 69 |
-
return obj
|
| 70 |
|
| 71 |
|
| 72 |
def create_download_zip(server_metadata: Dict[str, Any]) -> Optional[str]:
|
|
@@ -74,81 +53,80 @@ def create_download_zip(server_metadata: Dict[str, Any]) -> Optional[str]:
|
|
| 74 |
Create a ZIP file of the generated MCP server for download.
|
| 75 |
|
| 76 |
Returns:
|
| 77 |
-
Path to the ZIP file
|
| 78 |
"""
|
| 79 |
try:
|
| 80 |
server_dir = Path(server_metadata["directory"])
|
| 81 |
server_id = server_metadata["server_id"]
|
| 82 |
zip_path = server_dir.parent / f"{server_id}.zip"
|
| 83 |
|
|
|
|
|
|
|
|
|
|
| 84 |
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
| 85 |
for file_path in server_dir.rglob("*"):
|
| 86 |
if file_path.is_file():
|
| 87 |
arcname = file_path.relative_to(server_dir.parent)
|
| 88 |
zipf.write(file_path, arcname)
|
| 89 |
|
| 90 |
-
print(f"[
|
| 91 |
return str(zip_path)
|
| 92 |
except Exception as e:
|
| 93 |
print(f"[ERROR] Failed to create ZIP: {e}")
|
| 94 |
return None
|
| 95 |
|
| 96 |
|
| 97 |
-
def push_zip_to_space_repo(zip_path:
|
| 98 |
"""
|
| 99 |
-
Upload the generated ZIP into
|
| 100 |
|
| 101 |
Uses:
|
| 102 |
-
- SPACE_ID
|
| 103 |
-
- HF_WRITE_TOKEN (
|
| 104 |
-
or HF_TOKEN as a fallback.
|
| 105 |
|
| 106 |
Returns:
|
| 107 |
-
|
| 108 |
"""
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
token = os.getenv("HF_WRITE_TOKEN") or os.getenv("HF_TOKEN")
|
| 114 |
-
|
| 115 |
-
if HfApi is None:
|
| 116 |
-
print("[HF] huggingface_hub not installed - skipping Hub upload.")
|
| 117 |
return None
|
| 118 |
|
|
|
|
| 119 |
if not space_id:
|
| 120 |
-
print("[HF] SPACE_ID env var not
|
| 121 |
return None
|
| 122 |
|
|
|
|
| 123 |
if not token:
|
| 124 |
-
print("[HF] HF_WRITE_TOKEN / HF_TOKEN not set
|
| 125 |
return None
|
| 126 |
|
| 127 |
-
|
| 128 |
-
api = HfApi(token=token)
|
| 129 |
|
| 130 |
-
|
| 131 |
-
|
| 132 |
|
| 133 |
-
|
| 134 |
-
|
|
|
|
| 135 |
path_in_repo=path_in_repo,
|
| 136 |
repo_id=space_id,
|
| 137 |
repo_type="space",
|
| 138 |
)
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
return hub_url
|
| 143 |
-
|
| 144 |
except Exception as e:
|
| 145 |
-
print(f"[HF]
|
| 146 |
return None
|
| 147 |
|
| 148 |
|
| 149 |
-
#
|
| 150 |
# Agent Visualization (Blaxel Integration)
|
| 151 |
-
#
|
|
|
|
| 152 |
|
| 153 |
def create_agent_graph(agent_state: Dict[str, Any]) -> go.Figure:
|
| 154 |
"""
|
|
@@ -195,8 +173,8 @@ def create_agent_graph(agent_state: Dict[str, Any]) -> go.Figure:
|
|
| 195 |
node_colors = []
|
| 196 |
|
| 197 |
color_map = {
|
| 198 |
-
"planning": "#3B82F6",
|
| 199 |
-
"generating": "#10B981",
|
| 200 |
"deploying": "#F59E0B", # Orange
|
| 201 |
"executing": "#8B5CF6", # Purple
|
| 202 |
"completed": "#6B7280", # Gray
|
|
@@ -217,11 +195,7 @@ def create_agent_graph(agent_state: Dict[str, Any]) -> go.Figure:
|
|
| 217 |
hoverinfo="text",
|
| 218 |
text=node_text,
|
| 219 |
textposition="top center",
|
| 220 |
-
marker=dict(
|
| 221 |
-
size=30,
|
| 222 |
-
color=node_colors,
|
| 223 |
-
line=dict(width=2, color="white"),
|
| 224 |
-
),
|
| 225 |
)
|
| 226 |
|
| 227 |
# Create figure
|
|
@@ -242,9 +216,10 @@ def create_agent_graph(agent_state: Dict[str, Any]) -> go.Figure:
|
|
| 242 |
return fig
|
| 243 |
|
| 244 |
|
| 245 |
-
#
|
| 246 |
# Core Agent Orchestration
|
| 247 |
-
#
|
|
|
|
| 248 |
|
| 249 |
async def orchestrate_task(
|
| 250 |
user_request: str,
|
|
@@ -255,7 +230,7 @@ async def orchestrate_task(
|
|
| 255 |
Main orchestration function - the brain of OmniMind.
|
| 256 |
|
| 257 |
Yields:
|
| 258 |
-
(status_text, agent_graph, metadata,
|
| 259 |
"""
|
| 260 |
output = "# 🤖 OmniMind Orchestrator\n\n"
|
| 261 |
output += f"**Request:** {user_request}\n\n"
|
|
@@ -266,7 +241,6 @@ async def orchestrate_task(
|
|
| 266 |
"edges": [],
|
| 267 |
}
|
| 268 |
|
| 269 |
-
# Initial state
|
| 270 |
yield (output, create_agent_graph(agent_state), {}, None)
|
| 271 |
|
| 272 |
# Step 1: Analyze request with multi-model router
|
|
@@ -294,7 +268,9 @@ Respond in JSON:
|
|
| 294 |
"""
|
| 295 |
|
| 296 |
analysis = await router.generate(
|
| 297 |
-
analysis_prompt,
|
|
|
|
|
|
|
| 298 |
)
|
| 299 |
|
| 300 |
try:
|
|
@@ -317,12 +293,7 @@ Respond in JSON:
|
|
| 317 |
)
|
| 318 |
agent_state["edges"].append({"from": "start", "to": "analyze"})
|
| 319 |
|
| 320 |
-
yield (
|
| 321 |
-
output,
|
| 322 |
-
create_agent_graph(agent_state),
|
| 323 |
-
to_jsonable(analysis_data),
|
| 324 |
-
None,
|
| 325 |
-
)
|
| 326 |
|
| 327 |
# Step 2: Get knowledge context (if enabled)
|
| 328 |
context = None
|
|
@@ -341,15 +312,10 @@ Respond in JSON:
|
|
| 341 |
output += "**No relevant context found**\n\n"
|
| 342 |
|
| 343 |
agent_state["nodes"][-1]["type"] = "completed"
|
| 344 |
-
yield (
|
| 345 |
-
output,
|
| 346 |
-
create_agent_graph(agent_state),
|
| 347 |
-
{"has_context": bool(context)},
|
| 348 |
-
None,
|
| 349 |
-
)
|
| 350 |
|
| 351 |
# Step 3: Generate MCP (if needed)
|
| 352 |
-
server_metadata
|
| 353 |
zip_path: Optional[str] = None
|
| 354 |
|
| 355 |
if analysis_data.get("needs_custom_mcp", False):
|
|
@@ -370,8 +336,7 @@ Respond in JSON:
|
|
| 370 |
|
| 371 |
output += f"✅ **Generated:** {server_metadata['server_name']}\n"
|
| 372 |
output += (
|
| 373 |
-
f"**Tools:** "
|
| 374 |
-
f"{', '.join([t['name'] for t in server_metadata['tools']])}\n"
|
| 375 |
)
|
| 376 |
output += f"**Location:** `{server_metadata['directory']}`\n\n"
|
| 377 |
|
|
@@ -379,9 +344,10 @@ Respond in JSON:
|
|
| 379 |
output += "### 📄 Generated Code Preview\n\n"
|
| 380 |
output += "```python\n"
|
| 381 |
try:
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
|
|
|
| 385 |
output += "".join(code_lines)
|
| 386 |
if len(code_lines) >= 30:
|
| 387 |
output += "\n... (truncated - full code saved locally)\n"
|
|
@@ -399,25 +365,22 @@ Respond in JSON:
|
|
| 399 |
server_metadata["zip_path"] = zip_path
|
| 400 |
output += "📦 **Download button updated below!**\n\n"
|
| 401 |
|
| 402 |
-
#
|
| 403 |
-
hub_url = push_zip_to_space_repo(zip_path)
|
| 404 |
if hub_url:
|
| 405 |
server_metadata["hub_url"] = hub_url
|
| 406 |
-
output += f"
|
| 407 |
else:
|
| 408 |
output += (
|
| 409 |
-
"⚠️ Could not
|
| 410 |
-
"You can still use the download button and upload manually.\n\n"
|
| 411 |
)
|
| 412 |
-
else:
|
| 413 |
-
output += "⚠️ Failed to create ZIP archive for download.\n\n"
|
| 414 |
|
| 415 |
agent_state["nodes"][-1]["type"] = "completed"
|
| 416 |
yield (
|
| 417 |
output,
|
| 418 |
create_agent_graph(agent_state),
|
| 419 |
-
|
| 420 |
-
zip_path,
|
| 421 |
)
|
| 422 |
|
| 423 |
# Step 4: Deploy to Modal
|
|
@@ -426,29 +389,25 @@ Respond in JSON:
|
|
| 426 |
{"id": "deploy", "label": "Deploy", "type": "deploying"}
|
| 427 |
)
|
| 428 |
agent_state["edges"].append({"from": "generate", "to": "deploy"})
|
| 429 |
-
yield (output, create_agent_graph(agent_state), {},
|
| 430 |
|
| 431 |
deployment = await deployer.deploy_mcp_server(server_metadata)
|
| 432 |
|
| 433 |
if deployment.get("simulated"):
|
| 434 |
-
output +=
|
|
|
|
|
|
|
| 435 |
|
| 436 |
if deployment.get("status") == "failed":
|
| 437 |
output += (
|
| 438 |
-
f"⚠️ **Deployment skipped:** "
|
| 439 |
-
f"{deployment.get('error', 'Unknown error')}\n\n"
|
| 440 |
)
|
| 441 |
else:
|
| 442 |
output += f"**URL:** {deployment.get('modal_url', 'N/A')}\n"
|
| 443 |
output += f"**Status:** {deployment.get('status', 'unknown')}\n\n"
|
| 444 |
|
| 445 |
agent_state["nodes"][-1]["type"] = "completed"
|
| 446 |
-
yield (
|
| 447 |
-
output,
|
| 448 |
-
create_agent_graph(agent_state),
|
| 449 |
-
to_jsonable(deployment),
|
| 450 |
-
zip_path,
|
| 451 |
-
)
|
| 452 |
|
| 453 |
# Step 5: Final response generation
|
| 454 |
output += "## ✨ Step 5: Generating Response\n\n"
|
|
@@ -474,7 +433,9 @@ Provide a helpful response explaining what was accomplished and how the user can
|
|
| 474 |
"""
|
| 475 |
|
| 476 |
final_response = await router.generate(
|
| 477 |
-
response_prompt,
|
|
|
|
|
|
|
| 478 |
)
|
| 479 |
|
| 480 |
output += final_response["response"] + "\n\n"
|
|
@@ -486,27 +447,29 @@ Provide a helpful response explaining what was accomplished and how the user can
|
|
| 486 |
if use_voice and voice.client:
|
| 487 |
output += "\n🔊 **Generating voice response...**\n"
|
| 488 |
yield (output, create_agent_graph(agent_state), {}, zip_path)
|
| 489 |
-
# Voice generation
|
| 490 |
|
| 491 |
output += "\n---\n\n"
|
| 492 |
output += "**Model Usage:**\n"
|
| 493 |
stats = router.get_usage_stats()
|
| 494 |
output += f"- Total Requests: {stats['total_requests']}\n"
|
| 495 |
output += f"- Total Cost: ${stats['total_cost']}\n"
|
| 496 |
-
output += f"- Claude: {stats['by_model']['claude']['requests']}\n"
|
| 497 |
-
output += f"- Gemini: {stats['by_model']['gemini']['requests']}\n"
|
| 498 |
-
output += f"- GPT-4: {stats['by_model']['gpt4']['requests']}\n"
|
| 499 |
|
| 500 |
-
yield (output, create_agent_graph(agent_state),
|
| 501 |
|
| 502 |
|
| 503 |
-
#
|
| 504 |
# Gradio UI
|
| 505 |
-
#
|
|
|
|
| 506 |
|
| 507 |
def build_ui() -> gr.Blocks:
|
| 508 |
-
"""Build the Gradio interface"""
|
| 509 |
|
|
|
|
| 510 |
custom_css = """
|
| 511 |
.gradio-container {
|
| 512 |
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
@@ -528,7 +491,7 @@ def build_ui() -> gr.Blocks:
|
|
| 528 |
"""
|
| 529 |
|
| 530 |
with gr.Blocks(title="OmniMind Orchestrator - MCP Hackathon") as app:
|
| 531 |
-
# Inject CSS
|
| 532 |
gr.HTML(f"<style>{custom_css}</style>")
|
| 533 |
|
| 534 |
gr.HTML(
|
|
@@ -536,7 +499,7 @@ def build_ui() -> gr.Blocks:
|
|
| 536 |
<div class="main-header">
|
| 537 |
<h1>🧠 OmniMind Orchestrator</h1>
|
| 538 |
<p>The World's First Self-Evolving Multi-Agent MCP Ecosystem</p>
|
| 539 |
-
<p style="font-size: 0.9em; opacity: 0.9
|
| 540 |
Track 2 Submission - MCP's 1st Birthday Hackathon
|
| 541 |
</p>
|
| 542 |
</div>
|
|
@@ -563,10 +526,7 @@ def build_ui() -> gr.Blocks:
|
|
| 563 |
|
| 564 |
user_input = gr.Textbox(
|
| 565 |
label="What do you need?",
|
| 566 |
-
placeholder=
|
| 567 |
-
"Example: Create a tool that monitors my competitor's "
|
| 568 |
-
"pricing every hour"
|
| 569 |
-
),
|
| 570 |
lines=3,
|
| 571 |
)
|
| 572 |
|
|
@@ -575,8 +535,7 @@ def build_ui() -> gr.Blocks:
|
|
| 575 |
use_kb = gr.Checkbox(label="📚 Use Knowledge Base", value=False)
|
| 576 |
|
| 577 |
submit_btn = gr.Button(
|
| 578 |
-
"🚀 Let OmniMind Handle It",
|
| 579 |
-
variant="primary",
|
| 580 |
)
|
| 581 |
|
| 582 |
gr.Markdown(
|
|
@@ -592,14 +551,12 @@ def build_ui() -> gr.Blocks:
|
|
| 592 |
|
| 593 |
with gr.Column(scale=2):
|
| 594 |
output_md = gr.Markdown(
|
| 595 |
-
value="**Results will appear here**",
|
| 596 |
-
label="Agent Output",
|
| 597 |
)
|
| 598 |
|
| 599 |
agent_graph = gr.Plot(label="🧠 Agent Brain (Real-Time)")
|
| 600 |
|
| 601 |
-
|
| 602 |
-
download_btn = gr.DownloadButton(
|
| 603 |
label="📦 Download Generated MCP Server",
|
| 604 |
visible=False,
|
| 605 |
)
|
|
@@ -667,12 +624,12 @@ def build_ui() -> gr.Blocks:
|
|
| 667 |
1. **Never-Before-Done**: First agent that creates agents
|
| 668 |
2. **All Sponsors**: Uses every sponsor technology meaningfully
|
| 669 |
3. **Real Impact**: Saves enterprises weeks of custom development
|
| 670 |
-
4. **Beautiful UX**: Gradio + voice + visualizations
|
| 671 |
5. **Production-Ready**: Clean code, error handling, scalability
|
| 672 |
|
| 673 |
### Built With
|
| 674 |
|
| 675 |
-
- Gradio
|
| 676 |
- LangGraph
|
| 677 |
- Claude Sonnet 4
|
| 678 |
- Gemini 2.0 Flash
|
|
@@ -694,39 +651,37 @@ def build_ui() -> gr.Blocks:
|
|
| 694 |
async def handle_submit(request, voice_enabled, kb_enabled):
|
| 695 |
"""Handle user submissions"""
|
| 696 |
async for (
|
| 697 |
-
|
| 698 |
graph,
|
| 699 |
metadata,
|
| 700 |
download_zip,
|
| 701 |
) in orchestrate_task(request, voice_enabled, kb_enabled):
|
| 702 |
-
|
| 703 |
if download_zip:
|
| 704 |
-
# Provide a file path to DownloadButton
|
| 705 |
yield (
|
| 706 |
-
|
| 707 |
graph,
|
| 708 |
metadata,
|
| 709 |
gr.update(value=download_zip, visible=True),
|
| 710 |
)
|
| 711 |
else:
|
| 712 |
yield (
|
| 713 |
-
|
| 714 |
graph,
|
| 715 |
metadata,
|
| 716 |
-
gr.update(visible=False),
|
| 717 |
)
|
| 718 |
|
| 719 |
submit_btn.click(
|
| 720 |
fn=handle_submit,
|
| 721 |
inputs=[user_input, use_voice, use_kb],
|
| 722 |
-
outputs=[output_md, agent_graph, metadata_json,
|
| 723 |
)
|
| 724 |
|
| 725 |
gr.Markdown(
|
| 726 |
"""
|
| 727 |
---
|
| 728 |
<div style="text-align: center; padding: 1rem; color: #666;">
|
| 729 |
-
🎉 Built for MCP's 1st Birthday Hackathon | Hosted by Anthropic &
|
| 730 |
</div>
|
| 731 |
"""
|
| 732 |
)
|
|
@@ -734,9 +689,10 @@ def build_ui() -> gr.Blocks:
|
|
| 734 |
return app
|
| 735 |
|
| 736 |
|
| 737 |
-
#
|
| 738 |
# Main Execution
|
| 739 |
-
#
|
|
|
|
| 740 |
|
| 741 |
if __name__ == "__main__":
|
| 742 |
print("=" * 60)
|
|
|
|
| 19 |
import asyncio
|
| 20 |
import json
|
| 21 |
import zipfile
|
| 22 |
+
import shutil
|
| 23 |
from pathlib import Path
|
| 24 |
+
from typing import Dict, Any, List, Optional, Tuple, AsyncGenerator
|
| 25 |
+
|
| 26 |
from datetime import datetime
|
| 27 |
|
| 28 |
import gradio as gr
|
| 29 |
import plotly.graph_objects as go
|
| 30 |
import networkx as nx
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
# Add project root to path
|
| 33 |
sys.path.insert(0, str(Path(__file__).parent))
|
| 34 |
|
|
|
|
| 40 |
|
| 41 |
# Load environment variables
|
| 42 |
from dotenv import load_dotenv
|
|
|
|
| 43 |
|
| 44 |
+
load_dotenv()
|
| 45 |
|
| 46 |
+
# ============================================================================
|
| 47 |
+
# Download + Hub Upload Functionality
|
| 48 |
+
# ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
|
| 51 |
def create_download_zip(server_metadata: Dict[str, Any]) -> Optional[str]:
|
|
|
|
| 53 |
Create a ZIP file of the generated MCP server for download.
|
| 54 |
|
| 55 |
Returns:
|
| 56 |
+
Path to the ZIP file, or None if creation fails
|
| 57 |
"""
|
| 58 |
try:
|
| 59 |
server_dir = Path(server_metadata["directory"])
|
| 60 |
server_id = server_metadata["server_id"]
|
| 61 |
zip_path = server_dir.parent / f"{server_id}.zip"
|
| 62 |
|
| 63 |
+
# Make sure parent exists
|
| 64 |
+
zip_path.parent.mkdir(parents=True, exist_ok=True)
|
| 65 |
+
|
| 66 |
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
| 67 |
for file_path in server_dir.rglob("*"):
|
| 68 |
if file_path.is_file():
|
| 69 |
arcname = file_path.relative_to(server_dir.parent)
|
| 70 |
zipf.write(file_path, arcname)
|
| 71 |
|
| 72 |
+
print(f"[ZIP] Created MCP archive at {zip_path}")
|
| 73 |
return str(zip_path)
|
| 74 |
except Exception as e:
|
| 75 |
print(f"[ERROR] Failed to create ZIP: {e}")
|
| 76 |
return None
|
| 77 |
|
| 78 |
|
| 79 |
+
def push_zip_to_space_repo(zip_path: Path) -> Optional[str]:
|
| 80 |
"""
|
| 81 |
+
Upload the generated ZIP into the same Space repository using HF API.
|
| 82 |
|
| 83 |
Uses:
|
| 84 |
+
- SPACE_ID (your space id, e.g. MCP-1st-Birthday/OmniMind-Orchestrator)
|
| 85 |
+
- HF_WRITE_TOKEN (or HF_TOKEN) with write permission
|
|
|
|
| 86 |
|
| 87 |
Returns:
|
| 88 |
+
A Hub URL (string) pointing to the uploaded file, or None on failure.
|
| 89 |
"""
|
| 90 |
+
try:
|
| 91 |
+
from huggingface_hub import HfApi
|
| 92 |
+
except Exception as e:
|
| 93 |
+
print(f"[HF] huggingface_hub not installed - skipping Space upload ({e})")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
return None
|
| 95 |
|
| 96 |
+
space_id = os.getenv("SPACE_ID")
|
| 97 |
if not space_id:
|
| 98 |
+
print("[HF] SPACE_ID env var not set - skipping Space upload")
|
| 99 |
return None
|
| 100 |
|
| 101 |
+
token = os.getenv("HF_WRITE_TOKEN") or os.getenv("HF_TOKEN")
|
| 102 |
if not token:
|
| 103 |
+
print("[HF] HF_WRITE_TOKEN / HF_TOKEN not set - skipping Space upload")
|
| 104 |
return None
|
| 105 |
|
| 106 |
+
api = HfApi(token=token)
|
|
|
|
| 107 |
|
| 108 |
+
# We store files under generated_mcps/ in the repo
|
| 109 |
+
path_in_repo = f"generated_mcps/{zip_path.name}"
|
| 110 |
|
| 111 |
+
try:
|
| 112 |
+
hub_url = api.upload_file(
|
| 113 |
+
path_or_fileobj=str(zip_path),
|
| 114 |
path_in_repo=path_in_repo,
|
| 115 |
repo_id=space_id,
|
| 116 |
repo_type="space",
|
| 117 |
)
|
| 118 |
+
# `hub_url` is already the correct URL to visualize the uploaded file
|
| 119 |
+
print(f"[HF] Uploaded ZIP to Hub at: {hub_url}")
|
| 120 |
+
return str(hub_url)
|
|
|
|
|
|
|
| 121 |
except Exception as e:
|
| 122 |
+
print(f"[HF] Failed to upload ZIP to Hub: {e}")
|
| 123 |
return None
|
| 124 |
|
| 125 |
|
| 126 |
+
# ============================================================================
|
| 127 |
# Agent Visualization (Blaxel Integration)
|
| 128 |
+
# ============================================================================
|
| 129 |
+
|
| 130 |
|
| 131 |
def create_agent_graph(agent_state: Dict[str, Any]) -> go.Figure:
|
| 132 |
"""
|
|
|
|
| 173 |
node_colors = []
|
| 174 |
|
| 175 |
color_map = {
|
| 176 |
+
"planning": "#3B82F6", # Blue
|
| 177 |
+
"generating": "#10B981", # Green
|
| 178 |
"deploying": "#F59E0B", # Orange
|
| 179 |
"executing": "#8B5CF6", # Purple
|
| 180 |
"completed": "#6B7280", # Gray
|
|
|
|
| 195 |
hoverinfo="text",
|
| 196 |
text=node_text,
|
| 197 |
textposition="top center",
|
| 198 |
+
marker=dict(size=30, color=node_colors, line=dict(width=2, color="white")),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
)
|
| 200 |
|
| 201 |
# Create figure
|
|
|
|
| 216 |
return fig
|
| 217 |
|
| 218 |
|
| 219 |
+
# ============================================================================
|
| 220 |
# Core Agent Orchestration
|
| 221 |
+
# ============================================================================
|
| 222 |
+
|
| 223 |
|
| 224 |
async def orchestrate_task(
|
| 225 |
user_request: str,
|
|
|
|
| 230 |
Main orchestration function - the brain of OmniMind.
|
| 231 |
|
| 232 |
Yields:
|
| 233 |
+
(status_text, agent_graph, metadata, zip_path_for_download)
|
| 234 |
"""
|
| 235 |
output = "# 🤖 OmniMind Orchestrator\n\n"
|
| 236 |
output += f"**Request:** {user_request}\n\n"
|
|
|
|
| 241 |
"edges": [],
|
| 242 |
}
|
| 243 |
|
|
|
|
| 244 |
yield (output, create_agent_graph(agent_state), {}, None)
|
| 245 |
|
| 246 |
# Step 1: Analyze request with multi-model router
|
|
|
|
| 268 |
"""
|
| 269 |
|
| 270 |
analysis = await router.generate(
|
| 271 |
+
analysis_prompt,
|
| 272 |
+
task_type=TaskType.PLANNING,
|
| 273 |
+
temperature=0.3,
|
| 274 |
)
|
| 275 |
|
| 276 |
try:
|
|
|
|
| 293 |
)
|
| 294 |
agent_state["edges"].append({"from": "start", "to": "analyze"})
|
| 295 |
|
| 296 |
+
yield (output, create_agent_graph(agent_state), analysis_data, None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
|
| 298 |
# Step 2: Get knowledge context (if enabled)
|
| 299 |
context = None
|
|
|
|
| 312 |
output += "**No relevant context found**\n\n"
|
| 313 |
|
| 314 |
agent_state["nodes"][-1]["type"] = "completed"
|
| 315 |
+
yield (output, create_agent_graph(agent_state), {}, None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
|
| 317 |
# Step 3: Generate MCP (if needed)
|
| 318 |
+
server_metadata = None
|
| 319 |
zip_path: Optional[str] = None
|
| 320 |
|
| 321 |
if analysis_data.get("needs_custom_mcp", False):
|
|
|
|
| 336 |
|
| 337 |
output += f"✅ **Generated:** {server_metadata['server_name']}\n"
|
| 338 |
output += (
|
| 339 |
+
f"**Tools:** {', '.join([t['name'] for t in server_metadata['tools']])}\n"
|
|
|
|
| 340 |
)
|
| 341 |
output += f"**Location:** `{server_metadata['directory']}`\n\n"
|
| 342 |
|
|
|
|
| 344 |
output += "### 📄 Generated Code Preview\n\n"
|
| 345 |
output += "```python\n"
|
| 346 |
try:
|
| 347 |
+
with open(
|
| 348 |
+
server_metadata["files"]["app"], "r", encoding="utf-8"
|
| 349 |
+
) as f_code:
|
| 350 |
+
code_lines = f_code.readlines()[:30] # Show first 30 lines
|
| 351 |
output += "".join(code_lines)
|
| 352 |
if len(code_lines) >= 30:
|
| 353 |
output += "\n... (truncated - full code saved locally)\n"
|
|
|
|
| 365 |
server_metadata["zip_path"] = zip_path
|
| 366 |
output += "📦 **Download button updated below!**\n\n"
|
| 367 |
|
| 368 |
+
# Upload ZIP into this same Space repo (if configured)
|
| 369 |
+
hub_url = push_zip_to_space_repo(Path(zip_path))
|
| 370 |
if hub_url:
|
| 371 |
server_metadata["hub_url"] = hub_url
|
| 372 |
+
output += f"🔗 **Saved to Hub:** {hub_url}\n\n"
|
| 373 |
else:
|
| 374 |
output += (
|
| 375 |
+
"⚠️ **Could not save ZIP to Hub (check Space logs / env vars).**\n\n"
|
|
|
|
| 376 |
)
|
|
|
|
|
|
|
| 377 |
|
| 378 |
agent_state["nodes"][-1]["type"] = "completed"
|
| 379 |
yield (
|
| 380 |
output,
|
| 381 |
create_agent_graph(agent_state),
|
| 382 |
+
server_metadata,
|
| 383 |
+
zip_path if zip_path else None,
|
| 384 |
)
|
| 385 |
|
| 386 |
# Step 4: Deploy to Modal
|
|
|
|
| 389 |
{"id": "deploy", "label": "Deploy", "type": "deploying"}
|
| 390 |
)
|
| 391 |
agent_state["edges"].append({"from": "generate", "to": "deploy"})
|
| 392 |
+
yield (output, create_agent_graph(agent_state), {}, None)
|
| 393 |
|
| 394 |
deployment = await deployer.deploy_mcp_server(server_metadata)
|
| 395 |
|
| 396 |
if deployment.get("simulated"):
|
| 397 |
+
output += (
|
| 398 |
+
"⚠️ **Simulated deployment** (configure MODAL_TOKEN for real deployment)\n"
|
| 399 |
+
)
|
| 400 |
|
| 401 |
if deployment.get("status") == "failed":
|
| 402 |
output += (
|
| 403 |
+
f"⚠️ **Deployment skipped:** {deployment.get('error', 'Unknown error')}\n\n"
|
|
|
|
| 404 |
)
|
| 405 |
else:
|
| 406 |
output += f"**URL:** {deployment.get('modal_url', 'N/A')}\n"
|
| 407 |
output += f"**Status:** {deployment.get('status', 'unknown')}\n\n"
|
| 408 |
|
| 409 |
agent_state["nodes"][-1]["type"] = "completed"
|
| 410 |
+
yield (output, create_agent_graph(agent_state), deployment, zip_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
|
| 412 |
# Step 5: Final response generation
|
| 413 |
output += "## ✨ Step 5: Generating Response\n\n"
|
|
|
|
| 433 |
"""
|
| 434 |
|
| 435 |
final_response = await router.generate(
|
| 436 |
+
response_prompt,
|
| 437 |
+
task_type=TaskType.REASONING,
|
| 438 |
+
temperature=0.7,
|
| 439 |
)
|
| 440 |
|
| 441 |
output += final_response["response"] + "\n\n"
|
|
|
|
| 447 |
if use_voice and voice.client:
|
| 448 |
output += "\n🔊 **Generating voice response...**\n"
|
| 449 |
yield (output, create_agent_graph(agent_state), {}, zip_path)
|
| 450 |
+
# (Voice generation omitted in this demo)
|
| 451 |
|
| 452 |
output += "\n---\n\n"
|
| 453 |
output += "**Model Usage:**\n"
|
| 454 |
stats = router.get_usage_stats()
|
| 455 |
output += f"- Total Requests: {stats['total_requests']}\n"
|
| 456 |
output += f"- Total Cost: ${stats['total_cost']}\n"
|
| 457 |
+
output += f"- Claude: {stats['by_model']['claude']['requests']} requests\n"
|
| 458 |
+
output += f"- Gemini: {stats['by_model']['gemini']['requests']} requests\n"
|
| 459 |
+
output += f"- GPT-4: {stats['by_model']['gpt4']['requests']} requests\n"
|
| 460 |
|
| 461 |
+
yield (output, create_agent_graph(agent_state), stats, zip_path)
|
| 462 |
|
| 463 |
|
| 464 |
+
# ============================================================================
|
| 465 |
# Gradio UI
|
| 466 |
+
# ============================================================================
|
| 467 |
+
|
| 468 |
|
| 469 |
def build_ui() -> gr.Blocks:
|
| 470 |
+
"""Build the Gradio 6 interface"""
|
| 471 |
|
| 472 |
+
# Custom CSS for professional look
|
| 473 |
custom_css = """
|
| 474 |
.gradio-container {
|
| 475 |
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
|
|
| 491 |
"""
|
| 492 |
|
| 493 |
with gr.Blocks(title="OmniMind Orchestrator - MCP Hackathon") as app:
|
| 494 |
+
# Inject CSS (Blocks in this Gradio version doesn't accept css=...)
|
| 495 |
gr.HTML(f"<style>{custom_css}</style>")
|
| 496 |
|
| 497 |
gr.HTML(
|
|
|
|
| 499 |
<div class="main-header">
|
| 500 |
<h1>🧠 OmniMind Orchestrator</h1>
|
| 501 |
<p>The World's First Self-Evolving Multi-Agent MCP Ecosystem</p>
|
| 502 |
+
<p style="font-size: 0.9em; opacity: 0.9;">
|
| 503 |
Track 2 Submission - MCP's 1st Birthday Hackathon
|
| 504 |
</p>
|
| 505 |
</div>
|
|
|
|
| 526 |
|
| 527 |
user_input = gr.Textbox(
|
| 528 |
label="What do you need?",
|
| 529 |
+
placeholder="Example: Create a tool that monitors my competitor's pricing every hour",
|
|
|
|
|
|
|
|
|
|
| 530 |
lines=3,
|
| 531 |
)
|
| 532 |
|
|
|
|
| 535 |
use_kb = gr.Checkbox(label="📚 Use Knowledge Base", value=False)
|
| 536 |
|
| 537 |
submit_btn = gr.Button(
|
| 538 |
+
"🚀 Let OmniMind Handle It", variant="primary", size="lg"
|
|
|
|
| 539 |
)
|
| 540 |
|
| 541 |
gr.Markdown(
|
|
|
|
| 551 |
|
| 552 |
with gr.Column(scale=2):
|
| 553 |
output_md = gr.Markdown(
|
| 554 |
+
value="**Results will appear here**", label="Agent Output"
|
|
|
|
| 555 |
)
|
| 556 |
|
| 557 |
agent_graph = gr.Plot(label="🧠 Agent Brain (Real-Time)")
|
| 558 |
|
| 559 |
+
download_file = gr.File(
|
|
|
|
| 560 |
label="📦 Download Generated MCP Server",
|
| 561 |
visible=False,
|
| 562 |
)
|
|
|
|
| 624 |
1. **Never-Before-Done**: First agent that creates agents
|
| 625 |
2. **All Sponsors**: Uses every sponsor technology meaningfully
|
| 626 |
3. **Real Impact**: Saves enterprises weeks of custom development
|
| 627 |
+
4. **Beautiful UX**: Gradio 6 + voice + visualizations
|
| 628 |
5. **Production-Ready**: Clean code, error handling, scalability
|
| 629 |
|
| 630 |
### Built With
|
| 631 |
|
| 632 |
+
- Gradio 6.0
|
| 633 |
- LangGraph
|
| 634 |
- Claude Sonnet 4
|
| 635 |
- Gemini 2.0 Flash
|
|
|
|
| 651 |
async def handle_submit(request, voice_enabled, kb_enabled):
|
| 652 |
"""Handle user submissions"""
|
| 653 |
async for (
|
| 654 |
+
out_text,
|
| 655 |
graph,
|
| 656 |
metadata,
|
| 657 |
download_zip,
|
| 658 |
) in orchestrate_task(request, voice_enabled, kb_enabled):
|
|
|
|
| 659 |
if download_zip:
|
|
|
|
| 660 |
yield (
|
| 661 |
+
out_text,
|
| 662 |
graph,
|
| 663 |
metadata,
|
| 664 |
gr.update(value=download_zip, visible=True),
|
| 665 |
)
|
| 666 |
else:
|
| 667 |
yield (
|
| 668 |
+
out_text,
|
| 669 |
graph,
|
| 670 |
metadata,
|
| 671 |
+
gr.update(value=None, visible=False),
|
| 672 |
)
|
| 673 |
|
| 674 |
submit_btn.click(
|
| 675 |
fn=handle_submit,
|
| 676 |
inputs=[user_input, use_voice, use_kb],
|
| 677 |
+
outputs=[output_md, agent_graph, metadata_json, download_file],
|
| 678 |
)
|
| 679 |
|
| 680 |
gr.Markdown(
|
| 681 |
"""
|
| 682 |
---
|
| 683 |
<div style="text-align: center; padding: 1rem; color: #666;">
|
| 684 |
+
🎉 Built for MCP's 1st Birthday Hackathon | Hosted by Anthropic & Gradio
|
| 685 |
</div>
|
| 686 |
"""
|
| 687 |
)
|
|
|
|
| 689 |
return app
|
| 690 |
|
| 691 |
|
| 692 |
+
# ============================================================================
|
| 693 |
# Main Execution
|
| 694 |
+
# ============================================================================
|
| 695 |
+
|
| 696 |
|
| 697 |
if __name__ == "__main__":
|
| 698 |
print("=" * 60)
|