uuidenny commited on
Commit
7440a6f
·
verified ·
1 Parent(s): b57a027

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +13 -577
app.py CHANGED
@@ -1,590 +1,26 @@
1
- #!/usr/bin/env python3
2
- """
3
- Improved Gradio Interface with MCP Client and SmolAgents
4
- """
5
-
6
  import gradio as gr
7
  import os
8
- import asyncio
9
- import logging
10
- from typing import Optional
11
- from concurrent.futures import ThreadPoolExecutor
12
- from pathlib import Path
13
 
14
- # MCP and SmolAgents imports
15
  from smolagents import InferenceClientModel, CodeAgent, MCPClient
16
 
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
-
20
- # ============================================================================
21
- # ENVIRONMENT CONFIGURATION
22
- # ============================================================================
23
-
24
- def load_environment_variables():
25
- """
26
- Load environment variables with platform-specific handling.
27
-
28
- For Hugging Face Spaces: Uses os.environ directly
29
- For local development: Loads from .env file with fallbacks
30
- """
31
-
32
- def is_huggingface_space():
33
- """Detect if running on Hugging Face Spaces platform"""
34
- return (
35
- os.getenv("SPACE_ID") is not None or
36
- os.getenv("SPACE_AUTHOR_NAME") is not None or
37
- os.getenv("GRADIO_SERVER_NAME") is not None
38
- )
39
-
40
- def load_dotenv_file():
41
- """Load variables from .env file for local development"""
42
- env_file = Path(".env")
43
- env_vars = {}
44
-
45
- if env_file.exists():
46
- try:
47
- with open(env_file, 'r', encoding='utf-8') as f:
48
- for line in f:
49
- line = line.strip()
50
- if line and not line.startswith('#') and '=' in line:
51
- key, value = line.split('=', 1)
52
- # Remove quotes if present
53
- value = value.strip().strip('"').strip("'")
54
- env_vars[key.strip()] = value
55
- logger.info(f"Loaded {len(env_vars)} variables from .env file")
56
- except Exception as e:
57
- logger.warning(f"Failed to load .env file: {e}")
58
- else:
59
- logger.info("No .env file found, using system environment variables")
60
-
61
- return env_vars
62
-
63
- # Platform detection
64
- on_huggingface = is_huggingface_space()
65
- logger.info(f"Platform detected: {'Hugging Face Spaces' if on_huggingface else 'Local Development'}")
66
-
67
- # Load environment variables
68
- if on_huggingface:
69
- # On Hugging Face Spaces: use environment variables directly
70
- hf_token = os.getenv("HF_TOKEN")
71
- mcp_server_url = os.getenv("MCP_SERVER_URL", "http://localhost:7860/gradio_api/mcp/sse")
72
-
73
- logger.info("Using Hugging Face Spaces environment variables")
74
-
75
- else:
76
- # Local development: try .env file first, then system environment
77
- env_vars = load_dotenv_file()
78
-
79
- # Get HF_TOKEN
80
- hf_token = (
81
- env_vars.get("HF_TOKEN") or
82
- os.getenv("HF_TOKEN")
83
- )
84
-
85
- # Get MCP_SERVER_URL
86
- mcp_server_url = (
87
- env_vars.get("MCP_SERVER_URL") or
88
- os.getenv("MCP_SERVER_URL") or
89
- "http://localhost:7860/gradio_api/mcp/sse" # Default for local development
90
- )
91
-
92
- logger.info("Using local development environment configuration")
93
-
94
- # Validate required variables
95
- if not hf_token:
96
- logger.error("HF_TOKEN not found in environment variables!")
97
- logger.error("Please set HF_TOKEN in:")
98
- if on_huggingface:
99
- logger.error("- Hugging Face Spaces secrets/environment variables")
100
- else:
101
- logger.error("- .env file (HF_TOKEN=your_token_here)")
102
- logger.error("- System environment variables")
103
- raise ValueError("HF_TOKEN is required but not found")
104
-
105
- # Set environment variables for the application
106
- os.environ["HF_TOKEN"] = hf_token
107
-
108
- # Log configuration (without exposing sensitive data)
109
- logger.info(f"HF_TOKEN configured: {'✓' if hf_token else '✗'}")
110
- logger.info(f"MCP_SERVER_URL: {mcp_server_url}")
111
-
112
- return hf_token, mcp_server_url
113
 
114
- # Load environment configuration
115
  try:
