| """ |
| Enhanced Gradio Interface for ComputeAgent with Tool Approval Support |
| |
| This interface supports BOTH capacity approval and tool approval with full |
| modification capabilities. |
| |
| Features: |
| - Capacity approval (existing) |
| - Tool approval (NEW) |
| - Tool argument modification (NEW) |
| - Re-reasoning requests (NEW) |
| - Batch tool operations (NEW) |
| |
| Author: ComputeAgent Team |
| """ |
| |
| import os |
| import sys |
| sys.path.append("/home/hivenet") |
|
|
| import asyncio |
| import gradio as gr |
| import httpx |
| import logging |
| from typing import Optional, Dict, Any, List, Tuple |
| from datetime import datetime |
| import json |
| import base64 |
| from pathlib import Path |
|
|
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger("ComputeAgent-UI") |
|
|
| |
| API_BASE_URL = "http://localhost:8000" |
| API_TIMEOUT = 300.0 |
|
|
| |
| LOCATION_GPU_MAP = { |
| "France": ["RTX 4090"], |
| "UAE-1": ["RTX 4090"], |
| "Texas": ["RTX 5090"], |
| "UAE-2": ["RTX 5090"] |
| } |
|
|
| |
| def get_logo_base64(filename): |
| """Load a logo and convert to base64 for embedding in HTML.""" |
| try: |
| logo_path = Path(__file__).parent / filename |
| with open(logo_path, "rb") as f: |
| logo_bytes = f.read() |
| return base64.b64encode(logo_bytes).decode() |
| except Exception as e: |
| logger.warning(f"Could not load logo {filename}: {e}") |
| return None |
|
|
| HIVENET_LOGO_BASE64 = get_logo_base64("hivenet.jpg") |
| COMPUTEAGENT_LOGO_BASE64 = get_logo_base64("ComputeAgent.png") |
|
|
|
|
| class ComputeAgentClient: |
| """Client for interacting with ComputeAgent FastAPI backend.""" |
|
|
| def __init__(self, base_url: str): |
| self.base_url = base_url |
| self.client = httpx.AsyncClient(timeout=API_TIMEOUT) |
| |
| async def send_query( |
| self, |
| query: str, |
| user_id: str = "demo_user", |
| session_id: str = "demo_session" |
| ) -> Dict[str, Any]: |
| """Send query to FastAPI backend.""" |
| try: |
| response = await self.client.post( |
| f"{self.base_url}/api/compute/query", |
| json={ |
| "query": query, |
| "user_id": user_id, |
| "session_id": session_id |
| } |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"❌ Error sending query: {e}") |
| return {"success": False, "error": str(e)} |
| |
| async def continue_execution( |
| self, |
| thread_id: str, |
| user_input: Dict[str, Any] |
| ) -> Dict[str, Any]: |
| """Continue execution after interrupt.""" |
| try: |
| response = await self.client.post( |
| f"{self.base_url}/api/compute/continue/{thread_id}", |
| json=user_input |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"❌ Error continuing: {e}") |
| return {"success": False, "error": str(e)} |
| |
| async def approve_tools( |
| self, |
| thread_id: str, |
| decision: Dict[str, Any] |
| ) -> Dict[str, Any]: |
| """Approve/reject/modify tools.""" |
| try: |
| response = await self.client.post( |
| f"{self.base_url}/api/compute/approve-tools", |
| json={ |
| "thread_id": thread_id, |
| **decision |
| } |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"❌ Error with tool approval: {e}") |
| return {"success": False, "error": str(e)} |
|
|
|
|
| class ComputeAgentInterface: |
| """Enhanced Gradio interface with tool approval.""" |
|
|
| def __init__(self, api_base_url: str): |
| self.client = ComputeAgentClient(api_base_url) |
| self.current_thread_id = None |
| self.current_interrupt_data = None |
| self.approval_type = None |
| self.selected_tools = set() |
| self.tool_modifications = {} |
| self.stats = {"total": 0, "successful": 0} |
| logger.info(f"🚀 ComputeAgent UI initialized with tool approval support (API: {api_base_url})") |
| |
| def update_gpu_options(self, location: str): |
| """Update GPU dropdown based on location.""" |
| gpus = LOCATION_GPU_MAP.get(location, []) |
| return gr.update(choices=gpus, value=gpus[0] if gpus else None) |
| |
| def get_stats_display(self) -> str: |
| """Format stats display.""" |
| success_rate = (self.stats["successful"] / max(1, self.stats["total"])) * 100 |
| return f"""**📊 Session Statistics** |
| Total Requests: {self.stats["total"]} |
| Success Rate: {success_rate:.1f}%""" |
| |
| async def process_query( |
| self, |
| message: str, |
| history: List, |
| user_id: str, |
| session_id: str |
| ): |
| """Process query through FastAPI.""" |
| |
| if not message.strip(): |
| yield ( |
| history, "", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| return |
| |
| user_id = user_id.strip() or "demo_user" |
| session_id = session_id.strip() or f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}" |
| |
| |
| history.append([message, "🤖 **Processing...**"]) |
| yield ( |
| history, "", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| |
| try: |
| |
| result = await self.client.send_query(message, user_id, session_id) |
| |
| if not result.get("success"): |
| error_msg = f"❌ **Error:** {result.get('error', 'Unknown error')}" |
| history[-1][1] = error_msg |
| yield ( |
| history, "", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| return |
| |
| |
| if result.get("state") == "waiting_for_input": |
| self.current_thread_id = result.get("thread_id") |
| self.current_interrupt_data = result.get("interrupt_data", {}) |
| |
| |
| if "tool_calls" in self.current_interrupt_data: |
| |
| self.approval_type = "tool" |
| formatted_response = self._format_tool_approval(self.current_interrupt_data) |
| history[-1][1] = formatted_response |
| |
| yield ( |
| history, "", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=True), |
| self.get_stats_display() |
| ) |
| else: |
| |
| self.approval_type = "capacity" |
| formatted_response = self.current_interrupt_data.get( |
| "formatted_response", |
| self._format_basic_capacity(self.current_interrupt_data) |
| ) |
| history[-1][1] = formatted_response |
| |
| yield ( |
| history, "", |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| return |
| |
| |
| response_text = result.get("response", "Request completed") |
| history[-1][1] = response_text |
| |
| self.stats["total"] += 1 |
| self.stats["successful"] += 1 |
| |
| yield ( |
| history, "", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| |
| except Exception as e: |
| logger.error(f"❌ Error: {e}", exc_info=True) |
| history[-1][1] = f"❌ **Error:** {str(e)}" |
| yield ( |
| history, "", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| |
| def _format_tool_approval(self, interrupt_data: Dict[str, Any]) -> str: |
| """Format tool approval request for display.""" |
| tool_calls = interrupt_data.get("tool_calls", []) |
| query = interrupt_data.get("query", "") |
| |
| if not tool_calls: |
| return "⚠️ No tools proposed" |
| |
| tools_list = [] |
| for i, tool in enumerate(tool_calls): |
| tool_name = tool.get("name", "unknown") |
| tool_args = json.dumps(tool.get("args", {}), indent=2) |
| tool_desc = tool.get("description", "No description") |
| |
| tools_list.append(f""" |
| **Tool {i+1}: {tool_name}** |
| - Description: {tool_desc} |
| - Arguments: |
| ```json |
| {tool_args} |
| ``` |
| """) |
| |
| tools_text = "\n".join(tools_list) |
| |
| return f"""# 🔧 **Tool Approval Required** |
| |
| **Query:** {query} |
| |
| **Proposed Tools ({len(tool_calls)}):** |
| |
| {tools_text} |
| |
| ⚠️ **Please review and approve, modify, or request re-reasoning.** |
| """ |
| |
| def _format_basic_capacity(self, interrupt_data: Dict[str, Any]) -> str: |
| """Basic capacity formatting if formatted_response not available.""" |
| model_name = interrupt_data.get("model_name", "Unknown") |
| memory = interrupt_data.get("estimated_gpu_memory", 0) |
| gpu_reqs = interrupt_data.get("gpu_requirements", {}) |
| |
| gpu_lines = [f" • **{gpu}:** {count} GPU{'s' if count > 1 else ''}" |
| for gpu, count in gpu_reqs.items()] |
| gpu_text = "\n".join(gpu_lines) if gpu_lines else " • No requirements" |
| |
| return f"""# 📊 **Capacity Estimation** |
| |
| **Model:** `{model_name}` |
| **Estimated GPU Memory:** **{memory:.2f} GB** |
| |
| **GPU Requirements:** |
| {gpu_text} |
| |
| ⚠️ **Please review and approve or modify the configuration.** |
| """ |
| |
| def build_tool_checkboxes(self): |
| """Build checkbox UI for tool selection.""" |
| if not self.current_interrupt_data or "tool_calls" not in self.current_interrupt_data: |
| return [] |
| |
| tool_calls = self.current_interrupt_data.get("tool_calls", []) |
| |
| |
| return [ |
| f"[{i}] {tool.get('name', 'unknown')}: {json.dumps(tool.get('args', {}))}" |
| for i, tool in enumerate(tool_calls) |
| ] |
| |
| |
| |
| |
| |
| async def approve_capacity(self, history: List, user_id: str, session_id: str): |
| """Handle capacity approval.""" |
| if not self.current_thread_id or self.approval_type != "capacity": |
| history.append([None, "⚠️ No pending capacity approval"]) |
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| return |
|
|
| history.append(["✅ **Approved Capacity**", "🚀 **Continuing deployment...**"]) |
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
|
|
| try: |
| approval_input = { |
| "capacity_approved": True, |
| "custom_config": {}, |
| "needs_re_estimation": False |
| } |
|
|
| result = await self.client.continue_execution( |
| self.current_thread_id, |
| approval_input |
| ) |
|
|
| |
| if result.get("state") == "waiting_for_input": |
| self.current_interrupt_data = result.get("interrupt_data", {}) |
|
|
| |
| if "tool_calls" in self.current_interrupt_data: |
| |
| self.approval_type = "tool" |
| formatted_response = self._format_tool_approval(self.current_interrupt_data) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=True), |
| self.get_stats_display() |
| ) |
| else: |
| |
| self.approval_type = "capacity" |
| formatted_response = self.current_interrupt_data.get( |
| "formatted_response", |
| self._format_basic_capacity(self.current_interrupt_data) |
| ) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| return |
|
|
| |
| if result.get("success"): |
| response = result.get("response", "Deployment completed") |
| history[-1][1] = f"✅ **{response}**" |
| self.stats["total"] += 1 |
| self.stats["successful"] += 1 |
| else: |
| history[-1][1] = f"❌ **Error:** {result.get('error', 'Unknown error')}" |
|
|
| self._clear_approval_state() |
|
|
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
|
|
| except Exception as e: |
| logger.error(f"❌ Approval error: {e}") |
| history[-1][1] = f"❌ **Error:** {str(e)}" |
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| |
| async def reject_capacity(self, history: List, user_id: str, session_id: str): |
| """Handle capacity rejection.""" |
| if not self.current_thread_id or self.approval_type != "capacity": |
| return self._no_approval_response(history) |
| |
| history.append(["❌ **Rejected Capacity**", "Deployment cancelled"]) |
| |
| rejection_input = { |
| "capacity_approved": False, |
| "custom_config": {}, |
| "needs_re_estimation": False |
| } |
| |
| await self.client.continue_execution(self.current_thread_id, rejection_input) |
| self._clear_approval_state() |
| |
| return ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| |
| async def apply_capacity_modifications( |
| self, |
| history: List, |
| user_id: str, |
| session_id: str, |
| max_model_len: int, |
| max_num_seqs: int, |
| max_batched_tokens: int, |
| kv_cache_dtype: str, |
| gpu_util: float, |
| location: str, |
| gpu_type: str |
| ): |
| """Apply capacity modifications and re-estimate.""" |
| if not self.current_thread_id or self.approval_type != "capacity": |
| history.append([None, "⚠️ No pending capacity approval"]) |
| yield self._all_hidden_response(history) |
| return |
| |
| history.append(["🔧 **Re-estimating with new parameters...**", "⏳ **Please wait...**"]) |
| yield self._all_hidden_response(history) |
| |
| try: |
| custom_config = { |
| "GPU_type": gpu_type, |
| "location": location, |
| "max_model_len": int(max_model_len), |
| "max_num_seqs": int(max_num_seqs), |
| "max_num_batched_tokens": int(max_batched_tokens), |
| "kv_cache_dtype": kv_cache_dtype, |
| "gpu_memory_utilization": float(gpu_util) |
| } |
| |
| re_estimate_input = { |
| "capacity_approved": None, |
| "custom_config": custom_config, |
| "needs_re_estimation": True |
| } |
| |
| result = await self.client.continue_execution( |
| self.current_thread_id, |
| re_estimate_input |
| ) |
|
|
| |
| if result.get("state") == "waiting_for_input": |
| self.current_interrupt_data = result.get("interrupt_data", {}) |
|
|
| |
| if "tool_calls" in self.current_interrupt_data: |
| |
| self.approval_type = "tool" |
| formatted_response = self._format_tool_approval(self.current_interrupt_data) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=True), |
| self.get_stats_display() |
| ) |
| else: |
| |
| self.approval_type = "capacity" |
| formatted_response = self.current_interrupt_data.get( |
| "formatted_response", |
| self._format_basic_capacity(self.current_interrupt_data) |
| ) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| else: |
| |
| response = result.get("response", "Re-estimation completed") |
| history[-1][1] = f"✅ **{response}**" |
| self._clear_approval_state() |
| yield self._all_hidden_response(history) |
| |
| except Exception as e: |
| logger.error(f"❌ Re-estimation error: {e}") |
| history[-1][1] = f"❌ **Error:** {str(e)}" |
| yield self._all_hidden_response(history) |
| |
| |
| |
| |
| |
| async def approve_all_tools(self, history: List, user_id: str, session_id: str): |
| """Approve all tools.""" |
| if not self.current_thread_id or self.approval_type != "tool": |
| history.append([None, "⚠️ No pending tool approval"]) |
| yield self._all_hidden_response(history) |
| return |
|
|
| history.append(["✅ **Approved All Tools**", "⚡ **Executing tools...**"]) |
| yield self._all_hidden_response(history) |
|
|
| try: |
| result = await self.client.approve_tools( |
| self.current_thread_id, |
| {"action": "approve_all"} |
| ) |
|
|
| |
| if result.get("state") == "waiting_for_input": |
| self.current_interrupt_data = result.get("interrupt_data", {}) |
|
|
| |
| if "tool_calls" in self.current_interrupt_data: |
| |
| self.approval_type = "tool" |
| formatted_response = self._format_tool_approval(self.current_interrupt_data) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=True), |
| self.get_stats_display() |
| ) |
| else: |
| |
| self.approval_type = "capacity" |
| formatted_response = self.current_interrupt_data.get( |
| "formatted_response", |
| self._format_basic_capacity(self.current_interrupt_data) |
| ) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| return |
|
|
| |
| if result.get("success"): |
| response = result.get("response", "Tools executed successfully") |
| history[-1][1] = f"✅ **{response}**" |
| self.stats["total"] += 1 |
| self.stats["successful"] += 1 |
| else: |
| history[-1][1] = f"❌ **Error:** {result.get('error', 'Unknown error')}" |
|
|
| self._clear_approval_state() |
| yield self._all_hidden_response(history) |
|
|
| except Exception as e: |
| logger.error(f"❌ Tool approval error: {e}") |
| history[-1][1] = f"❌ **Error:** {str(e)}" |
| yield self._all_hidden_response(history) |
| |
| async def reject_all_tools(self, history: List, user_id: str, session_id: str): |
| """Reject all tools.""" |
| if not self.current_thread_id or self.approval_type != "tool": |
| return self._no_approval_response(history) |
| |
| history.append(["❌ **Rejected All Tools**", "Generating response without tools..."]) |
| |
| try: |
| result = await self.client.approve_tools( |
| self.current_thread_id, |
| {"action": "reject_all"} |
| ) |
| |
| if result.get("success"): |
| response = result.get("response", "Completed without tools") |
| history[-1][1] = f"✅ **{response}**" |
| else: |
| history[-1][1] = f"❌ **Error:** {result.get('error', 'Unknown error')}" |
| |
| self._clear_approval_state() |
| |
| except Exception as e: |
| logger.error(f"❌ Tool rejection error: {e}") |
| history[-1][1] = f"❌ **Error:** {str(e)}" |
| |
| return self._all_hidden_response(history) |
| |
| async def approve_selected_tools( |
| self, |
| history: List, |
| user_id: str, |
| session_id: str, |
| selected_indices: str |
| ): |
| """Approve selected tools by indices.""" |
| if not self.current_thread_id or self.approval_type != "tool": |
| history.append([None, "⚠️ No pending tool approval"]) |
| yield self._all_hidden_response(history) |
| return |
|
|
| |
| try: |
| |
| indices = [int(i.strip()) - 1 for i in selected_indices.split(",") if i.strip()] |
| |
| if any(idx < 0 for idx in indices): |
| history.append([None, "❌ Tool indices must be positive numbers (starting from 1). Example: 1,2,3"]) |
| yield self._all_hidden_response(history) |
| return |
| except: |
| history.append([None, "❌ Invalid indices format. Use: 1,2,3 (starting from 1)"]) |
| yield self._all_hidden_response(history) |
| return |
|
|
| history.append([ |
| f"✅ **Approved Tools: {indices}**", |
| "⚡ **Executing selected tools...**" |
| ]) |
| yield self._all_hidden_response(history) |
|
|
| try: |
| result = await self.client.approve_tools( |
| self.current_thread_id, |
| { |
| "action": "approve_selected", |
| "tool_indices": indices |
| } |
| ) |
|
|
| |
| if result.get("state") == "waiting_for_input": |
| self.current_interrupt_data = result.get("interrupt_data", {}) |
|
|
| if "tool_calls" in self.current_interrupt_data: |
| self.approval_type = "tool" |
| formatted_response = self._format_tool_approval(self.current_interrupt_data) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=True), |
| self.get_stats_display() |
| ) |
| else: |
| self.approval_type = "capacity" |
| formatted_response = self.current_interrupt_data.get( |
| "formatted_response", |
| self._format_basic_capacity(self.current_interrupt_data) |
| ) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| return |
|
|
| |
| if result.get("success"): |
| response = result.get("response", "Selected tools executed") |
| history[-1][1] = f"✅ **{response}**" |
| self.stats["total"] += 1 |
| self.stats["successful"] += 1 |
| else: |
| history[-1][1] = f"❌ **Error:** {result.get('error', 'Unknown error')}" |
|
|
| self._clear_approval_state() |
| yield self._all_hidden_response(history) |
|
|
| except Exception as e: |
| logger.error(f"❌ Tool approval error: {e}") |
| history[-1][1] = f"❌ **Error:** {str(e)}" |
| yield self._all_hidden_response(history) |
| |
| async def request_re_reasoning( |
| self, |
| history: List, |
| user_id: str, |
| session_id: str, |
| feedback: str |
| ): |
| """Request agent re-reasoning with feedback.""" |
| if not self.current_thread_id or self.approval_type != "tool": |
| history.append([None, "⚠️ No pending tool approval"]) |
| yield self._all_hidden_response(history) |
| return |
| |
| if not feedback.strip(): |
| history.append([None, "❌ Please provide feedback for re-reasoning"]) |
| yield self._all_hidden_response(history) |
| return |
| |
| history.append([ |
| f"🔄 **Re-reasoning Request:** {feedback}", |
| "🤔 **Agent reconsidering approach...**" |
| ]) |
| yield self._all_hidden_response(history) |
| |
| try: |
| result = await self.client.approve_tools( |
| self.current_thread_id, |
| { |
| "action": "request_re_reasoning", |
| "feedback": feedback |
| } |
| ) |
| |
| |
| if result.get("state") == "waiting_for_input": |
| self.current_interrupt_data = result.get("interrupt_data", {}) |
| formatted_response = self._format_tool_approval(self.current_interrupt_data) |
| history[-1][1] = formatted_response |
| |
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=True), |
| self.get_stats_display() |
| ) |
| else: |
| response = result.get("response", "Re-reasoning completed") |
| history[-1][1] = f"✅ **{response}**" |
| self._clear_approval_state() |
| yield self._all_hidden_response(history) |
| |
| except Exception as e: |
| logger.error(f"❌ Re-reasoning error: {e}") |
| history[-1][1] = f"❌ **Error:** {str(e)}" |
| yield self._all_hidden_response(history) |
| |
| async def modify_tool_args( |
| self, |
| history: List, |
| user_id: str, |
| session_id: str, |
| tool_index: int, |
| new_args_json: str |
| ): |
| """Modify tool arguments and approve.""" |
| if not self.current_thread_id or self.approval_type != "tool": |
| history.append([None, "⚠️ No pending tool approval"]) |
| yield self._all_hidden_response(history) |
| return |
|
|
| |
| try: |
| new_args = json.loads(new_args_json) |
| except: |
| history.append([None, "❌ Invalid JSON format for arguments"]) |
| yield self._all_hidden_response(history) |
| return |
|
|
| history.append([ |
| f"🔧 **Modified Tool {tool_index}**", |
| "⚡ **Executing with new arguments...**" |
| ]) |
| yield self._all_hidden_response(history) |
|
|
| |
| backend_index = tool_index - 1 |
| if backend_index < 0: |
| history.append([None, "❌ Tool index must be positive (starting from 1)"]) |
| yield self._all_hidden_response(history) |
| return |
|
|
| try: |
| result = await self.client.approve_tools( |
| self.current_thread_id, |
| { |
| "action": "modify_and_approve", |
| "modifications": [ |
| { |
| "tool_index": backend_index, |
| "new_args": new_args, |
| "approve": True |
| } |
| ] |
| } |
| ) |
|
|
| |
| if result.get("state") == "waiting_for_input": |
| self.current_interrupt_data = result.get("interrupt_data", {}) |
|
|
| if "tool_calls" in self.current_interrupt_data: |
| self.approval_type = "tool" |
| formatted_response = self._format_tool_approval(self.current_interrupt_data) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=True), |
| self.get_stats_display() |
| ) |
| else: |
| self.approval_type = "capacity" |
| formatted_response = self.current_interrupt_data.get( |
| "formatted_response", |
| self._format_basic_capacity(self.current_interrupt_data) |
| ) |
| history[-1][1] = formatted_response |
|
|
| yield ( |
| history, |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| return |
|
|
| |
| if result.get("success"): |
| response = result.get("response", "Tool executed with modifications") |
| history[-1][1] = f"✅ **{response}**" |
| self.stats["total"] += 1 |
| self.stats["successful"] += 1 |
| else: |
| history[-1][1] = f"❌ **Error:** {result.get('error', 'Unknown error')}" |
|
|
| self._clear_approval_state() |
| yield self._all_hidden_response(history) |
|
|
| except Exception as e: |
| logger.error(f"❌ Modification error: {e}") |
| history[-1][1] = f"❌ **Error:** {str(e)}" |
| yield self._all_hidden_response(history) |
| |
| |
| |
| |
| |
| def _clear_approval_state(self): |
| """Clear all approval state.""" |
| self.current_thread_id = None |
| self.current_interrupt_data = None |
| self.approval_type = None |
| self.selected_tools = set() |
| self.tool_modifications = {} |
| |
| def _all_hidden_response(self, history): |
| """Return response with all panels hidden.""" |
| return ( |
| history, |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| |
| def _no_approval_response(self, history): |
| """Return response for no pending approval.""" |
| history.append([None, "⚠️ No pending approval"]) |
| return self._all_hidden_response(history) |
| |
| def show_capacity_modify_dialog(self): |
| """Show capacity parameter modification dialog.""" |
| if not self.current_interrupt_data: |
| return ( |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| 2048, 256, 2048, "auto", 0.9, "France", "RTX 4090" |
| ) |
| |
| model_info = self.current_interrupt_data.get("model_info", {}) |
| |
| return ( |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| model_info.get("max_model_len", 2048), |
| model_info.get("max_num_seqs", 256), |
| model_info.get("max_num_batched_tokens", 2048), |
| model_info.get("kv_cache_dtype", "auto"), |
| model_info.get("gpu_memory_utilization", 0.9), |
| model_info.get("location", "France"), |
| model_info.get("GPU_type", "RTX 4090") |
| ) |
| |
| def cancel_capacity_modify(self): |
| """Cancel capacity modification.""" |
| return ( |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False) |
| ) |
| |
| def clear_chat(self, user_id: str, session_id: str): |
| """Clear chat history.""" |
| self._clear_approval_state() |
| return ( |
| [], |
| "", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| gr.update(visible=False), |
| self.get_stats_display() |
| ) |
| |
| def new_session(self, user_id: str): |
| """Generate new session ID.""" |
| new_session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}" |
| self._clear_approval_state() |
| return new_session_id |
|
|
|
|
| |
| def create_theme(): |
| return gr.themes.Soft( |
| primary_hue="orange", |
| secondary_hue="stone", |
| neutral_hue="slate", |
| font=gr.themes.GoogleFont("Inter") |
| ).set( |
| body_background_fill="#1a1a1a", |
| body_background_fill_dark="#0d0d0d", |
| button_primary_background_fill="#d97706", |
| button_primary_background_fill_hover="#ea580c", |
| button_primary_text_color="#ffffff", |
| block_background_fill="#262626", |
| block_border_color="#404040", |
| input_background_fill="#1f1f1f", |
| slider_color="#d97706", |
| ) |
|
|
|
|
| def create_gradio_demo(api_base_url: str = "http://localhost:8000"): |
| """ |
| Create and return the Gradio demo interface. |
| |
| Args: |
| api_base_url: Base URL for the FastAPI backend |
| |
| Returns: |
| Gradio Blocks demo |
| """ |
| |
| agent_interface = ComputeAgentInterface(api_base_url) |
|
|
| |
| with gr.Blocks( |
| title="ComputeAgent - Enhanced with Tool Approval", |
| theme=create_theme(), |
| css=""" |
| .gradio-container { |
| max-width: 100% !important; |
| } |
| .header-box { |
| background: linear-gradient(135deg, #d97706 0%, #ea580c 50%, #dc2626 100%); |
| color: white; |
| padding: 20px; |
| border-radius: 10px; |
| margin-bottom: 20px; |
| position: relative; |
| overflow: hidden; |
| } |
| .header-box::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 600'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' /%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.05' /%3E%3C/svg%3E"); |
| pointer-events: none; |
| } |
| .tool-box { |
| background: rgba(41, 37, 36, 0.5); |
| border: 2px solid #57534e; |
| border-radius: 8px; |
| padding: 15px; |
| margin: 10px 0; |
| } |
| /* Make chatbot fill available height dynamically */ |
| .chatbot { |
| height: calc(100vh - 750px) !important; |
| max-height: calc(100vh - 750px) !important; |
| } |
| /* Make input text white */ |
| textarea, input[type="text"], input[type="number"], .input-field { |
| color: white !important; |
| } |
| /* Make labels white */ |
| label { |
| color: white !important; |
| } |
| /* Make number input values white */ |
| input[type="number"] { |
| color: white !important; |
| } |
| /* Make dropdown values white - comprehensive */ |
| select, option { |
| color: white !important; |
| background-color: #1f1f1f !important; |
| } |
| /* Gradio-specific dropdown styling */ |
| .dropdown, .dropdown-content, .dropdown-item { |
| color: white !important; |
| } |
| /* Make sure all input elements show white text */ |
| input, select, textarea { |
| color: white !important; |
| } |
| """ |
| ) as demo: |
| |
| |
| hivenet_logo_html = f'<img src="data:image/jpeg;base64,{HIVENET_LOGO_BASE64}" alt="HiveNet Logo" style="height: 80px; width: auto; object-fit: contain;">' if HIVENET_LOGO_BASE64 else '' |
| computeagent_logo_html = f'<img src="data:image/png;base64,{COMPUTEAGENT_LOGO_BASE64}" alt="ComputeAgent Logo" style="height: 60px; width: auto; object-fit: contain; margin-right: 15px;">' if COMPUTEAGENT_LOGO_BASE64 else '' |
|
|
| gr.HTML(f""" |
| <div class="header-box" style="display: flex; justify-content: space-between; align-items: center;"> |
| <div style="display: flex; align-items: center;"> |
| {computeagent_logo_html} |
| <div> |
| <h1 style="margin: 0; font-size: 2.5em;">ComputeAgent</h1> |
| <p style="margin: 10px 0 0 0; opacity: 0.9;"> |
| Hivenet AI-Powered Deployment using MCP of Compute by Hivenet |
| </p> |
| </div> |
| </div> |
| <div> |
| {hivenet_logo_html} |
| </div> |
| </div> |
| """) |
| |
| with gr.Row(): |
| with gr.Column(scale=11): |
| |
| chatbot = gr.Chatbot( |
| label="Agent Conversation", |
| height=900, |
| show_copy_button=True, |
| elem_classes=["chatbot"] |
| ) |
| |
| with gr.Row(): |
| msg = gr.Textbox( |
| placeholder="Deploy meta-llama/Llama-3.1-70B or ask: What are the latest AI developments?", |
| scale=5, |
| show_label=False |
| ) |
| send_btn = gr.Button("🚀 Send", variant="primary", scale=1) |
| |
| |
| |
| |
| with gr.Row(visible=False) as capacity_approval_panel: |
| capacity_approve_btn = gr.Button("✅ Approve", variant="primary", scale=1) |
| capacity_modify_btn = gr.Button("🔧 Modify", variant="secondary", scale=1) |
| capacity_reject_btn = gr.Button("❌ Reject", variant="stop", scale=1) |
| |
| |
| with gr.Column(visible=False) as capacity_param_panel: |
| gr.Markdown('<h2 style="color: white;">🔧 Capacity Parameter Optimization</h2>') |
| |
| with gr.Row(): |
| with gr.Column(): |
| max_model_len = gr.Number( |
| label="Context Length", |
| value=2048, |
| minimum=1 |
| ) |
| max_num_seqs = gr.Number( |
| label="Max Sequences", |
| value=256, |
| minimum=1 |
| ) |
| |
| with gr.Column(): |
| max_batched_tokens = gr.Number( |
| label="Batch Size", |
| value=2048, |
| minimum=1 |
| ) |
| kv_cache_dtype = gr.Dropdown( |
| choices=["auto", "float32", "float16", "bfloat16", "fp8"], |
| value="auto", |
| label="KV Cache Type" |
| ) |
| |
| with gr.Column(): |
| gpu_util = gr.Slider( |
| minimum=0.1, |
| maximum=1.0, |
| value=0.9, |
| step=0.05, |
| label="GPU Utilization" |
| ) |
| location = gr.Dropdown( |
| choices=list(LOCATION_GPU_MAP.keys()), |
| value="France", |
| label="Location" |
| ) |
| gpu_type = gr.Dropdown( |
| choices=LOCATION_GPU_MAP["France"], |
| value="RTX 4090", |
| label="GPU Type" |
| ) |
| |
| with gr.Row(): |
| capacity_apply_btn = gr.Button("🔄 Re-estimate", variant="primary", scale=2) |
| capacity_cancel_btn = gr.Button("↩️ Back", variant="secondary", scale=1) |
| |
| |
| |
| |
| with gr.Row(visible=False) as tool_approval_panel: |
| tool_approve_all_btn = gr.Button("✅ Approve All", variant="primary", scale=1) |
| tool_reject_all_btn = gr.Button("❌ Reject All", variant="stop", scale=1) |
| |
| with gr.Column(visible=False) as tool_list_panel: |
| gr.Markdown("### 🔧 Tool Actions") |
| |
| with gr.Tab("Selective Approval"): |
| tool_indices_input = gr.Textbox( |
| label="Tool Indices to Approve (comma-separated)", |
| placeholder="1,2,3", |
| info="Enter indices of tools to approve (e.g., '1,3' to approve Tool 1 and Tool 3)" |
| ) |
| tool_approve_selected_btn = gr.Button("✅ Approve Selected", variant="primary") |
| |
| with gr.Tab("Modify Arguments"): |
| tool_index_input = gr.Number( |
| label="Tool Index", |
| value=1, |
| minimum=1, |
| precision=0, |
| info="Enter tool number (e.g., 1 for Tool 1)" |
| ) |
| tool_args_input = gr.TextArea( |
| label="New Arguments (JSON)", |
| placeholder='{"query": "modified search query"}', |
| lines=5 |
| ) |
| tool_modify_btn = gr.Button("🔧 Modify & Approve", variant="secondary") |
| |
| with gr.Tab("Re-Reasoning"): |
| feedback_input = gr.TextArea( |
| label="Feedback for Agent", |
| placeholder="Please search for academic papers instead of news articles...", |
| lines=4 |
| ) |
| re_reasoning_btn = gr.Button("🔄 Request Re-Reasoning", variant="secondary") |
| |
| |
| with gr.Column(scale=1): |
| gr.Markdown('<h2 style="color: white;">Control Panel</h2>') |
|
|
| with gr.Group(): |
| gr.Markdown('<h3 style="color: white;">User Session</h3>') |
| user_id = gr.Textbox( |
| label="User ID", |
| value="demo_user" |
| ) |
| session_id = gr.Textbox( |
| label="Session ID", |
| value=f"session_{datetime.now().strftime('%m%d_%H%M')}" |
| ) |
|
|
| with gr.Group(): |
| stats_display = gr.Markdown('<h3 style="color: white;">Statistics</h3><p style="color: white;">No requests yet</p>') |
|
|
| with gr.Group(): |
| gr.Markdown('<h3 style="color: white;">Management</h3>') |
| clear_btn = gr.Button("Clear History", variant="secondary") |
| new_session_btn = gr.Button("New Session", variant="secondary") |
|
|
| gr.Markdown(""" |
| <h2 style="color: white;">Examples</h2> |
| |
| <p style="color: white;"><strong style="color: white;">Model Deployment:</strong></p> |
| <ul> |
| <li style="color: white;">Deploy meta-llama/Llama-3.1-8B</li> |
| <li style="color: white;">Deploy openai/gpt-oss-20b</li> |
| </ul> |
| |
| <p style="color: white;"><strong style="color: white;">Tool Usage:</strong></p> |
| <ul> |
| <li style="color: white;">Search for latest AI developments</li> |
| <li style="color: white;">Calculate 25 * 34 + 128</li> |
| <li style="color: white;">What's the weather in Paris?</li> |
| </ul> |
| """) |
| |
| |
| location.change( |
| fn=agent_interface.update_gpu_options, |
| inputs=[location], |
| outputs=[gpu_type] |
| ) |
| |
| |
| |
| |
| |
| |
| msg.submit( |
| agent_interface.process_query, |
| inputs=[msg, chatbot, user_id, session_id], |
| outputs=[chatbot, msg, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| send_btn.click( |
| agent_interface.process_query, |
| inputs=[msg, chatbot, user_id, session_id], |
| outputs=[chatbot, msg, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| |
| capacity_approve_btn.click( |
| agent_interface.approve_capacity, |
| inputs=[chatbot, user_id, session_id], |
| outputs=[chatbot, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| capacity_reject_btn.click( |
| agent_interface.reject_capacity, |
| inputs=[chatbot, user_id, session_id], |
| outputs=[chatbot, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| capacity_modify_btn.click( |
| agent_interface.show_capacity_modify_dialog, |
| outputs=[capacity_approval_panel, capacity_param_panel, tool_approval_panel, |
| tool_list_panel, max_model_len, max_num_seqs, max_batched_tokens, |
| kv_cache_dtype, gpu_util, location, gpu_type] |
| ) |
| |
| capacity_cancel_btn.click( |
| agent_interface.cancel_capacity_modify, |
| outputs=[capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel] |
| ) |
| |
| capacity_apply_btn.click( |
| agent_interface.apply_capacity_modifications, |
| inputs=[chatbot, user_id, session_id, max_model_len, max_num_seqs, |
| max_batched_tokens, kv_cache_dtype, gpu_util, location, gpu_type], |
| outputs=[chatbot, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| |
| tool_approve_all_btn.click( |
| agent_interface.approve_all_tools, |
| inputs=[chatbot, user_id, session_id], |
| outputs=[chatbot, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| tool_reject_all_btn.click( |
| agent_interface.reject_all_tools, |
| inputs=[chatbot, user_id, session_id], |
| outputs=[chatbot, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| tool_approve_selected_btn.click( |
| agent_interface.approve_selected_tools, |
| inputs=[chatbot, user_id, session_id, tool_indices_input], |
| outputs=[chatbot, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| tool_modify_btn.click( |
| agent_interface.modify_tool_args, |
| inputs=[chatbot, user_id, session_id, tool_index_input, tool_args_input], |
| outputs=[chatbot, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| re_reasoning_btn.click( |
| agent_interface.request_re_reasoning, |
| inputs=[chatbot, user_id, session_id, feedback_input], |
| outputs=[chatbot, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| |
| clear_btn.click( |
| agent_interface.clear_chat, |
| inputs=[user_id, session_id], |
| outputs=[chatbot, msg, capacity_approval_panel, capacity_param_panel, |
| tool_approval_panel, tool_list_panel, stats_display] |
| ) |
| |
| new_session_btn.click( |
| agent_interface.new_session, |
| inputs=[user_id], |
| outputs=[session_id] |
| ) |
|
|
| return demo |