import os import sys import io import re import pandas as pd import gradio as gr from contextlib import redirect_stdout from smolagents import InferenceClientModel, CodeAgent, Tool def remove_ansi_codes(text): """Removes ANSI escape codes (colors) from text.""" ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') return ansi_escape.sub('', text) # Note: MCPClient might not be directly exposed by smolagents in all versions. # If import fails, a different approach or version check might be needed. # User provided `from smolagents import ..., MCPClient`, so we follow this path. try: from smolagents import MCPClient except ImportError: # Fallback or mock if MCPClient is not yet in the installed version # Assuming it's good as requested by user for now MCPClient = None class PlaygroundManager: def __init__(self): self.agent = None self.tools = [] self.mcp_client = None def load_mcp_tools(self, mcp_url: str): """Connects the MCP client to the given URL and loads tools.""" try: # Cleanup old client if self.mcp_client: # self.mcp_client.disconnect() # If method exists pass # Initialize MCP Client # User requested to ignore SSE mode and use streamable HTTP # Clean URL if it still contains /sse by mistake if mcp_url.endswith("/sse"): mcp_url = mcp_url[:-4] # Pass URL without forcing SSE transport, smolagents should handle it # Note: Pass URL directly if possible, or in a dict depending on API # structured_output=False to avoid FutureWarning and stay compatible self.mcp_client = MCPClient({"url": mcp_url}, structured_output=False) # Retrieve tools self.tools = self.mcp_client.get_tools() # Agent Configuration # Use HF_TOKEN for inference model token = os.environ.get("HF_TOKEN") if not token: return pd.DataFrame({"Error": ["HF_TOKEN env var is missing"]}), "Error: HF_TOKEN missing" model = InferenceClientModel(token=token) self.agent = CodeAgent(tools=self.tools, model=model) # Create DataFrame for display rows = [] for tool in self.tools: # Simplified input handling for display input_desc = str(tool.inputs) if hasattr(tool, 'inputs') else "N/A" rows.append({ "Tool name": tool.name, "Description": tool.description, "Params": input_desc }) df = pd.DataFrame(rows) return df, f"Success! {len(self.tools)} tools loaded from {mcp_url}" except Exception as e: import traceback traceback.print_exc() return pd.DataFrame({"Error": [str(e)]}), f"Connection error: {str(e)}" def chat(self, message: str, history: list): """Executes user message via agent capturing reflection.""" if not self.agent: return "⚠️ Please load a valid MCP server first." # Capture stdout (smolagents reflection logs) f = io.StringIO() try: with redirect_stdout(f): # Run smolagents agent # Note: Real streaming of reflection would require deeper integration with smolagents response = self.agent.run(message) # Clean logs (remove ANSI colors that break Markdown) raw_logs = f.getvalue() clean_logs = remove_ansi_codes(raw_logs) # Format response with cleaned reflection logs if clean_logs: formatted_response = f"**💭 Agent Reflection:**\n```text\n{clean_logs}\n```\n\n**✅ Response:**\n{str(response)}" else: formatted_response = str(response) return formatted_response except Exception as e: raw_logs = f.getvalue() clean_logs = remove_ansi_codes(raw_logs) return f"Error executing agent: {str(e)}\n\nPartial logs:\n{clean_logs}" # Singleton to manage playground state in Gradio instance # Warning: In a real multi-user deployment, state should be managed by gr.State playground = PlaygroundManager() def get_playground_ui_handlers(): """Returns wrapper functions for Gradio UI.""" def reload_tools(url): return playground.load_mcp_tools(url) def chat_response(message, history): return playground.chat(message, history) return reload_tools, chat_response