Update app.py
Browse files
app.py
CHANGED
|
@@ -689,98 +689,16 @@ def Search_DuckDuckGo( # <-- MCP tool #2 (DDG Search)
|
|
| 689 |
# Code Execution: Python (MCP tool #3)
|
| 690 |
# ======================================
|
| 691 |
|
| 692 |
-
import tempfile
|
| 693 |
-
import base64
|
| 694 |
-
from pathlib import Path
|
| 695 |
-
|
| 696 |
-
def _detect_created_files(working_dir: str, before_files: set) -> list[str]:
|
| 697 |
-
"""
|
| 698 |
-
Detect files created during code execution.
|
| 699 |
-
Returns list of newly created file paths.
|
| 700 |
-
"""
|
| 701 |
-
try:
|
| 702 |
-
current_files = set()
|
| 703 |
-
for file_path in Path(working_dir).rglob("*"):
|
| 704 |
-
if file_path.is_file():
|
| 705 |
-
current_files.add(str(file_path))
|
| 706 |
-
|
| 707 |
-
new_files = current_files - before_files
|
| 708 |
-
return list(new_files)
|
| 709 |
-
except Exception:
|
| 710 |
-
return []
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
def _generate_file_url(file_path: str) -> dict:
|
| 714 |
-
"""
|
| 715 |
-
Generate a data URL for small files or file info for larger files.
|
| 716 |
-
Returns dict with file info and download URL.
|
| 717 |
-
"""
|
| 718 |
-
try:
|
| 719 |
-
path = Path(file_path)
|
| 720 |
-
file_size = path.stat().st_size
|
| 721 |
-
|
| 722 |
-
# For files under 1MB, create data URL
|
| 723 |
-
if file_size < 1024 * 1024: # 1MB limit
|
| 724 |
-
with open(file_path, 'rb') as f:
|
| 725 |
-
file_data = f.read()
|
| 726 |
-
|
| 727 |
-
# Determine MIME type based on extension
|
| 728 |
-
mime_types = {
|
| 729 |
-
'.csv': 'text/csv',
|
| 730 |
-
'.txt': 'text/plain',
|
| 731 |
-
'.json': 'application/json',
|
| 732 |
-
'.png': 'image/png',
|
| 733 |
-
'.jpg': 'image/jpeg',
|
| 734 |
-
'.jpeg': 'image/jpeg',
|
| 735 |
-
'.gif': 'image/gif',
|
| 736 |
-
'.pdf': 'application/pdf',
|
| 737 |
-
'.html': 'text/html',
|
| 738 |
-
'.xml': 'text/xml',
|
| 739 |
-
'.svg': 'image/svg+xml'
|
| 740 |
-
}
|
| 741 |
-
|
| 742 |
-
mime_type = mime_types.get(path.suffix.lower(), 'application/octet-stream')
|
| 743 |
-
encoded_data = base64.b64encode(file_data).decode('utf-8')
|
| 744 |
-
data_url = f"data:{mime_type};base64,{encoded_data}"
|
| 745 |
-
|
| 746 |
-
return {
|
| 747 |
-
'name': path.name,
|
| 748 |
-
'size': file_size,
|
| 749 |
-
'type': mime_type,
|
| 750 |
-
'url': data_url,
|
| 751 |
-
'downloadable': True
|
| 752 |
-
}
|
| 753 |
-
else:
|
| 754 |
-
# For larger files, just return file info
|
| 755 |
-
return {
|
| 756 |
-
'name': path.name,
|
| 757 |
-
'size': file_size,
|
| 758 |
-
'type': 'file',
|
| 759 |
-
'url': None,
|
| 760 |
-
'downloadable': False,
|
| 761 |
-
'note': f'File too large ({file_size} bytes) for data URL'
|
| 762 |
-
}
|
| 763 |
-
except Exception as e:
|
| 764 |
-
return {
|
| 765 |
-
'name': Path(file_path).name,
|
| 766 |
-
'error': str(e),
|
| 767 |
-
'downloadable': False
|
| 768 |
-
}
|
| 769 |
-
|
| 770 |
-
|
| 771 |
def Execute_Python(code: Annotated[str, "Python source code to run; stdout is captured and returned."]) -> str:
|
| 772 |
"""
|
| 773 |
-
Execute arbitrary Python code and return captured stdout
|
| 774 |
-
|
| 775 |
-
Supports creating downloadable artifacts like CSV files, images, etc. Files created
|
| 776 |
-
during execution will be detected and made available as data URLs for download.
|
| 777 |
|
| 778 |
Args:
|
| 779 |
code (str): Python source code to run; stdout is captured and returned.
|
| 780 |
|
| 781 |
Returns:
|
| 782 |
-
str: Combined stdout produced by the code,
|
| 783 |
-
|
| 784 |
"""
|
| 785 |
_log_call_start("Execute_Python", code=_truncate_for_log(code or "", 300))
|
| 786 |
if code is None:
|
|
@@ -788,63 +706,15 @@ def Execute_Python(code: Annotated[str, "Python source code to run; stdout is ca
|
|
| 788 |
_log_call_end("Execute_Python", result)
|
| 789 |
return result
|
| 790 |
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
for file_path in Path(temp_dir).rglob("*"):
|
| 801 |
-
if file_path.is_file():
|
| 802 |
-
before_files.add(str(file_path))
|
| 803 |
-
|
| 804 |
-
# Execute code with stdout capture
|
| 805 |
-
old_stdout = sys.stdout
|
| 806 |
-
redirected_output = sys.stdout = StringIO()
|
| 807 |
-
|
| 808 |
-
try:
|
| 809 |
-
exec(code)
|
| 810 |
-
stdout_result = redirected_output.getvalue()
|
| 811 |
-
except Exception as e:
|
| 812 |
-
stdout_result = f"Error: {str(e)}"
|
| 813 |
-
finally:
|
| 814 |
-
sys.stdout = old_stdout
|
| 815 |
-
|
| 816 |
-
# Detect any files created during execution
|
| 817 |
-
created_files = _detect_created_files(temp_dir, before_files)
|
| 818 |
-
|
| 819 |
-
# Build result with stdout and file information
|
| 820 |
-
result_parts = []
|
| 821 |
-
|
| 822 |
-
if stdout_result.strip():
|
| 823 |
-
result_parts.append("=== Output ===")
|
| 824 |
-
result_parts.append(stdout_result.strip())
|
| 825 |
-
|
| 826 |
-
if created_files:
|
| 827 |
-
result_parts.append("\n=== Created Files ===")
|
| 828 |
-
for file_path in created_files:
|
| 829 |
-
file_info = _generate_file_url(file_path)
|
| 830 |
-
|
| 831 |
-
if file_info.get('downloadable', False):
|
| 832 |
-
result_parts.append(f"📁 {file_info['name']} ({file_info['size']} bytes)")
|
| 833 |
-
result_parts.append(f" Type: {file_info['type']}")
|
| 834 |
-
result_parts.append(f" Download: {file_info['url']}")
|
| 835 |
-
elif file_info.get('error'):
|
| 836 |
-
result_parts.append(f"❌ {file_info['name']} (error: {file_info['error']})")
|
| 837 |
-
else:
|
| 838 |
-
result_parts.append(f"📄 {file_info['name']} ({file_info.get('size', 'unknown')} bytes)")
|
| 839 |
-
if 'note' in file_info:
|
| 840 |
-
result_parts.append(f" Note: {file_info['note']}")
|
| 841 |
-
|
| 842 |
-
result = "\n".join(result_parts) if result_parts else "No output or files generated."
|
| 843 |
-
|
| 844 |
-
finally:
|
| 845 |
-
# Restore original working directory
|
| 846 |
-
os.chdir(original_cwd)
|
| 847 |
-
|
| 848 |
_log_call_end("Execute_Python", _truncate_for_log(result))
|
| 849 |
return result
|
| 850 |
|
|
@@ -1375,15 +1245,14 @@ code_interface = gr.Interface(
|
|
| 1375 |
outputs=gr.Textbox(label="Output"),
|
| 1376 |
title="Python Code Executor",
|
| 1377 |
description=(
|
| 1378 |
-
"<div style=\"text-align:center\">Execute Python code and
|
| 1379 |
),
|
| 1380 |
api_description=(
|
| 1381 |
-
"Execute arbitrary Python code and return captured stdout
|
| 1382 |
"Supports any valid Python code including imports, variables, functions, loops, and calculations. "
|
| 1383 |
-
"
|
| 1384 |
-
"Examples: 'print(2+2)', 'import pandas as pd; df.to_csv(\"data.csv\")', 'import matplotlib.pyplot as plt; plt.savefig(\"plot.png\")'. "
|
| 1385 |
"Parameters: code (str - Python source code to execute). "
|
| 1386 |
-
"Returns: Combined stdout output
|
| 1387 |
),
|
| 1388 |
flagging_mode="never",
|
| 1389 |
)
|
|
|
|
| 689 |
# Code Execution: Python (MCP tool #3)
|
| 690 |
# ======================================
|
| 691 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
def Execute_Python(code: Annotated[str, "Python source code to run; stdout is captured and returned."]) -> str:
|
| 693 |
"""
|
| 694 |
+
Execute arbitrary Python code and return captured stdout or an error message.
|
|
|
|
|
|
|
|
|
|
| 695 |
|
| 696 |
Args:
|
| 697 |
code (str): Python source code to run; stdout is captured and returned.
|
| 698 |
|
| 699 |
Returns:
|
| 700 |
+
str: Combined stdout produced by the code, or the exception text if
|
| 701 |
+
execution failed.
|
| 702 |
"""
|
| 703 |
_log_call_start("Execute_Python", code=_truncate_for_log(code or "", 300))
|
| 704 |
if code is None:
|
|
|
|
| 706 |
_log_call_end("Execute_Python", result)
|
| 707 |
return result
|
| 708 |
|
| 709 |
+
old_stdout = sys.stdout
|
| 710 |
+
redirected_output = sys.stdout = StringIO()
|
| 711 |
+
try:
|
| 712 |
+
exec(code)
|
| 713 |
+
result = redirected_output.getvalue()
|
| 714 |
+
except Exception as e:
|
| 715 |
+
result = str(e)
|
| 716 |
+
finally:
|
| 717 |
+
sys.stdout = old_stdout
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 718 |
_log_call_end("Execute_Python", _truncate_for_log(result))
|
| 719 |
return result
|
| 720 |
|
|
|
|
| 1245 |
outputs=gr.Textbox(label="Output"),
|
| 1246 |
title="Python Code Executor",
|
| 1247 |
description=(
|
| 1248 |
+
"<div style=\"text-align:center\">Execute Python code and see the output.</div>"
|
| 1249 |
),
|
| 1250 |
api_description=(
|
| 1251 |
+
"Execute arbitrary Python code and return captured stdout or an error message. "
|
| 1252 |
"Supports any valid Python code including imports, variables, functions, loops, and calculations. "
|
| 1253 |
+
"Examples: 'print(2+2)', 'import math; print(math.sqrt(16))', 'for i in range(3): print(i)'. "
|
|
|
|
| 1254 |
"Parameters: code (str - Python source code to execute). "
|
| 1255 |
+
"Returns: Combined stdout output or exception text if execution fails."
|
| 1256 |
),
|
| 1257 |
flagging_mode="never",
|
| 1258 |
)
|