116
- HF_TOKEN, MCP_SERVER_URL = load_environment_variables()
117
- except Exception as e:
118
- logger.error(f"Failed to load environment configuration: {e}")
119
- # Fallback values for development/testing
120
- HF_TOKEN = os.getenv("HF_TOKEN", "")
121
- MCP_SERVER_URL = "http://localhost:7860/gradio_api/mcp/sse"
122
-
123
- if not HF_TOKEN:
124
- logger.warning("Running without HF_TOKEN - some features may not work")
125
-
126
- # Global variables
127
- agent: Optional[CodeAgent] = None
128
- tools: Optional[list] = None
129
- mcp_client: Optional[MCPClient] = None
130
- executor = ThreadPoolExecutor(max_workers=4)
131
-
132
- # ============================================================================
133
- # ASYNC UTILITIES
134
- # ============================================================================
135
-
136
- def run_async_in_thread(coro):
137
- """Run async coroutine in a separate thread with its own event loop"""
138
- def run_in_thread():
139
- loop = asyncio.new_event_loop()
140
- asyncio.set_event_loop(loop)
141
- try:
142
- return loop.run_until_complete(coro)
143
- finally:
144
- loop.close()
145
-
146
- future = executor.submit(run_in_thread)
147
- return future.result(timeout=60)
148
-
149
- # ============================================================================
150
- # MCP CLIENT AND AGENT INITIALIZATION
151
- # ============================================================================
152
-
153
- async def _initialize_mcp_client():
154
- """Async helper for MCP client initialization"""
155
- global mcp_client, tools, agent
156
-
157
- logger.info(f"Connecting to MCP server at: {MCP_SERVER_URL}")
158
-
159
- # Create MCP Client connection
160
  mcp_client = MCPClient(
161
- {"url": MCP_SERVER_URL, "transport": "sse"}
162
  )
