mic3333 commited on
Commit
6169e06
·
1 Parent(s): 436bd09

add code interpreter service files

Browse files
python_service/app.py ADDED
@@ -0,0 +1,577 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ LibreChat Custom Code Interpreter - HuggingFace Space
4
+ A secure code execution server using Gradio + MCP with SSE support
5
+ """
6
+
7
+ import json
8
+ import subprocess
9
+ import tempfile
10
+ import os
11
+ import sys
12
+ import uuid
13
+ import time
14
+ import base64
15
+ import io
16
+ from pathlib import Path
17
+ from typing import Dict, Any, Optional, List
18
+ import gradio as gr
19
+ import matplotlib
20
+ matplotlib.use('Agg') # Use non-interactive backend
21
+ import matplotlib.pyplot as plt
22
+ import numpy as np
23
+ import pandas as pd
24
+
25
+ class SecureCodeExecutor:
26
+ def __init__(self):
27
+ self.sessions = {}
28
+ self.max_execution_time = 30
29
+ self.max_output_length = 10000
30
+ self.allowed_languages = ["python", "javascript", "bash"]
31
+
32
+ # Security: List of blocked commands/imports
33
+ self.blocked_imports = [
34
+ 'subprocess', 'os', 'sys', 'shutil', 'glob', 'pickle',
35
+ 'marshal', 'imp', 'importlib', '__import__'
36
+ ]
37
+ self.blocked_bash_commands = [
38
+ 'rm', 'sudo', 'chmod', 'chown', 'dd', 'mkfs', 'fdisk',
39
+ 'curl', 'wget', 'ssh', 'scp', 'nc', 'netcat'
40
+ ]
41
+
42
+ def create_session(self) -> str:
43
+ """Create a new execution session"""
44
+ session_id = str(uuid.uuid4())[:8] # Shorter ID for HF
45
+ self.sessions[session_id] = {
46
+ 'created_at': time.time(),
47
+ 'variables': {},
48
+ 'history': [],
49
+ 'files': {}
50
+ }
51
+ return session_id
52
+
53
+ def cleanup_old_sessions(self):
54
+ """Remove sessions older than 1 hour"""
55
+ current_time = time.time()
56
+ old_sessions = [
57
+ sid for sid, session in self.sessions.items()
58
+ if current_time - session['created_at'] > 3600
59
+ ]
60
+ for sid in old_sessions:
61
+ del self.sessions[sid]
62
+
63
+ def is_code_safe(self, code: str, language: str) -> tuple[bool, str]:
64
+ """Check if code is safe to execute"""
65
+ if language == "python":
66
+ # Check for blocked imports
67
+ for blocked in self.blocked_imports:
68
+ if blocked in code:
69
+ return False, f"Blocked import/function: {blocked}"
70
+
71
+ # Check for dangerous patterns
72
+ dangerous_patterns = ['exec(', 'eval(', 'open(', 'file(', '__']
73
+ for pattern in dangerous_patterns:
74
+ if pattern in code:
75
+ return False, f"Dangerous pattern detected: {pattern}"
76
+
77
+ elif language == "bash":
78
+ # Check for blocked commands
79
+ for blocked in self.blocked_bash_commands:
80
+ if blocked in code.lower():
81
+ return False, f"Blocked command: {blocked}"
82
+
83
+ return True, ""
84
+
85
+ def execute_python_code(self, code: str, session_id: Optional[str] = None) -> Dict[str, Any]:
86
+ """Execute Python code with visualization support"""
87
+ # Security check
88
+ is_safe, reason = self.is_code_safe(code, "python")
89
+ if not is_safe:
90
+ return {
91
+ "success": False,
92
+ "stdout": "",
93
+ "stderr": f"Security violation: {reason}",
94
+ "execution_time": time.time()
95
+ }
96
+
97
+ # Prepare execution environment
98
+ setup_code = '''
99
+ import matplotlib
100
+ matplotlib.use('Agg')
101
+ import matplotlib.pyplot as plt
102
+ import numpy as np
103
+ import pandas as pd
104
+ import json
105
+ import math
106
+ import random
107
+ import base64
108
+ import io
109
+ from datetime import datetime, timedelta
110
+
111
+ # Custom print function to capture output
112
+ _output_buffer = []
113
+ _original_print = print
114
+ def print(*args, **kwargs):
115
+ _output_buffer.append(' '.join(str(arg) for arg in args))
116
+
117
+ # Function to save plots as base64
118
+ def save_current_plot():
119
+ if plt.get_fignums(): # Check if there are any figures
120
+ buffer = io.BytesIO()
121
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
122
+ buffer.seek(0)
123
+ plot_data = buffer.getvalue()
124
+ buffer.close()
125
+ return base64.b64encode(plot_data).decode()
126
+ return None
127
+ '''
128
+
129
+ # Combine setup and user code
130
+ full_code = setup_code + "\n" + code + "\n"
131
+
132
+ # Add plot capture if plotting commands detected
133
+ if any(cmd in code for cmd in ['plt.', 'plot(', 'scatter(', 'bar(', 'hist(']):
134
+ full_code += "\n_plot_data = save_current_plot()\nif _plot_data: _output_buffer.append('PLOT_DATA:' + _plot_data)\n"
135
+
136
+ try:
137
+ # Create temporary file
138
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
139
+ f.write(full_code)
140
+ temp_file = f.name
141
+
142
+ # Execute with timeout
143
+ result = subprocess.run(
144
+ [sys.executable, temp_file],
145
+ capture_output=True,
146
+ text=True,
147
+ timeout=self.max_execution_time,
148
+ cwd=tempfile.gettempdir()
149
+ )
150
+
151
+ # Process output
152
+ stdout = result.stdout
153
+ stderr = result.stderr
154
+ plot_data = None
155
+
156
+ # Extract plot data if present
157
+ if 'PLOT_DATA:' in stdout:
158
+ lines = stdout.split('\n')
159
+ clean_lines = []
160
+ for line in lines:
161
+ if line.startswith('PLOT_DATA:'):
162
+ plot_data = line.replace('PLOT_DATA:', '')
163
+ else:
164
+ clean_lines.append(line)
165
+ stdout = '\n'.join(clean_lines)
166
+
167
+ # Limit output length
168
+ if len(stdout) > self.max_output_length:
169
+ stdout = stdout[:self.max_output_length] + "\n... (output truncated)"
170
+
171
+ execution_result = {
172
+ "success": result.returncode == 0,
173
+ "stdout": stdout.strip(),
174
+ "stderr": stderr.strip() if stderr else "",
175
+ "execution_time": time.time(),
176
+ "return_code": result.returncode
177
+ }
178
+
179
+ if plot_data:
180
+ execution_result["plot"] = plot_data
181
+
182
+ return execution_result
183
+
184
+ except subprocess.TimeoutExpired:
185
+ return {
186
+ "success": False,
187
+ "stdout": "",
188
+ "stderr": "Execution timed out (30s limit)",
189
+ "execution_time": time.time()
190
+ }
191
+ except Exception as e:
192
+ return {
193
+ "success": False,
194
+ "stdout": "",
195
+ "stderr": str(e),
196
+ "execution_time": time.time()
197
+ }
198
+ finally:
199
+ if 'temp_file' in locals():
200
+ try:
201
+ os.unlink(temp_file)
202
+ except:
203
+ pass
204
+
205
+ def execute_javascript_code(self, code: str, session_id: Optional[str] = None) -> Dict[str, Any]:
206
+ """Execute JavaScript code using Node.js"""
207
+ # Security check
208
+ is_safe, reason = self.is_code_safe(code, "javascript")
209
+ if not is_safe:
210
+ return {
211
+ "success": False,
212
+ "stdout": "",
213
+ "stderr": f"Security violation: {reason}",
214
+ "execution_time": time.time()
215
+ }
216
+
217
+ try:
218
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
219
+ f.write(code)
220
+ temp_file = f.name
221
+
222
+ result = subprocess.run(
223
+ ['node', temp_file],
224
+ capture_output=True,
225
+ text=True,
226
+ timeout=self.max_execution_time
227
+ )
228
+
229
+ stdout = result.stdout
230
+ if len(stdout) > self.max_output_length:
231
+ stdout = stdout[:self.max_output_length] + "\n... (output truncated)"
232
+
233
+ return {
234
+ "success": result.returncode == 0,
235
+ "stdout": stdout.strip(),
236
+ "stderr": result.stderr.strip() if result.stderr else "",
237
+ "execution_time": time.time(),
238
+ "return_code": result.returncode
239
+ }
240
+
241
+ except subprocess.TimeoutExpired:
242
+ return {
243
+ "success": False,
244
+ "stdout": "",
245
+ "stderr": "Execution timed out (30s limit)",
246
+ "execution_time": time.time()
247
+ }
248
+ except Exception as e:
249
+ return {
250
+ "success": False,
251
+ "stdout": "",
252
+ "stderr": str(e),
253
+ "execution_time": time.time()
254
+ }
255
+ finally:
256
+ if 'temp_file' in locals():
257
+ try:
258
+ os.unlink(temp_file)
259
+ except:
260
+ pass
261
+
262
+ def execute_bash_command(self, command: str, session_id: Optional[str] = None) -> Dict[str, Any]:
263
+ """Execute bash commands with security restrictions"""
264
+ # Security check
265
+ is_safe, reason = self.is_code_safe(command, "bash")
266
+ if not is_safe:
267
+ return {
268
+ "success": False,
269
+ "stdout": "",
270
+ "stderr": f"Security violation: {reason}",
271
+ "execution_time": time.time()
272
+ }
273
+
274
+ try:
275
+ result = subprocess.run(
276
+ command,
277
+ shell=True,
278
+ capture_output=True,
279
+ text=True,
280
+ timeout=self.max_execution_time,
281
+ cwd=tempfile.gettempdir()
282
+ )
283
+
284
+ stdout = result.stdout
285
+ if len(stdout) > self.max_output_length:
286
+ stdout = stdout[:self.max_output_length] + "\n... (output truncated)"
287
+
288
+ return {
289
+ "success": result.returncode == 0,
290
+ "stdout": stdout.strip(),
291
+ "stderr": result.stderr.strip() if result.stderr else "",
292
+ "execution_time": time.time(),
293
+ "return_code": result.returncode
294
+ }
295
+
296
+ except subprocess.TimeoutExpired:
297
+ return {
298
+ "success": False,
299
+ "stdout": "",
300
+ "stderr": "Command timed out (30s limit)",
301
+ "execution_time": time.time()
302
+ }
303
+ except Exception as e:
304
+ return {
305
+ "success": False,
306
+ "stdout": "",
307
+ "stderr": str(e),
308
+ "execution_time": time.time()
309
+ }
310
+
311
+ def execute_code(self, code: str, language: str = "python", session_id: Optional[str] = None) -> str:
312
+ """Main execution function - returns JSON for MCP compatibility"""
313
+ # Cleanup old sessions periodically
314
+ if len(self.sessions) > 10:
315
+ self.cleanup_old_sessions()
316
+
317
+ if language not in self.allowed_languages:
318
+ return json.dumps({
319
+ "success": False,
320
+ "error": f"Language '{language}' not supported. Allowed: {', '.join(self.allowed_languages)}"
321
+ })
322
+
323
+ # Create session if needed
324
+ if session_id and session_id not in self.sessions:
325
+ session_id = self.create_session()
326
+ elif not session_id:
327
+ session_id = self.create_session()
328
+
329
+ # Execute based on language
330
+ if language == "python":
331
+ result = self.execute_python_code(code, session_id)
332
+ elif language == "javascript":
333
+ result = self.execute_javascript_code(code, session_id)
334
+ elif language == "bash":
335
+ result = self.execute_bash_command(code, session_id)
336
+ else:
337
+ result = {
338
+ "success": False,
339
+ "error": f"Execution handler for {language} not implemented"
340
+ }
341
+
342
+ # Store in session history
343
+ if session_id in self.sessions:
344
+ self.sessions[session_id]['history'].append({
345
+ 'code': code,
346
+ 'language': language,
347
+ 'result': result,
348
+ 'timestamp': time.time()
349
+ })
350
+
351
+ result['session_id'] = session_id
352
+ return json.dumps(result, indent=2)
353
+
354
+ # Global executor instance
355
+ executor = SecureCodeExecutor()
356
+
357
+ # MCP Functions
358
+ def execute_python_code(code: str, session_id: str = None) -> str:
359
+ """
360
+ Execute Python code safely with visualization support.
361
+
362
+ Args:
363
+ code (str): Python code to execute
364
+ session_id (str, optional): Session ID for persistent context
365
+
366
+ Returns:
367
+ str: JSON string with execution results
368
+ """
369
+ return executor.execute_code(code, "python", session_id)
370
+
371
+ def execute_javascript_code(code: str, session_id: str = None) -> str:
372
+ """
373
+ Execute JavaScript code using Node.js.
374
+
375
+ Args:
376
+ code (str): JavaScript code to execute
377
+ session_id (str, optional): Session ID for persistent context
378
+
379
+ Returns:
380
+ str: JSON string with execution results
381
+ """
382
+ return executor.execute_code(code, "javascript", session_id)
383
+
384
+ def execute_bash_command(command: str, session_id: str = None) -> str:
385
+ """
386
+ Execute bash commands with security restrictions.
387
+
388
+ Args:
389
+ command (str): Bash command to execute
390
+ session_id (str, optional): Session ID for persistent context
391
+
392
+ Returns:
393
+ str: JSON string with execution results
394
+ """
395
+ return executor.execute_code(command, "bash", session_id)
396
+
397
+ def create_execution_session() -> str:
398
+ """
399
+ Create a new execution session for maintaining state.
400
+
401
+ Returns:
402
+ str: JSON string containing new session ID
403
+ """
404
+ session_id = executor.create_session()
405
+ return json.dumps({"session_id": session_id, "created_at": time.time()})
406
+
407
+ def list_execution_sessions() -> str:
408
+ """
409
+ List all active execution sessions.
410
+
411
+ Returns:
412
+ str: JSON string containing session information
413
+ """
414
+ return json.dumps({
415
+ "sessions": list(executor.sessions.keys()),
416
+ "count": len(executor.sessions),
417
+ "timestamp": time.time()
418
+ })
419
+
420
+ def get_execution_history(session_id: str) -> str:
421
+ """
422
+ Get execution history for a specific session.
423
+
424
+ Args:
425
+ session_id (str): Session ID to get history for
426
+
427
+ Returns:
428
+ str: JSON string containing execution history
429
+ """
430
+ if session_id not in executor.sessions:
431
+ return json.dumps({"error": "Session not found"})
432
+
433
+ return json.dumps({
434
+ "session_id": session_id,
435
+ "history": executor.sessions[session_id]['history'],
436
+ "created_at": executor.sessions[session_id]['created_at']
437
+ })
438
+
439
+ def get_system_info() -> str:
440
+ """
441
+ Get system information and available packages.
442
+
443
+ Returns:
444
+ str: JSON string containing system information
445
+ """
446
+ return json.dumps({
447
+ "python_version": sys.version,
448
+ "available_packages": [
449
+ "numpy", "pandas", "matplotlib", "json", "math",
450
+ "random", "datetime", "base64", "io"
451
+ ],
452
+ "execution_limits": {
453
+ "max_time": executor.max_execution_time,
454
+ "max_output": executor.max_output_length
455
+ },
456
+ "supported_languages": executor.allowed_languages
457
+ })
458
+
459
+ # Gradio Interface
460
+ def gradio_execute_code(code: str, language: str, session_id: str = ""):
461
+ """Gradio interface for code execution"""
462
+ if not session_id:
463
+ session_id = None
464
+
465
+ result_json = executor.execute_code(code, language.lower(), session_id)
466
+ result = json.loads(result_json)
467
+
468
+ output = ""
469
+ if result.get("success"):
470
+ if result.get("stdout"):
471
+ output += f"Output:\n{result['stdout']}\n\n"
472
+ if result.get("stderr"):
473
+ output += f"Warnings:\n{result['stderr']}\n\n"
474
+ if result.get("plot"):
475
+ output += f"Plot generated (base64): {result['plot'][:100]}...\n\n"
476
+ else:
477
+ output += f"Error:\n{result.get('stderr', result.get('error', 'Unknown error'))}\n\n"
478
+
479
+ output += f"Session ID: {result.get('session_id', 'N/A')}"
480
+
481
+ return output
482
+
483
+ # Create Gradio interface
484
+ with gr.Blocks(title="LibreChat Code Interpreter") as demo:
485
+ gr.Markdown("# LibreChat Code Interpreter")
486
+ gr.Markdown("Execute Python, JavaScript, and Bash code safely through MCP integration.")
487
+
488
+ with gr.Row():
489
+ with gr.Column():
490
+ code_input = gr.Textbox(
491
+ placeholder="Enter your code here...",
492
+ lines=10,
493
+ label="Code"
494
+ )
495
+ language_dropdown = gr.Dropdown(
496
+ choices=["Python", "JavaScript", "Bash"],
497
+ value="Python",
498
+ label="Language"
499
+ )
500
+ session_input = gr.Textbox(
501
+ placeholder="Optional: Session ID for persistent context",
502
+ label="Session ID"
503
+ )
504
+ execute_btn = gr.Button("Execute Code", variant="primary")
505
+
506
+ with gr.Column():
507
+ output_display = gr.Textbox(
508
+ lines=15,
509
+ label="Execution Result",
510
+ interactive=False
511
+ )
512
+
513
+ # Examples
514
+ gr.Markdown("## Examples")
515
+
516
+ example_python = gr.Code("""
517
+ import matplotlib.pyplot as plt
518
+ import numpy as np
519
+
520
+ # Generate sample data
521
+ x = np.linspace(0, 10, 100)
522
+ y = np.sin(x) * np.exp(-x/10)
523
+
524
+ # Create plot
525
+ plt.figure(figsize=(10, 6))
526
+ plt.plot(x, y, 'b-', linewidth=2, label='Damped Sine Wave')
527
+ plt.title('Example Visualization')
528
+ plt.xlabel('X values')
529
+ plt.ylabel('Y values')
530
+ plt.legend()
531
+ plt.grid(True, alpha=0.3)
532
+ plt.show()
533
+
534
+ print("Visualization created successfully!")
535
+ """, language="python", label="Python Example with Visualization")
536
+
537
+ example_js = gr.Code("""
538
+ // Data processing example
539
+ const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
540
+
541
+ const sum = data.reduce((acc, val) => acc + val, 0);
542
+ const mean = sum / data.length;
543
+ const variance = data.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / data.length;
544
+ const stdDev = Math.sqrt(variance);
545
+
546
+ console.log(`Dataset: [${data.join(', ')}]`);
547
+ console.log(`Sum: ${sum}`);
548
+ console.log(`Mean: ${mean}`);
549
+ console.log(`Standard Deviation: ${stdDev.toFixed(3)}`);
550
+
551
+ // JSON processing
552
+ const result = {
553
+ dataset: data,
554
+ statistics: {
555
+ sum, mean, variance, stdDev
556
+ },
557
+ timestamp: new Date().toISOString()
558
+ };
559
+
560
+ console.log('\\nResult:');
561
+ console.log(JSON.stringify(result, null, 2));
562
+ """, language="javascript", label="JavaScript Example")
563
+
564
+ execute_btn.click(
565
+ fn=gradio_execute_code,
566
+ inputs=[code_input, language_dropdown, session_input],
567
+ outputs=[output_display]
568
+ )
569
+
570
+ if __name__ == "__main__":
571
+ # Launch with MCP server enabled
572
+ demo.launch(
573
+ mcp_server=True,
574
+ share=False,
575
+ server_name="0.0.0.0",
576
+ server_port=7860
577
+ )
python_service/requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ matplotlib>=3.7.0
3
+ numpy>=1.24.0
4
+ pandas>=2.0.0
5
+ #plotly>=5.15.0
6
+ #narwhal>=0.1.0