├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── README_CN.md ├── app ├── __init__.py ├── executor.py └── main.py ├── example_dsl ├── matplotlib.yml └── python-requirements.txt ├── images ├── Xnip2024-11-25_11-30-12.jpg ├── Xnip2024-11-25_11-31-01.jpg └── Xnip2025-04-28_16-48-48.jpg ├── requirements.txt └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim-bookworm 2 | COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ 3 | 4 | # 安装Node.js 5 | RUN apt-get update && \ 6 | apt-get install -y curl && \ 7 | curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ 8 | apt-get install -y nodejs && \ 9 | apt-get clean && \ 10 | rm -rf /var/lib/apt/lists/* 11 | 12 | # 设置工作目录 13 | WORKDIR /app 14 | 15 | # 复制依赖文件 16 | COPY requirements.txt . 17 | 18 | # 使用 uv 安装基础依赖到系统环境 19 | RUN uv pip install --system -r requirements.txt 20 | 21 | # 复制应用代码和启动脚本 22 | COPY app/ ./app/ 23 | COPY start.sh . 24 | 25 | # 创建依赖目录 26 | RUN mkdir -p /dependencies 27 | 28 | # 设置启动脚本权限 29 | RUN chmod +x start.sh 30 | 31 | # 暴露端口 32 | EXPOSE 8194 33 | 34 | # 使用启动脚本替代直接的 uvicorn 命令 35 | CMD ["./start.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 svcvit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DIFY-SANDBOX-PY 2 | [English](README.md) | [中文](README_CN.md) 3 | 4 | A code executor for Dify that is compatible with the official sandbox API calls and dependency installation. 5 | - Supports Python 3.12 6 | - Supports Node.js 20 7 | 8 | ## Purpose 9 | While the official sandbox has many permission settings and is a better sandboxing solution, in personal use cases where Dify's code nodes are entirely self-edited, there's no risk of code injection. This project aims to provide broader permissions and support for more dependencies (like numpy>2.0, matplotlib, scikit-learn) to reduce confusing error messages. This code was developed by referencing the official sandbox's API call examples. 10 | 11 | ## Usage 12 | In the official docker-compose.yaml, locate the sandbox image section and replace it with: 13 | ``` 14 | sandbox: 15 | # image: langgenius/dify-sandbox:0.2.10 16 | image: svcvit/dify-sandbox-py:0.1.4 17 | ``` 18 | 19 | If you prefer to build the image yourself, you can clone this repository and run: 20 | ``` 21 | docker build -t dify-sandbox-py:local . 22 | ``` 23 | Then modify the sandbox image in `docker-compose.yaml` to use `dify-sandbox-py:local` 24 | 25 | ## Screenshots 26 | Python support 27 | ![](/images/Xnip2024-11-25_11-30-12.jpg) 28 | Node.js support 29 | ![](/images/Xnip2024-11-25_11-31-01.jpg) 30 | Docker container logs 31 | ![](/images/Xnip2025-04-28_16-48-48.jpg) 32 | 33 | 34 | ## Notes 35 | - Network access restrictions have been removed; network access is enabled by default 36 | - Using UV as the dependency manager for faster package installation, allowing millisecond-level dependency installation on restart 37 | - Third-party dependencies can be installed following the official method: simply add required dependencies to `/docker/volumes/sandbox/dependencies/python-requirements.txt` and restart the sandbox 38 | - The image only contains FastAPI-related dependencies. Any additional dependencies you need must be manually added to python-requirements.txt -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # DIFY-SANDBOX-PY 2 | [English](README.md) | [中文](README_CN.md) 3 | 4 | 这是一个供 Dify 使用的代码执行器,兼容官方sandbox的API调用以及依赖安装。 5 | - 支持 Python3.12 6 | - 支持 Node.js 20 7 | 8 | ## 目的 9 | 因为官方sandbox有很多关于权限的设置,那是一个更好的沙盒方案,但是个人实际使用过程中,Dify的代码节点完全是个人编辑,所以也不存在代码注入风险,希望有更大的权限,安装更多依赖包例如numpy>2.0,matplotlib,scikit-learn 减少一些看不懂的报错,因此参考官方sandbox的API调用示例,开发了本代码。 10 | 11 | ## 用法 12 | 在官方 docker-compose.yaml 中,找到 sandbox 的 image 部分内容,替换镜像即可。 13 | ``` 14 | sandbox: 15 | # image: langgenius/dify-sandbox:0.2.10 16 | image: svcvit/dify-sandbox-py:0.1.4 17 | ``` 18 | 19 | 如果你不放心,希望自己打包镜像,你可以下载这个仓库,运行下面的代码打包 20 | ``` 21 | docker build -t dify-sandbox-py:local . 22 | ``` 23 | 然后修改`docker-compose.yaml`里面sandbox为上面的`dify-sandbox-py:local`即可 24 | 25 | ## 截图 26 | Python的支持 27 | ![](/images/Xnip2024-11-25_11-30-12.jpg) 28 | nodejs的支持 29 | ![](/images/Xnip2024-11-25_11-31-01.jpg) 30 | docker容器的日志 31 | ![](/images/Xnip2025-04-28_16-48-48.jpg) 32 | 33 | ## 说明 34 | - 去掉了网络访问的控制,默认就支持访问网络 35 | - 使用UV作为依赖管理,安装依赖速度更快,重启可以毫秒级安装依赖。 36 | - 第三方依赖安装与官方一致,将需要的依赖放入`/docker/volumes/sandbox/dependencies/python-requirements.txt`,重启sandbox即可。 37 | - 镜像只有fastapi相关的依赖,任何你需要的依赖,需要自己加到python-requirements.txt中。 38 | - 支持环境变量配置 `PIP_MIRROR_URL`。如果你是中国去用户可以配置 `PIP_MIRROR_URL=https://pypi.tuna.tsinghua.edu.cn/simple` 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svcvit/dify-sandbox-py/472730cb03d907776a6e4894d3e68ae10b567b55/app/__init__.py -------------------------------------------------------------------------------- /app/executor.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | import io 4 | import tempfile 5 | import os 6 | import subprocess 7 | from contextlib import redirect_stdout, redirect_stderr 8 | from typing import Dict, Any 9 | from concurrent.futures import ProcessPoolExecutor 10 | 11 | def _run_python_code_in_process(code: str) -> Dict[str, Any]: 12 | """在进程中执行Python代码的函数""" 13 | stdout_buffer = io.StringIO() 14 | stderr_buffer = io.StringIO() 15 | 16 | try: 17 | with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer): 18 | global_namespace = {} 19 | exec(code, global_namespace) 20 | 21 | return { 22 | "success": True, 23 | "output": stdout_buffer.getvalue(), 24 | "error": stderr_buffer.getvalue() or None 25 | } 26 | except Exception as e: 27 | return { 28 | "success": False, 29 | "output": stdout_buffer.getvalue(), 30 | "error": str(e) 31 | } 32 | finally: 33 | stdout_buffer.close() 34 | stderr_buffer.close() 35 | 36 | def _run_nodejs_code_in_process(code: str) -> Dict[str, Any]: 37 | """在进程中执行Node.js代码的函数""" 38 | try: 39 | # 创建临时文件来存储JavaScript代码 40 | with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as temp_file: 41 | temp_file.write(code) 42 | temp_file_path = temp_file.name 43 | 44 | # 使用Node.js执行代码 45 | process = subprocess.Popen( 46 | ['node', temp_file_path], 47 | stdout=subprocess.PIPE, 48 | stderr=subprocess.PIPE, 49 | text=True 50 | ) 51 | 52 | stdout, stderr = process.communicate() 53 | 54 | # 删除临时文件 55 | os.unlink(temp_file_path) 56 | 57 | if process.returncode == 0: 58 | return { 59 | "success": True, 60 | "output": stdout, 61 | "error": None 62 | } 63 | else: 64 | return { 65 | "success": False, 66 | "output": stdout, 67 | "error": stderr 68 | } 69 | except Exception as e: 70 | return { 71 | "success": False, 72 | "output": "", 73 | "error": str(e) 74 | } 75 | 76 | def check_nodejs_available(): 77 | """检查Node.js是否可用""" 78 | try: 79 | subprocess.run(['node', '--version'], 80 | stdout=subprocess.PIPE, 81 | stderr=subprocess.PIPE, 82 | check=True) 83 | return True 84 | except (subprocess.SubprocessError, FileNotFoundError): 85 | return False 86 | 87 | class CodeExecutor: 88 | def __init__(self, timeout: int = 30, max_workers: int = 10): 89 | self.timeout = timeout 90 | self.process_pool = ProcessPoolExecutor(max_workers=max_workers) 91 | self.nodejs_available = check_nodejs_available() 92 | 93 | async def shutdown(self): 94 | """关闭进程池""" 95 | self.process_pool.shutdown(wait=True) 96 | 97 | async def execute(self, code: str, language: str = "python3") -> Dict[str, Any]: 98 | try: 99 | loop = asyncio.get_event_loop() 100 | 101 | if language == "python3": 102 | executor_func = _run_python_code_in_process 103 | elif language == "nodejs": 104 | if not self.nodejs_available: 105 | return { 106 | "success": False, 107 | "output": "", 108 | "error": "Node.js未安装或不可用" 109 | } 110 | executor_func = _run_nodejs_code_in_process 111 | else: 112 | return { 113 | "success": False, 114 | "output": "", 115 | "error": f"不支持的语言: {language}" 116 | } 117 | 118 | future = loop.run_in_executor( 119 | self.process_pool, 120 | executor_func, 121 | code 122 | ) 123 | result = await asyncio.wait_for(future, timeout=self.timeout) 124 | return result 125 | 126 | except asyncio.TimeoutError: 127 | return { 128 | "success": False, 129 | "output": "", 130 | "error": f"代码执行超时 (>{self.timeout}秒)" 131 | } 132 | except Exception as e: 133 | return { 134 | "success": False, 135 | "output": "", 136 | "error": str(e) 137 | } -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Header, HTTPException, Request 2 | from starlette.middleware.base import BaseHTTPMiddleware 3 | from pydantic import BaseModel 4 | from typing import Optional 5 | import asyncio 6 | from .executor import CodeExecutor 7 | import os 8 | 9 | 10 | # 配置 11 | API_KEY = os.getenv("API_KEY", "dify-sandbox") 12 | MAX_REQUESTS = int(os.getenv("MAX_REQUESTS", "100")) 13 | MAX_WORKERS = int(os.getenv("MAX_WORKERS", "10")) 14 | WORKER_TIMEOUT = int(os.getenv("WORKER_TIMEOUT", "15")) 15 | 16 | app = FastAPI() 17 | executor = CodeExecutor(timeout=WORKER_TIMEOUT, max_workers=MAX_WORKERS) 18 | 19 | # 请求模型 20 | class CodeRequest(BaseModel): 21 | language: str 22 | code: str 23 | preload: Optional[str] = "" 24 | enable_network: Optional[bool] = False 25 | 26 | # 认证中间件 27 | class AuthMiddleware(BaseHTTPMiddleware): 28 | async def dispatch(self, request: Request, call_next): 29 | if request.url.path.startswith("/v1/sandbox"): 30 | api_key = request.headers.get("X-Api-Key") 31 | if not api_key or api_key != API_KEY: 32 | # 修改这里:返回 JSONResponse 而不是直接返回 HTTPException 33 | from fastapi.responses import JSONResponse 34 | return JSONResponse( 35 | status_code=401, 36 | content={ 37 | "code": -401, 38 | "message": "Unauthorized", 39 | "data": None 40 | } 41 | ) 42 | return await call_next(request) 43 | 44 | # 并发控制中间件 45 | class ConcurrencyMiddleware(BaseHTTPMiddleware): 46 | def __init__(self, app): 47 | super().__init__(app) 48 | self.semaphore = asyncio.Semaphore(MAX_WORKERS) 49 | self.current_requests = 0 50 | 51 | async def dispatch(self, request: Request, call_next): 52 | if request.url.path.startswith("/v1/sandbox/run"): 53 | if self.current_requests >= MAX_REQUESTS: 54 | return { 55 | "code": -503, 56 | "message": "Too many requests", 57 | "data": None 58 | } 59 | 60 | self.current_requests += 1 61 | try: 62 | async with self.semaphore: 63 | response = await call_next(request) 64 | return response 65 | finally: 66 | self.current_requests -= 1 67 | return await call_next(request) 68 | 69 | # 添加中间件 70 | app.add_middleware(AuthMiddleware) 71 | app.add_middleware(ConcurrencyMiddleware) 72 | 73 | @app.get("/health") 74 | async def health_check(): 75 | return "ok" 76 | 77 | @app.post("/v1/sandbox/run") 78 | async def execute_code(request: CodeRequest): 79 | if request.language not in ["python3", "nodejs"]: 80 | return { 81 | "code": -400, 82 | "message": "unsupported language", 83 | "data": None 84 | } 85 | 86 | result = await executor.execute(request.code, request.language) 87 | 88 | return { 89 | "code": 0, 90 | "message": "success", 91 | "data": { 92 | "error": result["error"] or "", 93 | "stdout": result["output"] or "", 94 | } 95 | } 96 | 97 | 98 | if __name__ == "__main__": 99 | import uvicorn 100 | uvicorn.run(app, host="0.0.0.0", port=8194) -------------------------------------------------------------------------------- /example_dsl/matplotlib.yml: -------------------------------------------------------------------------------- 1 | app: 2 | description: '' 3 | icon: 🤖 4 | icon_background: '#FFEAD5' 5 | mode: advanced-chat 6 | name: matplotlib 7 | use_icon_as_answer_icon: false 8 | kind: app 9 | version: 0.1.3 10 | workflow: 11 | conversation_variables: [] 12 | environment_variables: [] 13 | features: 14 | file_upload: 15 | allowed_file_extensions: 16 | - .JPG 17 | - .JPEG 18 | - .PNG 19 | - .GIF 20 | - .WEBP 21 | - .SVG 22 | allowed_file_types: 23 | - image 24 | allowed_file_upload_methods: 25 | - local_file 26 | - remote_url 27 | enabled: false 28 | fileUploadConfig: 29 | audio_file_size_limit: 50 30 | batch_count_limit: 5 31 | file_size_limit: 15 32 | image_file_size_limit: 10 33 | video_file_size_limit: 100 34 | workflow_file_upload_limit: 10 35 | image: 36 | enabled: false 37 | number_limits: 3 38 | transfer_methods: 39 | - local_file 40 | - remote_url 41 | number_limits: 3 42 | opening_statement: '' 43 | retriever_resource: 44 | enabled: true 45 | sensitive_word_avoidance: 46 | enabled: false 47 | speech_to_text: 48 | enabled: false 49 | suggested_questions: [] 50 | suggested_questions_after_answer: 51 | enabled: false 52 | text_to_speech: 53 | enabled: false 54 | language: '' 55 | voice: '' 56 | graph: 57 | edges: 58 | - data: 59 | isInIteration: false 60 | sourceType: start 61 | targetType: code 62 | id: 1732083938374-source-1732083953925-target 63 | source: '1732083938374' 64 | sourceHandle: source 65 | target: '1732083953925' 66 | targetHandle: target 67 | type: custom 68 | zIndex: 0 69 | - data: 70 | isInIteration: false 71 | sourceType: code 72 | targetType: answer 73 | id: 1732083953925-source-answer-target 74 | source: '1732083953925' 75 | sourceHandle: source 76 | target: answer 77 | targetHandle: target 78 | type: custom 79 | zIndex: 0 80 | nodes: 81 | - data: 82 | desc: '' 83 | selected: false 84 | title: Start 85 | type: start 86 | variables: [] 87 | height: 54 88 | id: '1732083938374' 89 | position: 90 | x: 315.1428571428571 91 | y: 165.78571428571428 92 | positionAbsolute: 93 | x: 315.1428571428571 94 | y: 165.78571428571428 95 | selected: false 96 | sourcePosition: right 97 | targetPosition: left 98 | type: custom 99 | width: 244 100 | - data: 101 | answer: '![描述文本](data:image/png;base64,{{#1732083953925.result#}}) 102 | 103 | ' 104 | desc: '' 105 | selected: false 106 | title: Answer 107 | type: answer 108 | variables: [] 109 | height: 119 110 | id: answer 111 | position: 112 | x: 315.1428571428571 113 | y: 481.50000000000006 114 | positionAbsolute: 115 | x: 315.1428571428571 116 | y: 481.50000000000006 117 | selected: false 118 | sourcePosition: right 119 | targetPosition: left 120 | type: custom 121 | width: 244 122 | - data: 123 | code: "import matplotlib.pyplot as plt\nimport numpy as np\nimport io\nimport\ 124 | \ base64\n\ndef generate_base64_plot():\n # 数据准备\n x = np.linspace(0,\ 125 | \ 10, 100)\n y = np.sin(x)\n \n # 创建图形\n plt.figure(figsize=(8,\ 126 | \ 6)) # 设置画布大小为 800x600 像素\n plt.plot(x, y, label='Sine Wave', color='blue',\ 127 | \ linewidth=2)\n \n # 添加标题和标签\n plt.title('Sine Wave Example',\ 128 | \ fontsize=16)\n plt.xlabel('X-axis', fontsize=12)\n plt.ylabel('Y-axis',\ 129 | \ fontsize=12)\n \n # 添加网格和图例\n plt.grid(True, linestyle='--',\ 130 | \ alpha=0.6)\n plt.legend(fontsize=12)\n \n # 使用内存缓冲区保存图片为Base64字符串\n\ 131 | \ buffer = io.BytesIO()\n plt.savefig(buffer, format='png', dpi=100)\ 132 | \ # 使用 100 DPI 保存,确保像素为 800x600\n buffer.seek(0) # 将缓冲区指针回到起点\n \ 133 | \ base64_str = base64.b64encode(buffer.read()).decode('utf-8')\n buffer.close()\n\ 134 | \ plt.close() # 关闭绘图对象,释放资源\n \n return base64_str\n\n\ndef main():\n\ 135 | \ base64_image = generate_base64_plot()\n return {\"result\": base64_image}\n" 136 | code_language: python3 137 | desc: '' 138 | outputs: 139 | result: 140 | children: null 141 | type: string 142 | selected: false 143 | title: Code 144 | type: code 145 | variables: [] 146 | height: 54 147 | id: '1732083953925' 148 | position: 149 | x: 315.1428571428571 150 | y: 315.7857142857143 151 | positionAbsolute: 152 | x: 315.1428571428571 153 | y: 315.7857142857143 154 | selected: true 155 | sourcePosition: right 156 | targetPosition: left 157 | type: custom 158 | width: 244 159 | viewport: 160 | x: 346.00000000000006 161 | y: 216.05000000000004 162 | zoom: 0.7 163 | -------------------------------------------------------------------------------- /example_dsl/python-requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | jinja2 3 | json_repair 4 | numpy 5 | matplotlib -------------------------------------------------------------------------------- /images/Xnip2024-11-25_11-30-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svcvit/dify-sandbox-py/472730cb03d907776a6e4894d3e68ae10b567b55/images/Xnip2024-11-25_11-30-12.jpg -------------------------------------------------------------------------------- /images/Xnip2024-11-25_11-31-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svcvit/dify-sandbox-py/472730cb03d907776a6e4894d3e68ae10b567b55/images/Xnip2024-11-25_11-31-01.jpg -------------------------------------------------------------------------------- /images/Xnip2025-04-28_16-48-48.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svcvit/dify-sandbox-py/472730cb03d907776a6e4894d3e68ae10b567b55/images/Xnip2025-04-28_16-48-48.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | uvicorn 3 | starlette 4 | pydantic 5 | nodejs 6 | jinja2 -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 设置默认的 pip 镜像源 4 | MIRROR_URL=${PIP_MIRROR_URL:-"https://pypi.org/simple"} 5 | 6 | # 检查并安装依赖 7 | if [ -f "/dependencies/python-requirements.txt" ]; then 8 | echo "Dependency file found, starting to install additional dependencies..." 9 | echo "Using pip mirror: $MIRROR_URL" 10 | uv pip install --system -r /dependencies/python-requirements.txt -i "$MIRROR_URL" 11 | fi 12 | 13 | # 启动 FastAPI 应用 14 | exec uvicorn app.main:app --host 0.0.0.0 --port 8194 --------------------------------------------------------------------------------