163
-
164
- # Get available tools from the MCP server
165
- logger.info("Retrieving tools from MCP server...")
166
- all_tools = mcp_client.get_tools()
167
- logger.info(f"Retrieved {len(all_tools)} tools from MCP server")
168
-
169
- # Filter out duplicate tool names
170
- seen_names = set()
171
- tools = []
172
- for tool in all_tools:
173
- tool_name = getattr(tool, 'name', f'tool_{len(tools)}')
174
- if tool_name not in seen_names and not tool_name.startswith('lambda'):
175
- seen_names.add(tool_name)
176
- tools.append(tool)
177
-
178
- logger.info(f"Using {len(tools)} unique tools (filtered out duplicates and lambda functions)")
179
- for i, tool in enumerate(tools, 1):
180
- tool_name = getattr(tool, 'name', f'tool_{i}')
181
- tool_desc = getattr(tool, 'description', 'No description available')
182
- logger.info(f" {i}. {tool_name}: {tool_desc}")
183
-
184
- # Initialize the Hugging Face model
185
- logger.info("Initializing InferenceClientModel...")
186
- try:
187
- # Try with different free models (2024-2025 актуальные)
188
- models_to_try = [
189
- "facebook/blenderbot-400M-distill",
190
- "google/flan-t5-small",
191
- "huggingface/CodeBERTa-small-v1",
192
- "bigscience/bloom-560m"
193
- ]
194
-
195
- model = None
196
- for model_name in models_to_try:
197
- try:
198
- logger.info(f"Trying model: {model_name}")
199
- model = InferenceClientModel(
200
- model=model_name,
201
- token=os.getenv("HF_TOKEN"),
202
- timeout=30
203
- )
204
- logger.info(f"Successfully initialized with {model_name}")
205
- break
206
- except Exception as model_error:
207
- logger.warning(f"Failed with {model_name}: {model_error}")
208
- continue
209
-
210
- if model is None:
211
- # Last resort - try without specifying model
212
- logger.info("Trying default model configuration...")
213
- model = InferenceClientModel(
214
- token=os.getenv("HF_TOKEN"),
215
- timeout=30
216
- )
217
-
218
- except Exception as e:
219
- logger.error(f"All model initialization attempts failed: {e}")
220
- # Create a mock model for testing
221
- logger.info("Creating mock model for testing...")
222
-
223
- class MockModel:
224
- def __init__(self):
225
- self.name = "MockModel"
226
-
227
- async def __call__(self, messages, **kwargs):
228
- # Simple mock response
229
- if messages and len(messages) > 0:
230
- user_message = str(messages[-1]).lower()
231
- if "sentiment" in user_message or "analyze" in user_message:
232
- return "I'll help you analyze sentiment. Please use the analyze_sentiment tool."
233
- elif "health" in user_message:
234
- return "I'll check the system health for you using the health_check tool."
235
- elif "tools" in user_message:
236
- return "Let me show you the available tools using the get_backend_info tool."
237
- else:
238
- return "I can help you with sentiment analysis. What would you like to analyze?"
239
- return "How can I help you with sentiment analysis?"
240
-
241
- model = MockModel()
242
- logger.info("Mock model created successfully")
243
-
244
- # Create the CodeAgent with discovered tools
245
- logger.info("Creating CodeAgent with MCP tools...")
246
- try:
247
- agent = CodeAgent(
248
- tools=[*tools],
249
- model=model,
250
- max_steps=3
251
- )
252
- logger.info("CodeAgent created successfully")
253
- except Exception as e:
254
- logger.error(f"Failed to create CodeAgent: {e}")
255
- # Create a simple agent wrapper
256
- logger.info("Creating simple agent wrapper...")
257
-
258
- class SimpleAgent:
259
- def __init__(self, tools, model):
260
- self.tools = tools
261
- self.model = model
262
- self.name = "SimpleAgent"
263
-
264
- async def run(self, query):
265
- query_lower = query.lower()
266
-
267
- # Direct tool mapping
268
- if "analyze" in query_lower and ":" in query:
269
- text = query.split(":", 1)[1].strip().strip("'\"")
270
- for tool in self.tools:
271
- if getattr(tool, 'name', '') == 'analyze_sentiment':
272
- try:
273
- result = await tool(text=text)
274
- return f"Sentiment Analysis Result: {result}"
275
- except Exception as e:
276
- return f"Error analyzing sentiment: {e}"
277
-
278
- elif "health" in query_lower:
279
- for tool in self.tools:
280
- if 'health' in getattr(tool, 'name', '').lower():
281
- try:
282
- result = await tool()
283
- return f"Health Check: {result}"
284
- except Exception as e:
285
- return f"Error checking health: {e}"
286
-
287
- elif "tools" in query_lower or "available" in query_lower:
288
- tool_list = []
289
- for i, tool in enumerate(self.tools, 1):
290
- tool_name = getattr(tool, 'name', f'tool_{i}')
291
- tool_desc = getattr(tool, 'description', 'No description')
292
- tool_list.append(f"{i}. {tool_name}: {tool_desc}")
293
- return "Available tools:\n" + "\n".join(tool_list)
294
-
295
- elif "batch" in query_lower:
296
- for tool in self.tools:
297
- if 'batch' in getattr(tool, 'name', '').lower():
298
- try:
299
- texts = ["I love this!", "This is terrible", "It's okay"]
300
- result = await tool(texts=texts)
301
- return f"Batch Analysis: {result}"
302
- except Exception as e:
303
- return f"Error in batch analysis: {e}"
304
-
305
- else:
306
- return """I can help you with:
307
-
308
- • Sentiment Analysis: "analyze: [your text]"
309
- • Health Check: "health check"
310
- • List Tools: "what tools are available?"
311
- • Batch Analysis: "run batch analysis"
312
-
313
- Example: "analyze: I love this product!" """
314
-
315
- agent = SimpleAgent(tools, model)
316
- logger.info("Simple agent wrapper created successfully")
317
-
318
- logger.info("MCP client and agent initialized successfully!")
319
- return True
320
-
321
- def initialize_mcp_client():
322
- """Initialize MCP client and connect to the local server"""
323
- try:
324
- success = run_async_in_thread(_initialize_mcp_client())
325
- return success
326
- except Exception as e:
327
- logger.error(f"Failed to initialize MCP client: {str(e)}")
328
- return False
329
-
330
- # ============================================================================
331
- # GRADIO INTERFACE FUNCTIONS
332
- # ============================================================================
333
-
334
- async def _process_question_async(question: str):
335
- """Async helper for processing questions"""
336
- global agent
337
-
338
- if not agent:
339
- return "❌ Agent not initialized. Please check MCP server connection."
340
-
341
- logger.info(f"Processing question: {question}")
342
- response = await agent.run(question)
343
- logger.info("Question processed successfully")
344
- return str(response)
345
-
346
- def process_question(question: str, history: list) -> tuple:
347
- """Process user question using the MCP-enabled agent"""
348
-
349
- if not question.strip():
350
- error_msg = "⚠️ Please enter a question."
351
- history.append({"role": "user", "content": question})
352
- history.append({"role": "assistant", "content": error_msg})
353
- return history, ""
354
-
355
- try:
356
- # Add user question to history with thinking indicator
357
- history.append({"role": "user", "content": question})
358
- history.append({"role": "assistant", "content": "🤔 Thinking..."})
359
-
360
- # Process the question in a separate thread
361
- response = run_async_in_thread(_process_question_async(question))
362
-
363
- # Update history with the response
364
- history[-1]["content"] = f"🤖 {response}"
365
-
366
- except Exception as e:
367
- error_msg = f"❌ Error processing question: {str(e)}"
368
- history[-1]["content"] = error_msg
369
- logger.error(f"Error processing question: {str(e)}")
370
-
371
- return history, ""
372
-
373
- def get_server_status() -> str:
374
- """Get current server connection status"""
375
- global agent, tools, mcp_client
376
-
377
- status_parts = []
378
-
379
- # MCP Client Status
380
- if mcp_client:
381
- status_parts.append("✅ MCP Client: Connected")
382
- else:
383
- status_parts.append("❌ MCP Client: Not connected")
384
-
385
- # Tools Status
386
- if tools:
387
- status_parts.append(f"✅ Tools: {len(tools)} available")
388
- for i, tool in enumerate(tools[:5], 1):
389
- tool_name = getattr(tool, 'name', f'tool_{i}')
390
- tool_desc = getattr(tool, 'description', 'No description')
391
- status_parts.append(f" • {tool_name}: {tool_desc[:50]}...")
392
- if len(tools) > 5:
393
- status_parts.append(f" ... and {len(tools) - 5} more tools")
394
- else:
395
- status_parts.append("❌ Tools: None available")
396
-
397
- # Agent Status
398
- if agent:
399
- status_parts.append("✅ Agent: Ready")
400
- else:
401
- status_parts.append("❌ Agent: Not initialized")
402
-
403
- status_parts.append(f"🔗 Server URL: {MCP_SERVER_URL}")
404
- return "\n".join(status_parts)
405
-
406
- def reconnect_to_server():
407
- """Attempt to reconnect to the MCP server"""
408
- try:
409
- success = initialize_mcp_client()
410
- if success:
411
- return "✅ Successfully reconnected to MCP server!"
412
- else:
413
- return "❌ Failed to reconnect. Please check if the MCP server is running."
414
- except Exception as e:
415
- return f"❌ Reconnection error: {str(e)}"
416
-
417
- # ============================================================================
418
- # GRADIO INTERFACE CREATION
419
- # ============================================================================
420
-
421
- def create_gradio_interface():
422
- """Create and configure the Gradio interface"""
423
-
424
- css = """
425
- .gradio-container {
426
- max-width: 1200px !important;
427
- }
428
- .status-box {
429
- background-color: #f8f9fa;
430
- border: 1px solid #dee2e6;
431
- border-radius: 8px;
432
- padding: 15px;
433
- font-family: monospace;
434
- font-size: 12px;
435
- white-space: pre-line;
436
- }
437
- """
438
-
439
- with gr.Blocks(
440
- title="MCP Sentiment Analysis Client v2",
441
- theme=gr.themes.Soft(),
442
- css=css
443
- ) as interface:
444
-
445
- # Header
446
- gr.Markdown("""
447
- # 🎭 MCP Sentiment Analysis Client v2
448
-
449
- **Improved Version** - Connect to your local MCP server using SmolAgents for AI-powered sentiment analysis.
450
- """)
451
-
452
- with gr.Row():
453
- with gr.Column(scale=2):
454
- # Main chat interface
455
- chatbot = gr.Chatbot(
456
- label="Chat with MCP Agent",
457
- height=500,
458
- show_label=True,
459
- container=True,
460
- type='messages'
461
- )
462
-
463
- with gr.Row():
464
- question_input = gr.Textbox(
465
- placeholder="Ask about sentiment analysis (e.g., 'Analyze: I love this product!')",
466
- label="Your Question",
467
- lines=2,
468
- scale=4
469
- )
470
- submit_btn = gr.Button("Submit", variant="primary", scale=1)
471
-
472
- # Quick action buttons
473
- with gr.Row():
474
- gr.Markdown("### 💡 Quick Actions:")
475
-
476
- with gr.Row():
477
- examples = [
478
- "Analyze: 'I love this!'",
479
- "Analyze: 'This is terrible'",
480
- "What tools are available?",
481
- "Health check"
482
- ]
483
-
484
- for example in examples:
485
- btn = gr.Button(example, size="sm", scale=1)
486
- btn.click(
487
- lambda x=example: x,
488
- outputs=question_input
489
- )
490
-
491
- with gr.Column(scale=1):
492
- # Server status and controls
493
- gr.Markdown("### 🔧 Server Status")
494
-
495
- status_display = gr.Textbox(
496
- label="Connection Status",
497
- lines=12,
498
- interactive=False,
499
- elem_classes=["status-box"]
500
- )
501
-
502
- with gr.Row():
503
- refresh_btn = gr.Button("🔄 Refresh", size="sm")
504
- reconnect_btn = gr.Button("🔌 Reconnect", size="sm", variant="secondary")
505
-
506
- # Information panel
507
- gr.Markdown("""
508
- ### ℹ️ Quick Guide
509
-
510
- **Example Questions:**
511
- - "Analyze the sentiment of: [your text]"
512
- - "What's the sentiment of multiple texts?"
513
- - "Check system health"
514
- - "What tools do you have?"
515
-
516
- **Tips:**
517
- - Be specific with your requests
518
- - Wait for responses (may take 10-30 seconds)
519
- - Use the reconnect button if you see errors
520
- """)
521
-
522
- # Event handlers
523
- submit_btn.click(
524
- process_question,
525
- inputs=[question_input, chatbot],
526
- outputs=[chatbot, question_input]
527
- )
528
-
529
- question_input.submit(
530
- process_question,
531
- inputs=[question_input, chatbot],
532
- outputs=[chatbot, question_input]
533
- )
534
-
535
- refresh_btn.click(
536
- get_server_status,
537
- outputs=status_display
538
- )
539
-
540
- reconnect_btn.click(
541
- reconnect_to_server,
542
- outputs=status_display
543
- )
544
-
545
- # Initialize status display on load
546
- interface.load(
547
- get_server_status,
548
- outputs=status_display
549
- )
550
-
551
- return interface
552
 
