Denys Kanunnikov commited on
Commit
2521958
Β·
1 Parent(s): a4b9912

adding files

Browse files
Files changed (1) hide show
  1. app.py +356 -0
app.py ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Main entry point for MCP Sentiment Analysis Server.
4
+
5
+ This script provides multiple modes of operation:
6
+ 1. MCP Server mode - JSON-RPC 2.0 server for AI model integration
7
+ 2. Gradio Interface mode - Web UI for human interaction
8
+ 3. Combined mode - Both MCP server and Gradio interface
9
+ 4. Test mode - Run basic functionality tests
10
+
11
+ Usage:
12
+ python app.py --mode mcp # Run MCP server only
13
+ python app.py --mode gradio # Run Gradio interface only
14
+ python app.py --mode combined # Run both (default)
15
+ python app.py --mode test # Run tests
16
+ """
17
+
18
+ import asyncio
19
+ import argparse
20
+ import logging
21
+ import sys
22
+ import signal
23
+ import threading
24
+ from typing import Optional
25
+ from concurrent.futures import ThreadPoolExecutor
26
+
27
+ try:
28
+ import uvloop
29
+ UVLOOP_AVAILABLE = True
30
+ except ImportError:
31
+ UVLOOP_AVAILABLE = False
32
+
33
+ from src import (
34
+ create_server,
35
+ MCPServerRunner,
36
+ create_gradio_interface,
37
+ get_analyzer
38
+ )
39
+
40
+
41
+ class ApplicationRunner:
42
+ """
43
+ Main application runner that manages different execution modes.
44
+
45
+ Supports running MCP server, Gradio interface, or both simultaneously
46
+ with proper resource management and graceful shutdown.
47
+ """
48
+
49
+ def __init__(self):
50
+ """Initialize application runner."""
51
+ self.logger = logging.getLogger(__name__)
52
+ self.mcp_server = None
53
+ self.gradio_interface = None
54
+ self.running = False
55
+ self.executor = ThreadPoolExecutor(max_workers=2)
56
+
57
+ # Setup signal handlers
58
+ signal.signal(signal.SIGINT, self._signal_handler)
59
+ signal.signal(signal.SIGTERM, self._signal_handler)
60
+
61
+ def _signal_handler(self, signum, frame):
62
+ """Handle shutdown signals."""
63
+ self.logger.info(f"Received signal {signum}, shutting down...")
64
+ self.running = False
65
+
66
+ async def run_mcp_server(self) -> None:
67
+ """Run MCP server only."""
68
+ self.logger.info("Starting MCP server mode")
69
+
70
+ try:
71
+ # Create and run MCP server
72
+ self.mcp_server = await create_server()
73
+ runner = MCPServerRunner(self.mcp_server)
74
+
75
+ self.running = True
76
+ await runner.run()
77
+
78
+ except Exception as e:
79
+ self.logger.error(f"MCP server failed: {e}")
80
+ raise
81
+ finally:
82
+ if self.mcp_server:
83
+ await self.mcp_server.stop()
84
+
85
+ def run_gradio_interface(self, **kwargs) -> None:
86
+ """Run Gradio interface only."""
87
+ self.logger.info("Starting Gradio interface mode")
88
+
89
+ try:
90
+ # Create and launch Gradio interface
91
+ self.gradio_interface = create_gradio_interface()
92
+
93
+ # Default launch parameters
94
+ launch_params = {
95
+ "server_name": "0.0.0.0",
96
+ "server_port": 7860,
97
+ "share": False,
98
+ "debug": False,
99
+ "show_error": True,
100
+ "quiet": False
101
+ }
102
+ launch_params.update(kwargs)
103
+
104
+ self.running = True
105
+ self.gradio_interface.launch(**launch_params)
106
+
107
+ except Exception as e:
108
+ self.logger.error(f"Gradio interface failed: {e}")
109
+ raise
110
+
111
+ async def run_combined(self, **gradio_kwargs) -> None:
112
+ """Run both MCP server and Gradio interface."""
113
+ self.logger.info("Starting combined mode (MCP server + Gradio interface)")
114
+
115
+ try:
116
+ # Create MCP server
117
+ self.mcp_server = await create_server()
118
+
119
+ # Create Gradio interface
120
+ self.gradio_interface = create_gradio_interface()
121
+
122
+ # Default Gradio launch parameters
123
+ launch_params = {
124
+ "server_name": "0.0.0.0",
125
+ "server_port": 7860,
126
+ "share": False,
127
+ "debug": False,
128
+ "show_error": True,
129
+ "quiet": False
130
+ }
131
+ launch_params.update(gradio_kwargs)
132
+
133
+ self.running = True
134
+
135
+ # Run Gradio in thread pool
136
+ gradio_future = self.executor.submit(
137
+ self.gradio_interface.launch, **launch_params
138
+ )
139
+
140
+ # Run MCP server in main thread
141
+ runner = MCPServerRunner(self.mcp_server)
142
+
143
+ # Start both services
144
+ self.logger.info("Both services starting...")
145
+
146
+ # Wait for either to complete or fail
147
+ try:
148
+ await runner.run()
149
+ except Exception as e:
150
+ self.logger.error(f"MCP server error: {e}")
151
+ raise
152
+ finally:
153
+ # Cleanup
154
+ if gradio_future:
155
+ gradio_future.cancel()
156
+
157
+ except Exception as e:
158
+ self.logger.error(f"Combined mode failed: {e}")
159
+ raise
160
+ finally:
161
+ if self.mcp_server:
162
+ await self.mcp_server.stop()
163
+
164
+ async def run_tests(self) -> bool:
165
+ """Run basic functionality tests."""
166
+ self.logger.info("Running functionality tests...")
167
+
168
+ try:
169
+ # Test 1: Sentiment analyzer initialization
170
+ self.logger.info("Test 1: Initializing sentiment analyzer...")
171
+ analyzer = await get_analyzer("textblob")
172
+ self.logger.info(f"βœ“ Analyzer initialized with backend: {analyzer.backend}")
173
+
174
+ # Test 2: Basic sentiment analysis
175
+ self.logger.info("Test 2: Basic sentiment analysis...")
176
+ test_texts = [
177
+ "I love this product!",
178
+ "This is terrible.",
179
+ "It's okay, nothing special."
180
+ ]
181
+
182
+ for text in test_texts:
183
+ result = await analyzer.analyze(text)
184
+ self.logger.info(f"βœ“ '{text}' -> {result.label.value} ({result.confidence:.2f})")
185
+
186
+ # Test 3: Batch analysis
187
+ self.logger.info("Test 3: Batch analysis...")
188
+ batch_results = await analyzer.analyze_batch(test_texts)
189
+ self.logger.info(f"βœ“ Batch analysis completed: {len(batch_results)} results")
190
+
191
+ # Test 4: MCP tools
192
+ self.logger.info("Test 4: MCP tools...")
193
+ from src.tools import get_tools
194
+ tools = get_tools()
195
+ available_tools = tools.get_tools()
196
+ self.logger.info(f"βœ“ {len(available_tools)} MCP tools available")
197
+
198
+ # Test 5: Tool execution
199
+ self.logger.info("Test 5: Tool execution...")
200
+ result = await tools.call_tool("analyze_sentiment", {
201
+ "text": "This is a test message",
202
+ "backend": "textblob"
203
+ })
204
+ self.logger.info(f"βœ“ Tool execution successful: {result.get('success', False)}")
205
+
206
+ # Test 6: Health check
207
+ self.logger.info("Test 6: Health check...")
208
+ health_result = await tools.call_tool("health_check", {})
209
+ self.logger.info(f"βœ“ Health check: {health_result.get('status', 'unknown')}")
210
+
211
+ # Cleanup
212
+ await analyzer.cleanup()
213
+
214
+ self.logger.info("πŸŽ‰ All tests passed!")
215
+ return True
216
+
217
+ except Exception as e:
218
+ self.logger.error(f"❌ Test failed: {e}")
219
+ return False
220
+
221
+
222
+ def setup_logging(level: str = "INFO") -> None:
223
+ """
224
+ Setup logging configuration.
225
+
226
+ Args:
227
+ level: Logging level
228
+ """
229
+ logging.basicConfig(
230
+ level=getattr(logging, level.upper()),
231
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
232
+ handlers=[
233
+ logging.StreamHandler(sys.stderr)
234
+ ]
235
+ )
236
+
237
+
238
+ def parse_arguments() -> argparse.Namespace:
239
+ """
240
+ Parse command line arguments.
241
+
242
+ Returns:
243
+ Parsed arguments
244
+ """
245
+ parser = argparse.ArgumentParser(
246
+ description="MCP Sentiment Analysis Server",
247
+ formatter_class=argparse.RawDescriptionHelpFormatter,
248
+ epilog="""
249
+ Examples:
250
+ python app.py --mode mcp # MCP server only
251
+ python app.py --mode gradio # Gradio interface only
252
+ python app.py --mode combined # Both services
253
+ python app.py --mode test # Run tests
254
+ python app.py --mode gradio --port 8080 # Custom port
255
+ python app.py --mode gradio --share # Public sharing
256
+ """
257
+ )
258
+
259
+ parser.add_argument(
260
+ "--mode",
261
+ choices=["mcp", "gradio", "combined", "test"],
262
+ default="combined",
263
+ help="Execution mode (default: combined)"
264
+ )
265
+
266
+ parser.add_argument(
267
+ "--log-level",
268
+ choices=["DEBUG", "INFO", "WARNING", "ERROR"],
269
+ default="INFO",
270
+ help="Logging level (default: INFO)"
271
+ )
272
+
273
+ # Gradio-specific options
274
+ parser.add_argument(
275
+ "--port",
276
+ type=int,
277
+ default=7860,
278
+ help="Gradio server port (default: 7860)"
279
+ )
280
+
281
+ parser.add_argument(
282
+ "--host",
283
+ default="0.0.0.0",
284
+ help="Gradio server host (default: 0.0.0.0)"
285
+ )
286
+
287
+ parser.add_argument(
288
+ "--share",
289
+ action="store_true",
290
+ help="Enable Gradio public sharing"
291
+ )
292
+
293
+ parser.add_argument(
294
+ "--debug",
295
+ action="store_true",
296
+ help="Enable debug mode"
297
+ )
298
+
299
+ return parser.parse_args()
300
+
301
+
302
+ async def main() -> None:
303
+ """Main application entry point."""
304
+ # Parse arguments
305
+ args = parse_arguments()
306
+
307
+ # Setup logging
308
+ setup_logging(args.log_level)
309
+ logger = logging.getLogger(__name__)
310
+
311
+ # Use uvloop if available for better performance
312
+ if UVLOOP_AVAILABLE and args.mode in ["mcp", "combined"]:
313
+ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
314
+ logger.info("Using uvloop for better performance")
315
+
316
+ # Create application runner
317
+ runner = ApplicationRunner()
318
+
319
+ try:
320
+ if args.mode == "mcp":
321
+ await runner.run_mcp_server()
322
+
323
+ elif args.mode == "gradio":
324
+ # Gradio runs in sync mode
325
+ gradio_kwargs = {
326
+ "server_name": args.host,
327
+ "server_port": args.port,
328
+ "share": args.share,
329
+ "debug": args.debug
330
+ }
331
+ runner.run_gradio_interface(**gradio_kwargs)
332
+
333
+ elif args.mode == "combined":
334
+ gradio_kwargs = {
335
+ "server_name": args.host,
336
+ "server_port": args.port,
337
+ "share": args.share,
338
+ "debug": args.debug
339
+ }
340
+ await runner.run_combined(**gradio_kwargs)
341
+
342
+ elif args.mode == "test":
343
+ success = await runner.run_tests()
344
+ sys.exit(0 if success else 1)
345
+
346
+ except KeyboardInterrupt:
347
+ logger.info("Application interrupted by user")
348
+ except Exception as e:
349
+ logger.error(f"Application error: {e}")
350
+ sys.exit(1)
351
+ finally:
352
+ logger.info("Application shutdown complete")
353
+
354
+
355
+ if __name__ == "__main__":
356
+ asyncio.run(main())