553
- # ============================================================================
554
- # MAIN EXECUTION
555
- # ============================================================================
556
 
557
- def main():
558
- """Main function to initialize and launch the application"""
559
-
560
- print("🚀 Starting MCP Gradio Client v2...")
561
- print(f"🔗 Connecting to MCP server at: {MCP_SERVER_URL}")
562
-
563
- # Initialize MCP client and agent
564
- print("⚙️ Initializing MCP client and agent...")
565
- success = initialize_mcp_client()
566
-
567
- if success:
568
- print("✅ Initialization successful!")
569
- else:
570
- print("⚠️ Initialization failed, but interface will still launch.")
571
- print(" You can try reconnecting using the interface.")
572
-
573
- # Create and launch Gradio interface
574
- print("🌐 Creating Gradio interface...")
575
- interface = create_gradio_interface()
576
-
577
- print("🎉 Launching interface...")
578
- print("🛑 Press Ctrl+C to stop the server")
579
-
580
- # Launch the interface
581
- interface.launch(
582
- server_name="0.0.0.0",
583
- server_port=7862,
584
- share=False,
585
- debug=False,
586
- show_error=True
587
  )
588
 
589
- if __name__ == "__main__":
590
- main()
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import os
 
 
 
 
 
3
 
 
4
  from smolagents import InferenceClientModel, CodeAgent, MCPClient
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
 
7
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  mcp_client = MCPClient(
9
+ {"url": os.environ['MCP_SERVER_URL']}
10
  )
11
+ tools = mcp_client.get_tools()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ model = InferenceClientModel(token=os.environ['HF_TOKEN']))
14
+ agent = CodeAgent(tools=[*tools], model=model, additional_authorized_imports=["json", "ast", "urllib", "base64"])
 
15
 
16
+ demo = gr.ChatInterface(
17
+ fn=lambda message, history: str(agent.run(message)),
18
+ type="messages",
19
+ examples=["Analyze the sentiment of the following text 'This is awesome'"],
20
+ title="Agent with MCP Tools",
21
+ description="This is a simple agent that uses MCP tools to answer questions.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  )
23
 
24
+ demo.launch()
25
+ finally:
26
+ mcp_client.disconnect()