Spaces:
Running
Running
terryyz
commited on
Commit
·
fe3f5b0
1
Parent(s):
f1aac33
init
Browse files- .gitignore +208 -0
- README.md +2 -2
- api_config.yaml +216 -0
- app.py +777 -0
- chat_state.py +259 -0
- completion.py +1304 -0
- conversation.py +0 -0
- requirements.txt +21 -0
- sandbox/__init__.py +0 -0
- sandbox/code_analyzer.py +935 -0
- sandbox/code_runner.py +1612 -0
- sandbox/constants.py +148 -0
- sandbox/prompts.py +393 -0
- sandbox/sandbox_manager.py +254 -0
- sandbox/sandbox_state.py +83 -0
- sandbox/sandbox_telemetry.py +221 -0
.gitignore
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[codz]
|
| 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 |
+
run.sh
|
| 29 |
+
|
| 30 |
+
# PyInstaller
|
| 31 |
+
# Usually these files are written by a python script from a template
|
| 32 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 33 |
+
*.manifest
|
| 34 |
+
*.spec
|
| 35 |
+
|
| 36 |
+
# Installer logs
|
| 37 |
+
pip-log.txt
|
| 38 |
+
pip-delete-this-directory.txt
|
| 39 |
+
|
| 40 |
+
# Unit test / coverage reports
|
| 41 |
+
htmlcov/
|
| 42 |
+
.tox/
|
| 43 |
+
.nox/
|
| 44 |
+
.coverage
|
| 45 |
+
.coverage.*
|
| 46 |
+
.cache
|
| 47 |
+
nosetests.xml
|
| 48 |
+
coverage.xml
|
| 49 |
+
*.cover
|
| 50 |
+
*.py.cover
|
| 51 |
+
.hypothesis/
|
| 52 |
+
.pytest_cache/
|
| 53 |
+
cover/
|
| 54 |
+
|
| 55 |
+
# Translations
|
| 56 |
+
*.mo
|
| 57 |
+
*.pot
|
| 58 |
+
|
| 59 |
+
# Django stuff:
|
| 60 |
+
*.log
|
| 61 |
+
local_settings.py
|
| 62 |
+
db.sqlite3
|
| 63 |
+
db.sqlite3-journal
|
| 64 |
+
|
| 65 |
+
# Flask stuff:
|
| 66 |
+
instance/
|
| 67 |
+
.webassets-cache
|
| 68 |
+
|
| 69 |
+
# Scrapy stuff:
|
| 70 |
+
.scrapy
|
| 71 |
+
|
| 72 |
+
# Sphinx documentation
|
| 73 |
+
docs/_build/
|
| 74 |
+
|
| 75 |
+
# PyBuilder
|
| 76 |
+
.pybuilder/
|
| 77 |
+
target/
|
| 78 |
+
|
| 79 |
+
# Jupyter Notebook
|
| 80 |
+
.ipynb_checkpoints
|
| 81 |
+
|
| 82 |
+
# IPython
|
| 83 |
+
profile_default/
|
| 84 |
+
ipython_config.py
|
| 85 |
+
|
| 86 |
+
# pyenv
|
| 87 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 88 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 89 |
+
# .python-version
|
| 90 |
+
|
| 91 |
+
# pipenv
|
| 92 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 93 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 94 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 95 |
+
# install all needed dependencies.
|
| 96 |
+
#Pipfile.lock
|
| 97 |
+
|
| 98 |
+
# UV
|
| 99 |
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
| 100 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 101 |
+
# commonly ignored for libraries.
|
| 102 |
+
#uv.lock
|
| 103 |
+
|
| 104 |
+
# poetry
|
| 105 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 106 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 107 |
+
# commonly ignored for libraries.
|
| 108 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 109 |
+
#poetry.lock
|
| 110 |
+
#poetry.toml
|
| 111 |
+
|
| 112 |
+
# pdm
|
| 113 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 114 |
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
| 115 |
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
| 116 |
+
#pdm.lock
|
| 117 |
+
#pdm.toml
|
| 118 |
+
.pdm-python
|
| 119 |
+
.pdm-build/
|
| 120 |
+
|
| 121 |
+
# pixi
|
| 122 |
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
| 123 |
+
#pixi.lock
|
| 124 |
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
| 125 |
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
| 126 |
+
.pixi
|
| 127 |
+
|
| 128 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 129 |
+
__pypackages__/
|
| 130 |
+
|
| 131 |
+
# Celery stuff
|
| 132 |
+
celerybeat-schedule
|
| 133 |
+
celerybeat.pid
|
| 134 |
+
|
| 135 |
+
# SageMath parsed files
|
| 136 |
+
*.sage.py
|
| 137 |
+
|
| 138 |
+
# Environments
|
| 139 |
+
.env
|
| 140 |
+
.envrc
|
| 141 |
+
.venv
|
| 142 |
+
env/
|
| 143 |
+
venv/
|
| 144 |
+
ENV/
|
| 145 |
+
env.bak/
|
| 146 |
+
venv.bak/
|
| 147 |
+
|
| 148 |
+
# Spyder project settings
|
| 149 |
+
.spyderproject
|
| 150 |
+
.spyproject
|
| 151 |
+
|
| 152 |
+
# Rope project settings
|
| 153 |
+
.ropeproject
|
| 154 |
+
|
| 155 |
+
# mkdocs documentation
|
| 156 |
+
/site
|
| 157 |
+
|
| 158 |
+
# mypy
|
| 159 |
+
.mypy_cache/
|
| 160 |
+
.dmypy.json
|
| 161 |
+
dmypy.json
|
| 162 |
+
|
| 163 |
+
# Pyre type checker
|
| 164 |
+
.pyre/
|
| 165 |
+
|
| 166 |
+
# pytype static type analyzer
|
| 167 |
+
.pytype/
|
| 168 |
+
|
| 169 |
+
# Cython debug symbols
|
| 170 |
+
cython_debug/
|
| 171 |
+
|
| 172 |
+
# PyCharm
|
| 173 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 174 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 175 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 176 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 177 |
+
#.idea/
|
| 178 |
+
|
| 179 |
+
# Abstra
|
| 180 |
+
# Abstra is an AI-powered process automation framework.
|
| 181 |
+
# Ignore directories containing user credentials, local state, and settings.
|
| 182 |
+
# Learn more at https://abstra.io/docs
|
| 183 |
+
.abstra/
|
| 184 |
+
|
| 185 |
+
# Visual Studio Code
|
| 186 |
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
| 187 |
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
| 188 |
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
| 189 |
+
# you could uncomment the following to ignore the entire vscode folder
|
| 190 |
+
# .vscode/
|
| 191 |
+
|
| 192 |
+
# Ruff stuff:
|
| 193 |
+
.ruff_cache/
|
| 194 |
+
|
| 195 |
+
# PyPI configuration file
|
| 196 |
+
.pypirc
|
| 197 |
+
|
| 198 |
+
# Cursor
|
| 199 |
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
| 200 |
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
| 201 |
+
# refer to https://docs.cursor.com/context/ignore-files
|
| 202 |
+
.cursorignore
|
| 203 |
+
.cursorindexingignore
|
| 204 |
+
|
| 205 |
+
# Marimo
|
| 206 |
+
marimo/_static/
|
| 207 |
+
marimo/_lsp/
|
| 208 |
+
__marimo__/
|
README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
emoji: 🚀
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: yellow
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.44.1
|
| 8 |
app_file: app.py
|
| 9 |
-
pinned:
|
| 10 |
license: apache-2.0
|
| 11 |
---
|
| 12 |
|
|
|
|
| 1 |
---
|
| 2 |
+
title: BigCodeArena
|
| 3 |
emoji: 🚀
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: yellow
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.44.1
|
| 8 |
app_file: app.py
|
| 9 |
+
pinned: true
|
| 10 |
license: apache-2.0
|
| 11 |
---
|
| 12 |
|
api_config.yaml
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gpt-4o-mini-2024-07-18:
|
| 2 |
+
model: gpt-4o-mini-2024-07-18
|
| 3 |
+
endpoints: null
|
| 4 |
+
api_type: openai
|
| 5 |
+
parallel: 32
|
| 6 |
+
max_tokens: 8192
|
| 7 |
+
temperature: 0.0
|
| 8 |
+
|
| 9 |
+
gpt-4o-2024-11-20:
|
| 10 |
+
model: gpt-4o-2024-11-20
|
| 11 |
+
endpoints: null
|
| 12 |
+
api_type: openai
|
| 13 |
+
parallel: 32
|
| 14 |
+
max_tokens: 8192
|
| 15 |
+
temperature: 0.0
|
| 16 |
+
|
| 17 |
+
# o1-2024-12-17:
|
| 18 |
+
# model: o1-2024-12-17
|
| 19 |
+
# endpoints: null
|
| 20 |
+
# api_type: openai
|
| 21 |
+
# parallel: 32
|
| 22 |
+
# max_tokens: 8192
|
| 23 |
+
# temperature: 0.0
|
| 24 |
+
|
| 25 |
+
# o4-mini-2025-04-16:
|
| 26 |
+
# model: o4-mini-2025-04-16
|
| 27 |
+
# endpoints: null
|
| 28 |
+
# api_type: openai_thinking
|
| 29 |
+
# parallel: 32
|
| 30 |
+
# max_tokens: 8192
|
| 31 |
+
# temperature: 1.0
|
| 32 |
+
|
| 33 |
+
# o3-mini-2025-01-31:
|
| 34 |
+
# model: o3-mini-2025-01-31
|
| 35 |
+
# endpoints: null
|
| 36 |
+
# api_type: openai_thinking
|
| 37 |
+
# parallel: 32
|
| 38 |
+
# max_tokens: 8192
|
| 39 |
+
# temperature: 0.0
|
| 40 |
+
|
| 41 |
+
# gemini-2.0-flash-001:
|
| 42 |
+
# model: google/gemini-2.0-flash-001
|
| 43 |
+
# endpoints:
|
| 44 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 45 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 46 |
+
# api_type: openai
|
| 47 |
+
# parallel: 32
|
| 48 |
+
# max_tokens: 81920
|
| 49 |
+
# temperature: 0.0
|
| 50 |
+
|
| 51 |
+
# gemini-2.5-pro:
|
| 52 |
+
# model: google/gemini-2.5-pro
|
| 53 |
+
# endpoints:
|
| 54 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 55 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 56 |
+
# api_type: openai
|
| 57 |
+
# parallel: 32
|
| 58 |
+
# max_tokens: 8192
|
| 59 |
+
# temperature: 0.0
|
| 60 |
+
|
| 61 |
+
# gemini-2.5-flash:
|
| 62 |
+
# model: google/gemini-2.5-flash
|
| 63 |
+
# endpoints:
|
| 64 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 65 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 66 |
+
# api_type: openai
|
| 67 |
+
# parallel: 32
|
| 68 |
+
# max_tokens: 8192
|
| 69 |
+
# temperature: 0.0
|
| 70 |
+
|
| 71 |
+
# claude35_haiku:
|
| 72 |
+
# model: bedrock/anthropic.claude-3-5-haiku-20241022-v1:0
|
| 73 |
+
# endpoints: null
|
| 74 |
+
# api_type: litellm
|
| 75 |
+
# parallel: 32
|
| 76 |
+
# max_tokens: 8192
|
| 77 |
+
# temperature: 0.0
|
| 78 |
+
|
| 79 |
+
# claude35_sonnet:
|
| 80 |
+
# model: bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0
|
| 81 |
+
# endpoints: null
|
| 82 |
+
# api_type: litellm
|
| 83 |
+
# parallel: 32
|
| 84 |
+
# max_tokens: 8192
|
| 85 |
+
# temperature: 0.0
|
| 86 |
+
|
| 87 |
+
# claude37_sonnet:
|
| 88 |
+
# model: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0
|
| 89 |
+
# endpoints: null
|
| 90 |
+
# api_type: litellm
|
| 91 |
+
# parallel: 32
|
| 92 |
+
# max_tokens: 8192
|
| 93 |
+
# temperature: 0.0
|
| 94 |
+
|
| 95 |
+
# qwen3-coder:
|
| 96 |
+
# model: qwen/qwen3-coder
|
| 97 |
+
# endpoints:
|
| 98 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 99 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 100 |
+
# api_type: openai
|
| 101 |
+
# parallel: 32
|
| 102 |
+
# max_tokens: 8192
|
| 103 |
+
# temperature: 0.0
|
| 104 |
+
|
| 105 |
+
# kimi-k2:
|
| 106 |
+
# model: moonshotai/kimi-k2
|
| 107 |
+
# endpoints:
|
| 108 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 109 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 110 |
+
# api_type: openai
|
| 111 |
+
# parallel: 32
|
| 112 |
+
# max_tokens: 8192
|
| 113 |
+
# temperature: 0.0
|
| 114 |
+
|
| 115 |
+
# claude-4-sonnet:
|
| 116 |
+
# model: bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0
|
| 117 |
+
# endpoints: null
|
| 118 |
+
# api_type: litellm
|
| 119 |
+
# parallel: 16
|
| 120 |
+
# max_tokens: 8192
|
| 121 |
+
# temperature: 0.0
|
| 122 |
+
|
| 123 |
+
# claude-4-opus:
|
| 124 |
+
# model: bedrock/us.anthropic.claude-opus-4-20250514-v1:0
|
| 125 |
+
# endpoints: null
|
| 126 |
+
# api_type: litellm
|
| 127 |
+
# parallel: 16
|
| 128 |
+
# max_tokens: 8192
|
| 129 |
+
# temperature: 0.0
|
| 130 |
+
|
| 131 |
+
# gpt-oss-120b:
|
| 132 |
+
# model: openai/gpt-oss-120b
|
| 133 |
+
# endpoints:
|
| 134 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 135 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 136 |
+
# api_type: openai_thinking
|
| 137 |
+
# parallel: 32
|
| 138 |
+
# max_tokens: 8192
|
| 139 |
+
# temperature: 1.0
|
| 140 |
+
|
| 141 |
+
# gpt-oss-20b:
|
| 142 |
+
# model: openai/gpt-oss-20b
|
| 143 |
+
# endpoints:
|
| 144 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 145 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 146 |
+
# api_type: openai_thinking
|
| 147 |
+
# parallel: 32
|
| 148 |
+
# max_tokens: 8192
|
| 149 |
+
# temperature: 1.0
|
| 150 |
+
|
| 151 |
+
# deepseek-chat-v3-0324:
|
| 152 |
+
# model: deepseek/deepseek-chat-v3-0324
|
| 153 |
+
# endpoints:
|
| 154 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 155 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 156 |
+
# api_type: openai
|
| 157 |
+
# parallel: 32
|
| 158 |
+
# max_tokens: 8192
|
| 159 |
+
# temperature: 0.0
|
| 160 |
+
|
| 161 |
+
# deepseek-chat-v3.1:
|
| 162 |
+
# model: deepseek-chat
|
| 163 |
+
# endpoints:
|
| 164 |
+
# - api_base: https://api.deepseek.com
|
| 165 |
+
# api_key: ${DEEPSEEK_API_KEY}
|
| 166 |
+
# api_type: openai
|
| 167 |
+
# parallel: 32
|
| 168 |
+
# max_tokens: 8192
|
| 169 |
+
# temperature: 0.0
|
| 170 |
+
|
| 171 |
+
# glm-4.5:
|
| 172 |
+
# model: z-ai/glm-4.5
|
| 173 |
+
# endpoints:
|
| 174 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 175 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 176 |
+
# api_type: openai
|
| 177 |
+
# parallel: 32
|
| 178 |
+
# max_tokens: 8192
|
| 179 |
+
# temperature: 0.0
|
| 180 |
+
|
| 181 |
+
# gpt-4.1-2025-04-14:
|
| 182 |
+
# model: gpt-4.1-2025-04-14
|
| 183 |
+
# endpoints: null
|
| 184 |
+
# api_type: openai
|
| 185 |
+
# parallel: 32
|
| 186 |
+
# max_tokens: 8192
|
| 187 |
+
# temperature: 0.0
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# deepseek-r1-0528:
|
| 191 |
+
# model: deepseek/deepseek-r1-0528
|
| 192 |
+
# endpoints:
|
| 193 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 194 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 195 |
+
# api_type: openai_thinking
|
| 196 |
+
# parallel: 32
|
| 197 |
+
# max_tokens: 81920
|
| 198 |
+
# temperature: 1.0
|
| 199 |
+
|
| 200 |
+
# gpt-5-2025-08-07:
|
| 201 |
+
# model: gpt-5-2025-08-07
|
| 202 |
+
# endpoints: null
|
| 203 |
+
# api_type: openai_thinking
|
| 204 |
+
# parallel: 32
|
| 205 |
+
# max_tokens: 8192
|
| 206 |
+
# temperature: 1.0
|
| 207 |
+
|
| 208 |
+
# grok-code:
|
| 209 |
+
# model: x-ai/grok-code-fast-1
|
| 210 |
+
# endpoints:
|
| 211 |
+
# - api_base: https://openrouter.ai/api/v1
|
| 212 |
+
# api_key: ${OPENROUTER_API_KEY}
|
| 213 |
+
# api_type: openai_thinking
|
| 214 |
+
# parallel: 32
|
| 215 |
+
# max_tokens: 8192
|
| 216 |
+
# temperature: 1.0
|
app.py
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Simple BigCodeArena - A simplified AI coding battle arena
|
| 3 |
+
Focuses on core functionality: two models, automatic code extraction, and execution
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
from gradio_sandboxcomponent import SandboxComponent
|
| 8 |
+
|
| 9 |
+
# Import completion utilities
|
| 10 |
+
from completion import make_config, registered_api_completion
|
| 11 |
+
|
| 12 |
+
# Import code extraction utilities
|
| 13 |
+
from sandbox.code_analyzer import (
|
| 14 |
+
SandboxEnvironment,
|
| 15 |
+
extract_code_from_markdown,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
# Import sandbox execution functions
|
| 19 |
+
from sandbox.code_runner import (
|
| 20 |
+
run_html_sandbox,
|
| 21 |
+
run_react_sandbox,
|
| 22 |
+
run_vue_sandbox,
|
| 23 |
+
run_pygame_sandbox,
|
| 24 |
+
run_gradio_sandbox,
|
| 25 |
+
run_streamlit_sandbox,
|
| 26 |
+
run_code_interpreter,
|
| 27 |
+
run_c_code,
|
| 28 |
+
run_cpp_code,
|
| 29 |
+
run_java_code,
|
| 30 |
+
run_golang_code,
|
| 31 |
+
run_rust_code,
|
| 32 |
+
mermaid_to_html
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# Create a proper sandbox state structure
|
| 36 |
+
def create_sandbox_state() -> dict:
|
| 37 |
+
"""Create a new sandbox state for a model"""
|
| 38 |
+
return {
|
| 39 |
+
'enable_sandbox': True,
|
| 40 |
+
'enabled_round': 0,
|
| 41 |
+
'sandbox_run_round': 0,
|
| 42 |
+
'edit_round': 0,
|
| 43 |
+
'sandbox_environment': SandboxEnvironment.AUTO,
|
| 44 |
+
'auto_selected_sandbox_environment': None,
|
| 45 |
+
'sandbox_instruction': "Run the extracted code in the appropriate sandbox environment",
|
| 46 |
+
'code_to_execute': "",
|
| 47 |
+
'code_dependencies': ([], []),
|
| 48 |
+
'btn_list_length': 5,
|
| 49 |
+
'sandbox_id': None,
|
| 50 |
+
'chat_session_id': None,
|
| 51 |
+
'conv_id': None,
|
| 52 |
+
"sandbox_output": None,
|
| 53 |
+
"sandbox_error": None,
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
def reset_sandbox_state(state: dict) -> dict:
|
| 57 |
+
"""Reset the sandbox state"""
|
| 58 |
+
state['enabled_round'] = 0
|
| 59 |
+
state['sandbox_run_round'] = 0
|
| 60 |
+
state['edit_round'] = 0
|
| 61 |
+
state['auto_selected_sandbox_environment'] = None
|
| 62 |
+
state['code_to_execute'] = ""
|
| 63 |
+
state['code_dependencies'] = ([], [])
|
| 64 |
+
state['sandbox_error'] = None
|
| 65 |
+
state['sandbox_output'] = None
|
| 66 |
+
state['sandbox_id'] = None
|
| 67 |
+
state['conv_id'] = None
|
| 68 |
+
state['chat_session_id'] = None
|
| 69 |
+
return state
|
| 70 |
+
|
| 71 |
+
# Load API configuration
|
| 72 |
+
def load_api_config():
|
| 73 |
+
"""Load API configuration from yaml file"""
|
| 74 |
+
try:
|
| 75 |
+
config = make_config("api_config.yaml")
|
| 76 |
+
return config
|
| 77 |
+
except Exception as e:
|
| 78 |
+
print(f"Error loading API config: {e}")
|
| 79 |
+
return {}
|
| 80 |
+
|
| 81 |
+
# Global variables
|
| 82 |
+
api_config = load_api_config()
|
| 83 |
+
available_models = list(api_config.keys()) if api_config else []
|
| 84 |
+
|
| 85 |
+
def get_random_models():
|
| 86 |
+
"""Get two random models from available models"""
|
| 87 |
+
if len(available_models) < 2:
|
| 88 |
+
return available_models[0] if available_models else None, available_models[0] if available_models else None
|
| 89 |
+
|
| 90 |
+
import random
|
| 91 |
+
models = random.sample(available_models, 2)
|
| 92 |
+
return models[0], models[1]
|
| 93 |
+
|
| 94 |
+
def create_chat_state(model_name: str) -> dict:
|
| 95 |
+
"""Create a new chat state for a model"""
|
| 96 |
+
return {
|
| 97 |
+
"model_name": model_name,
|
| 98 |
+
"messages": [],
|
| 99 |
+
"sandbox_state": create_sandbox_state()
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
def generate_response_with_completion(state, temperature, max_tokens):
|
| 103 |
+
"""Generate response using the completion API system with full conversation history"""
|
| 104 |
+
if state is None:
|
| 105 |
+
return state, ""
|
| 106 |
+
|
| 107 |
+
# Get the last user message
|
| 108 |
+
user_message = None
|
| 109 |
+
for msg in reversed(state["messages"]):
|
| 110 |
+
if msg["role"] == "user":
|
| 111 |
+
user_message = msg["content"]
|
| 112 |
+
break
|
| 113 |
+
|
| 114 |
+
if not user_message:
|
| 115 |
+
return state, ""
|
| 116 |
+
|
| 117 |
+
# Prepare messages for API call - include full conversation history
|
| 118 |
+
messages = []
|
| 119 |
+
for msg in state["messages"]:
|
| 120 |
+
if msg["role"] in ["user", "assistant"] and msg["content"] is not None:
|
| 121 |
+
messages.append({"role": msg["role"], "content": msg["content"]})
|
| 122 |
+
|
| 123 |
+
# Get model config
|
| 124 |
+
model_name = state["model_name"]
|
| 125 |
+
if model_name not in api_config:
|
| 126 |
+
print(f"Model {model_name} not found in config")
|
| 127 |
+
return state, f"Error: Model {model_name} not configured"
|
| 128 |
+
|
| 129 |
+
model_config = api_config[model_name]
|
| 130 |
+
api_type = model_config.get("api_type", "openai")
|
| 131 |
+
|
| 132 |
+
# retrieve the api completion function from register
|
| 133 |
+
api_completion_func = registered_api_completion[api_type]
|
| 134 |
+
|
| 135 |
+
# build arguments for api completions
|
| 136 |
+
# Use the actual model identifier from config, not the display name
|
| 137 |
+
actual_model = model_config.get("model", model_name)
|
| 138 |
+
kwargs = {
|
| 139 |
+
"model": actual_model,
|
| 140 |
+
"temperature": temperature,
|
| 141 |
+
"max_tokens": max_tokens,
|
| 142 |
+
"api_dict": model_config.get("endpoints", [{}])[0] if model_config.get("endpoints") else None,
|
| 143 |
+
"messages": messages,
|
| 144 |
+
}
|
| 145 |
+
output = api_completion_func(**kwargs)
|
| 146 |
+
|
| 147 |
+
# Extract the answer from the response
|
| 148 |
+
if isinstance(output, dict) and "answer" in output:
|
| 149 |
+
response_text = output["answer"]
|
| 150 |
+
return state, response_text
|
| 151 |
+
else:
|
| 152 |
+
error_msg = f"Error: Invalid response format from {api_type}"
|
| 153 |
+
print(error_msg)
|
| 154 |
+
return state, error_msg
|
| 155 |
+
|
| 156 |
+
def extract_and_execute_code(message, sandbox_state):
|
| 157 |
+
"""Extract code from message and prepare for execution"""
|
| 158 |
+
if not message:
|
| 159 |
+
return sandbox_state, "", ""
|
| 160 |
+
|
| 161 |
+
# Extract code using the same logic as code_runner.py
|
| 162 |
+
extract_result = extract_code_from_markdown(
|
| 163 |
+
message=message,
|
| 164 |
+
enable_auto_env=True
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
if extract_result is None:
|
| 168 |
+
return sandbox_state, "", ""
|
| 169 |
+
|
| 170 |
+
code, code_language, code_dependencies, env_selection = extract_result
|
| 171 |
+
|
| 172 |
+
# Update sandbox state (now a dictionary)
|
| 173 |
+
sandbox_state['code_to_execute'] = code
|
| 174 |
+
sandbox_state['code_dependencies'] = code_dependencies
|
| 175 |
+
sandbox_state['auto_selected_sandbox_environment'] = env_selection
|
| 176 |
+
|
| 177 |
+
return sandbox_state, code, str(env_selection)
|
| 178 |
+
|
| 179 |
+
def add_text_and_generate(state0, state1, text, temperature, max_tokens, model_a, model_b):
|
| 180 |
+
"""Add text and generate responses for both models"""
|
| 181 |
+
if not text.strip():
|
| 182 |
+
return state0, state1, "", "", "", "", "", "", "", "", "", "", "", ""
|
| 183 |
+
|
| 184 |
+
# Initialize states if needed
|
| 185 |
+
if state0 is None or state1 is None:
|
| 186 |
+
if state0 is None:
|
| 187 |
+
state0 = create_chat_state(model_a)
|
| 188 |
+
if state1 is None:
|
| 189 |
+
state1 = create_chat_state(model_b)
|
| 190 |
+
print(f"Models: {state0['model_name']} vs {state1['model_name']}")
|
| 191 |
+
|
| 192 |
+
# Add user message to both states
|
| 193 |
+
state0["messages"].append({"role": "user", "content": text})
|
| 194 |
+
state1["messages"].append({"role": "user", "content": text})
|
| 195 |
+
|
| 196 |
+
# Generate responses
|
| 197 |
+
state0, response0 = generate_response_with_completion(state0, temperature, max_tokens)
|
| 198 |
+
state1, response1 = generate_response_with_completion(state1, temperature, max_tokens)
|
| 199 |
+
|
| 200 |
+
# Add the assistant responses to the message history
|
| 201 |
+
state0["messages"].append({"role": "assistant", "content": response0})
|
| 202 |
+
state1["messages"].append({"role": "assistant", "content": response1})
|
| 203 |
+
|
| 204 |
+
# Format chat history for display
|
| 205 |
+
chat0 = format_chat_history(state0["messages"])
|
| 206 |
+
chat1 = format_chat_history(state1["messages"])
|
| 207 |
+
|
| 208 |
+
# Extract code from responses for sandbox
|
| 209 |
+
sandbox_state0 = state0.get("sandbox_state", create_sandbox_state())
|
| 210 |
+
sandbox_state1 = state1.get("sandbox_state", create_sandbox_state())
|
| 211 |
+
|
| 212 |
+
_, code0, env0 = extract_and_execute_code(response0, sandbox_state0)
|
| 213 |
+
_, code1, env1 = extract_and_execute_code(response1, sandbox_state1)
|
| 214 |
+
|
| 215 |
+
# Update sandbox states in the main states
|
| 216 |
+
state0["sandbox_state"] = sandbox_state0
|
| 217 |
+
state1["sandbox_state"] = sandbox_state1
|
| 218 |
+
|
| 219 |
+
# Clear previous sandbox outputs when new message is sent
|
| 220 |
+
sandbox_output0 = ""
|
| 221 |
+
sandbox_output1 = ""
|
| 222 |
+
sandbox_component_update0 = gr.update(visible=False)
|
| 223 |
+
sandbox_component_update1 = gr.update(visible=False)
|
| 224 |
+
|
| 225 |
+
# Also clear the sandbox view components to show fresh results
|
| 226 |
+
sandbox_view_a = ""
|
| 227 |
+
sandbox_view_b = ""
|
| 228 |
+
|
| 229 |
+
if code0.strip():
|
| 230 |
+
# Get the dependencies from the sandbox state
|
| 231 |
+
dependencies0 = sandbox_state0.get('code_dependencies', ([], []))
|
| 232 |
+
print(f"DEBUG: Running code0 with dependencies: {dependencies0}")
|
| 233 |
+
sandbox_url0, sandbox_output0, sandbox_error0 = run_sandbox_code(sandbox_state0, code0, dependencies0)
|
| 234 |
+
print(f"DEBUG: Code0 result - URL: {sandbox_url0}, Output: {sandbox_output0[:100] if sandbox_output0 else 'None'}, Error: {sandbox_error0[:100] if sandbox_error0 else 'None'}")
|
| 235 |
+
|
| 236 |
+
# Check if this is a web-based environment that should use SandboxComponent
|
| 237 |
+
env_type = sandbox_state0.get('auto_selected_sandbox_environment') or sandbox_state0.get('sandbox_environment')
|
| 238 |
+
print(f"DEBUG: Model A environment type: {env_type}")
|
| 239 |
+
# Use the URL directly from the function return
|
| 240 |
+
if sandbox_url0:
|
| 241 |
+
sandbox_component_update0 = gr.update(value=(sandbox_url0, True, []), visible=True)
|
| 242 |
+
|
| 243 |
+
# Update sandbox view with output and errors
|
| 244 |
+
if sandbox_output0:
|
| 245 |
+
sandbox_view_a += f"# Output\n{sandbox_output0}"
|
| 246 |
+
if sandbox_error0:
|
| 247 |
+
sandbox_view_a += f"# Errors\n{sandbox_error0}"
|
| 248 |
+
|
| 249 |
+
if code1.strip():
|
| 250 |
+
# Get the dependencies from the sandbox state
|
| 251 |
+
dependencies1 = sandbox_state1.get('code_dependencies', ([], []))
|
| 252 |
+
print(f"DEBUG: Running code1 with dependencies: {dependencies1}")
|
| 253 |
+
sandbox_url1, sandbox_output1, sandbox_error1 = run_sandbox_code(sandbox_state1, code1, dependencies1)
|
| 254 |
+
print(f"DEBUG: Code1 result - URL: {sandbox_url1}, Output: {sandbox_output1[:100] if sandbox_output1 else 'None'}, Error: {sandbox_error1[:100] if sandbox_error1 else 'None'}")
|
| 255 |
+
|
| 256 |
+
# Check if this is a web-based environment that should use SandboxComponent
|
| 257 |
+
env_type = sandbox_state1.get('auto_selected_sandbox_environment') or sandbox_state1.get('sandbox_environment')
|
| 258 |
+
print(f"DEBUG: Model B environment type: {env_type}")
|
| 259 |
+
# Use the URL directly from the function return
|
| 260 |
+
if sandbox_url1:
|
| 261 |
+
sandbox_component_update1 = gr.update(value=(sandbox_url1, True, []), visible=True)
|
| 262 |
+
|
| 263 |
+
if sandbox_output1:
|
| 264 |
+
sandbox_view_b += f"## Output\n{sandbox_output1}"
|
| 265 |
+
if sandbox_error1:
|
| 266 |
+
sandbox_view_b += f"## Errors\n{sandbox_error1}"
|
| 267 |
+
|
| 268 |
+
# Calculate conversation statistics
|
| 269 |
+
turn_count_a = len([msg for msg in state0["messages"] if msg["role"] == "assistant" and msg["content"]])
|
| 270 |
+
turn_count_b = len([msg for msg in state1["messages"] if msg["role"] == "assistant" and msg["content"]])
|
| 271 |
+
|
| 272 |
+
# Format conversation statistics
|
| 273 |
+
chat_stats_a = f"**Conversation:** {turn_count_a} turns | **Total Messages:** {len(state0['messages'])}"
|
| 274 |
+
chat_stats_b = f"**Conversation:** {turn_count_b} turns | **Total Messages:** {len(state1['messages'])}"
|
| 275 |
+
|
| 276 |
+
return state0, state1, chat0, chat1, response0, response1, code0, code1, env0, env1, sandbox_state0, sandbox_state1, sandbox_output0, sandbox_output1, sandbox_component_update0, sandbox_component_update1, chat_stats_a, chat_stats_b, sandbox_view_a, sandbox_view_b
|
| 277 |
+
|
| 278 |
+
def format_chat_history(messages):
|
| 279 |
+
"""Format messages for chat display with turn numbers"""
|
| 280 |
+
formatted = []
|
| 281 |
+
|
| 282 |
+
for msg in messages:
|
| 283 |
+
if msg["role"] == "user" and msg["content"]:
|
| 284 |
+
# Add turn number to user messages
|
| 285 |
+
formatted.append({
|
| 286 |
+
"role": "user",
|
| 287 |
+
"content": msg['content']
|
| 288 |
+
})
|
| 289 |
+
elif msg["role"] == "assistant" and msg["content"]:
|
| 290 |
+
# Add turn number to assistant messages
|
| 291 |
+
formatted.append({
|
| 292 |
+
"role": "assistant",
|
| 293 |
+
"content": msg['content']
|
| 294 |
+
})
|
| 295 |
+
|
| 296 |
+
return formatted
|
| 297 |
+
|
| 298 |
+
def clear_chat(state0, state1):
|
| 299 |
+
"""Clear chat history"""
|
| 300 |
+
if state0 and "sandbox_state" in state0:
|
| 301 |
+
reset_sandbox_state(state0["sandbox_state"])
|
| 302 |
+
if state1 and "sandbox_state" in state1:
|
| 303 |
+
reset_sandbox_state(state1["sandbox_state"])
|
| 304 |
+
|
| 305 |
+
# Get current model names for display
|
| 306 |
+
model_a, model_b = get_random_models()
|
| 307 |
+
|
| 308 |
+
return None, None, "", "", "", "", "", "", "", "", "", "", "", "", gr.update(visible=False), gr.update(visible=False), "**Conversation:** 0 turns | **Total Messages:** 0", "**Conversation:** 0 turns | **Total Messages:** 0", "", "", f"**Model A:** {model_a}", f"**Model B:** {model_b}"
|
| 309 |
+
|
| 310 |
+
def run_sandbox_code(sandbox_state: dict, code: str, dependencies: tuple) -> tuple[str, str, str]:
|
| 311 |
+
"""Run code in the appropriate sandbox environment"""
|
| 312 |
+
if not code.strip():
|
| 313 |
+
return "", "", "No code to run"
|
| 314 |
+
|
| 315 |
+
# Update sandbox state
|
| 316 |
+
sandbox_state['code_to_execute'] = code
|
| 317 |
+
sandbox_state['code_dependencies'] = dependencies
|
| 318 |
+
|
| 319 |
+
# Determine environment
|
| 320 |
+
env = sandbox_state.get('auto_selected_sandbox_environment') or sandbox_state.get('sandbox_environment')
|
| 321 |
+
|
| 322 |
+
try:
|
| 323 |
+
if env == SandboxEnvironment.HTML:
|
| 324 |
+
sandbox_url, sandbox_id, stderr = run_html_sandbox(code, dependencies, sandbox_state.get('sandbox_id'))
|
| 325 |
+
sandbox_state['sandbox_id'] = sandbox_id
|
| 326 |
+
return sandbox_url, "", stderr
|
| 327 |
+
|
| 328 |
+
elif env == SandboxEnvironment.REACT:
|
| 329 |
+
result = run_react_sandbox(code, dependencies, sandbox_state.get('sandbox_id'))
|
| 330 |
+
sandbox_state['sandbox_id'] = result['sandbox_id']
|
| 331 |
+
return result['sandbox_url'], "", result['stderr']
|
| 332 |
+
|
| 333 |
+
elif env == SandboxEnvironment.VUE:
|
| 334 |
+
result = run_vue_sandbox(code, dependencies, sandbox_state.get('sandbox_id'))
|
| 335 |
+
sandbox_state['sandbox_id'] = result['sandbox_id']
|
| 336 |
+
return result['sandbox_url'], "", result['stderr']
|
| 337 |
+
|
| 338 |
+
elif env == SandboxEnvironment.PYGAME:
|
| 339 |
+
result = run_pygame_sandbox(code, dependencies, sandbox_state.get('sandbox_id'))
|
| 340 |
+
sandbox_state['sandbox_id'] = result['sandbox_id']
|
| 341 |
+
return result['sandbox_url'], "", result['stderr']
|
| 342 |
+
|
| 343 |
+
elif env == SandboxEnvironment.GRADIO:
|
| 344 |
+
sandbox_url, sandbox_id, stderr = run_gradio_sandbox(code, dependencies, sandbox_state.get('sandbox_id'))
|
| 345 |
+
sandbox_state['sandbox_id'] = sandbox_id
|
| 346 |
+
return sandbox_url, "", stderr
|
| 347 |
+
|
| 348 |
+
elif env == SandboxEnvironment.STREAMLIT:
|
| 349 |
+
sandbox_url, sandbox_id, stderr = run_streamlit_sandbox(code, dependencies, sandbox_state.get('sandbox_id'))
|
| 350 |
+
sandbox_state['sandbox_id'] = sandbox_id
|
| 351 |
+
return sandbox_url, "", stderr
|
| 352 |
+
|
| 353 |
+
elif env == SandboxEnvironment.MERMAID:
|
| 354 |
+
# Convert Mermaid to HTML and run in HTML sandbox
|
| 355 |
+
html_code = mermaid_to_html(code, theme='light')
|
| 356 |
+
sandbox_url, sandbox_id, stderr = run_html_sandbox(html_code, dependencies, sandbox_state.get('sandbox_id'))
|
| 357 |
+
sandbox_state['sandbox_id'] = sandbox_id
|
| 358 |
+
return sandbox_url, "", stderr
|
| 359 |
+
|
| 360 |
+
elif env == SandboxEnvironment.PYTHON_RUNNER:
|
| 361 |
+
output, stderr = run_code_interpreter(code, 'python', dependencies)
|
| 362 |
+
return "", output, stderr
|
| 363 |
+
|
| 364 |
+
elif env == SandboxEnvironment.JAVASCRIPT_RUNNER:
|
| 365 |
+
html_code = javascript_to_html(code)
|
| 366 |
+
output, stderr = run_html_sandbox(html_code, dependencies, sandbox_state.get('sandbox_id'))
|
| 367 |
+
return "", output, stderr
|
| 368 |
+
|
| 369 |
+
elif env == SandboxEnvironment.C_RUNNER:
|
| 370 |
+
output, stderr = run_c_code(code, sandbox_state.get('sandbox_id'))
|
| 371 |
+
return "", output, stderr
|
| 372 |
+
|
| 373 |
+
elif env == SandboxEnvironment.CPP_RUNNER:
|
| 374 |
+
output, stderr = run_cpp_code(code, sandbox_state.get('sandbox_id'))
|
| 375 |
+
return "", output, stderr
|
| 376 |
+
|
| 377 |
+
elif env == SandboxEnvironment.JAVA_RUNNER:
|
| 378 |
+
output, stderr = run_java_code(code, sandbox_state.get('sandbox_id'))
|
| 379 |
+
return "", output, stderr
|
| 380 |
+
|
| 381 |
+
elif env == SandboxEnvironment.GOLANG_RUNNER:
|
| 382 |
+
output, stderr = run_golang_code(code, sandbox_state.get('sandbox_id'))
|
| 383 |
+
return "", output, stderr
|
| 384 |
+
|
| 385 |
+
elif env == SandboxEnvironment.RUST_RUNNER:
|
| 386 |
+
output, stderr = run_rust_code(code, sandbox_state.get('sandbox_id'))
|
| 387 |
+
return "", output, stderr
|
| 388 |
+
|
| 389 |
+
else:
|
| 390 |
+
# Fallback to Python runner
|
| 391 |
+
output, stderr = run_code_interpreter(code, 'python', dependencies)
|
| 392 |
+
return "", output, stderr
|
| 393 |
+
|
| 394 |
+
except Exception as e:
|
| 395 |
+
return "", "", str(e)
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def build_ui():
|
| 400 |
+
"""Build a UI for the coding arena with integrated sandbox"""
|
| 401 |
+
|
| 402 |
+
# Get random models for this session
|
| 403 |
+
model_a, model_b = get_random_models()
|
| 404 |
+
|
| 405 |
+
with gr.Blocks(title="BigCodeArena") as demo:
|
| 406 |
+
gr.Markdown("# BigCodeArena - Start Your Vibe Coding!")
|
| 407 |
+
|
| 408 |
+
# Model display (non-interactive)
|
| 409 |
+
with gr.Row():
|
| 410 |
+
with gr.Column():
|
| 411 |
+
model_display_a = gr.Markdown(f"**Model A:** {model_a}", visible=False)
|
| 412 |
+
with gr.Column():
|
| 413 |
+
model_display_b = gr.Markdown(f"**Model B:** {model_b}", visible=False)
|
| 414 |
+
|
| 415 |
+
# Sandbox section with tabs for each model - Collapsible and open by default
|
| 416 |
+
with gr.Accordion("🏗️ Code Execution & Sandbox", open=True):
|
| 417 |
+
|
| 418 |
+
with gr.Row():
|
| 419 |
+
# Model A Sandbox
|
| 420 |
+
with gr.Column():
|
| 421 |
+
gr.Markdown("### Model A Sandbox")
|
| 422 |
+
with gr.Tabs():
|
| 423 |
+
with gr.Tab("View"):
|
| 424 |
+
sandbox_view_a = gr.Markdown("**Sandbox output will appear here automatically**")
|
| 425 |
+
sandbox_component_a = SandboxComponent(
|
| 426 |
+
value=("", False, []),
|
| 427 |
+
label="Model A Sandbox",
|
| 428 |
+
visible=False
|
| 429 |
+
)
|
| 430 |
+
with gr.Tab("Code"):
|
| 431 |
+
code_a = gr.Code(
|
| 432 |
+
label="Extracted Code",
|
| 433 |
+
language="python",
|
| 434 |
+
lines=8,
|
| 435 |
+
interactive=False
|
| 436 |
+
)
|
| 437 |
+
|
| 438 |
+
# Model B Sandbox
|
| 439 |
+
with gr.Column():
|
| 440 |
+
gr.Markdown("### Model B Sandbox")
|
| 441 |
+
with gr.Tabs():
|
| 442 |
+
with gr.Tab("View"):
|
| 443 |
+
sandbox_view_b = gr.Markdown("**Sandbox output will appear here automatically**")
|
| 444 |
+
sandbox_component_b = SandboxComponent(
|
| 445 |
+
value=("", False, []),
|
| 446 |
+
label="Model B Sandbox",
|
| 447 |
+
visible=False
|
| 448 |
+
)
|
| 449 |
+
with gr.Tab("Code"):
|
| 450 |
+
code_b = gr.Code(
|
| 451 |
+
label="Extracted Code",
|
| 452 |
+
language="python",
|
| 453 |
+
lines=8,
|
| 454 |
+
interactive=False
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
# Main chat interface - Collapsible and hidden by default
|
| 458 |
+
with gr.Accordion("💬 Chat Interface", open=False):
|
| 459 |
+
with gr.Row():
|
| 460 |
+
with gr.Column():
|
| 461 |
+
gr.Markdown("## Model A")
|
| 462 |
+
chatbot_a = gr.Chatbot(
|
| 463 |
+
label="Model A",
|
| 464 |
+
height=300,
|
| 465 |
+
show_copy_button=True,
|
| 466 |
+
type="messages"
|
| 467 |
+
)
|
| 468 |
+
chat_stats_a = gr.Markdown("**Conversation:** 0 turns")
|
| 469 |
+
|
| 470 |
+
with gr.Column():
|
| 471 |
+
gr.Markdown("## Model B")
|
| 472 |
+
chatbot_b = gr.Chatbot(
|
| 473 |
+
label="Model B",
|
| 474 |
+
height=300,
|
| 475 |
+
show_copy_button=True,
|
| 476 |
+
type="messages"
|
| 477 |
+
)
|
| 478 |
+
chat_stats_b = gr.Markdown("**Conversation:** 0 turns")
|
| 479 |
+
|
| 480 |
+
# Input section
|
| 481 |
+
with gr.Row():
|
| 482 |
+
text_input = gr.Textbox(
|
| 483 |
+
label="Enter your coding prompt",
|
| 484 |
+
placeholder="e.g., 'Write a Python function to calculate fibonacci numbers'",
|
| 485 |
+
lines=1
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
# Control buttons
|
| 489 |
+
with gr.Row():
|
| 490 |
+
send_btn = gr.Button("🚀 Send to Both Models", variant="primary", size="lg")
|
| 491 |
+
clear_btn = gr.Button("🗑️ Clear Chat", variant="secondary")
|
| 492 |
+
refresh_models_btn = gr.Button("🔄 New Random Models", variant="secondary")
|
| 493 |
+
|
| 494 |
+
# Advanced Settings (Collapsible)
|
| 495 |
+
with gr.Accordion("⚙️ Advanced Settings", open=False):
|
| 496 |
+
with gr.Row():
|
| 497 |
+
with gr.Column(scale=1):
|
| 498 |
+
temperature = gr.Slider(
|
| 499 |
+
minimum=0.0,
|
| 500 |
+
maximum=1.0,
|
| 501 |
+
value=0.7,
|
| 502 |
+
step=0.1,
|
| 503 |
+
label="Temperature"
|
| 504 |
+
)
|
| 505 |
+
with gr.Column(scale=1):
|
| 506 |
+
max_tokens = gr.Slider(
|
| 507 |
+
minimum=100,
|
| 508 |
+
maximum=4000,
|
| 509 |
+
value=1000,
|
| 510 |
+
step=100,
|
| 511 |
+
label="Max Tokens"
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
# Event handlers
|
| 515 |
+
# Create state variables for the run buttons
|
| 516 |
+
state0_var = gr.State()
|
| 517 |
+
state1_var = gr.State()
|
| 518 |
+
|
| 519 |
+
# Create response components (hidden but needed for outputs)
|
| 520 |
+
response_a = gr.Markdown("", visible=False)
|
| 521 |
+
response_b = gr.Markdown("", visible=False)
|
| 522 |
+
|
| 523 |
+
# Create a wrapper function that handles both the main execution and state update
|
| 524 |
+
def send_and_update_state(state0, state1, text, temp, max_tok, model_a, model_b):
|
| 525 |
+
print(f"DEBUG: send_and_update_state called with text: {text[:50] if text else 'None'}")
|
| 526 |
+
# Call the main function
|
| 527 |
+
result = add_text_and_generate(state0, state1, text, temp, max_tok, model_a, model_b)
|
| 528 |
+
# Extract the state from the result
|
| 529 |
+
new_state0, new_state1 = result[0], result[1]
|
| 530 |
+
print(f"DEBUG: send_and_update_state returning new_state0: {type(new_state0)}, new_state1: {type(new_state1)}")
|
| 531 |
+
# Return all the original outputs plus the updated state for run buttons
|
| 532 |
+
# Make sure all outputs are properly formatted for their expected types
|
| 533 |
+
return (
|
| 534 |
+
new_state0, # state0
|
| 535 |
+
new_state1, # state1
|
| 536 |
+
result[2], # chatbot_a (chat0)
|
| 537 |
+
result[3], # chatbot_b (chat1)
|
| 538 |
+
result[4], # response_a (response0)
|
| 539 |
+
result[5], # response_b (response1)
|
| 540 |
+
result[6], # code_a (code0)
|
| 541 |
+
result[7], # code_b (code1)
|
| 542 |
+
result[10], # sandbox_state0
|
| 543 |
+
result[11], # sandbox_state1
|
| 544 |
+
result[12], # sandbox_output0
|
| 545 |
+
result[13], # sandbox_output1
|
| 546 |
+
result[14], # sandbox_component_update0
|
| 547 |
+
result[15], # sandbox_component_update1
|
| 548 |
+
result[16], # chat_stats_a
|
| 549 |
+
result[17], # chat_stats_b
|
| 550 |
+
result[18], # sandbox_view_a
|
| 551 |
+
result[19], # sandbox_view_b
|
| 552 |
+
new_state0, # state0_var
|
| 553 |
+
new_state1, # state1_var
|
| 554 |
+
"", # Clear text input
|
| 555 |
+
f"**Model A:** {model_a}", # Update model display A
|
| 556 |
+
f"**Model B:** {model_b}", # Update model display B
|
| 557 |
+
)
|
| 558 |
+
|
| 559 |
+
send_btn.click(
|
| 560 |
+
fn=send_and_update_state,
|
| 561 |
+
inputs=[
|
| 562 |
+
state0_var, # state0
|
| 563 |
+
state1_var, # state1
|
| 564 |
+
text_input,
|
| 565 |
+
temperature,
|
| 566 |
+
max_tokens,
|
| 567 |
+
gr.State(model_a), # Use fixed model A
|
| 568 |
+
gr.State(model_b) # Use fixed model B
|
| 569 |
+
],
|
| 570 |
+
outputs=[
|
| 571 |
+
state0_var, # state0
|
| 572 |
+
state1_var, # state1
|
| 573 |
+
chatbot_a,
|
| 574 |
+
chatbot_b,
|
| 575 |
+
response_a,
|
| 576 |
+
response_b,
|
| 577 |
+
code_a,
|
| 578 |
+
code_b,
|
| 579 |
+
gr.State(), # sandbox_state0
|
| 580 |
+
gr.State(), # sandbox_state1
|
| 581 |
+
sandbox_view_a, # sandbox output for model A
|
| 582 |
+
sandbox_view_b, # sandbox output for model B
|
| 583 |
+
sandbox_component_a, # sandbox component for model A
|
| 584 |
+
sandbox_component_b, # sandbox component for model B
|
| 585 |
+
chat_stats_a, # Conversation statistics for model A
|
| 586 |
+
chat_stats_b, # Conversation statistics for model B
|
| 587 |
+
sandbox_view_a, # Sandbox view for model A
|
| 588 |
+
sandbox_view_b, # Sandbox view for model B
|
| 589 |
+
state0_var, # Updated state for run button A
|
| 590 |
+
state1_var, # Updated state for run button B
|
| 591 |
+
text_input, # Clear the text input after sending
|
| 592 |
+
model_display_a, # Update model display A
|
| 593 |
+
model_display_b, # Update model display B
|
| 594 |
+
]
|
| 595 |
+
)
|
| 596 |
+
|
| 597 |
+
clear_btn.click(
|
| 598 |
+
fn=clear_chat,
|
| 599 |
+
inputs=[gr.State(), gr.State()],
|
| 600 |
+
outputs=[
|
| 601 |
+
gr.State(None),
|
| 602 |
+
gr.State(None),
|
| 603 |
+
chatbot_a,
|
| 604 |
+
chatbot_b,
|
| 605 |
+
response_a,
|
| 606 |
+
response_b,
|
| 607 |
+
code_a,
|
| 608 |
+
code_b,
|
| 609 |
+
gr.State(None),
|
| 610 |
+
gr.State(None),
|
| 611 |
+
sandbox_view_a,
|
| 612 |
+
sandbox_view_b,
|
| 613 |
+
sandbox_component_a,
|
| 614 |
+
sandbox_component_b,
|
| 615 |
+
state0_var, # Reset state for run button A
|
| 616 |
+
state1_var, # Reset state for run button B
|
| 617 |
+
chat_stats_a, # Reset conversation statistics for model A
|
| 618 |
+
chat_stats_b, # Reset conversation statistics for model B
|
| 619 |
+
sandbox_view_a, # Reset sandbox view for model A
|
| 620 |
+
sandbox_view_b, # Reset sandbox view for model B
|
| 621 |
+
model_display_a, # Reset model display A
|
| 622 |
+
model_display_b, # Reset model display B
|
| 623 |
+
]
|
| 624 |
+
)
|
| 625 |
+
|
| 626 |
+
# Refresh models button handler
|
| 627 |
+
def refresh_models():
|
| 628 |
+
new_model_a, new_model_b = get_random_models()
|
| 629 |
+
return (
|
| 630 |
+
None, # Reset state0
|
| 631 |
+
None, # Reset state1
|
| 632 |
+
"", # Clear chat A
|
| 633 |
+
"", # Clear chat B
|
| 634 |
+
"", # Clear response A
|
| 635 |
+
"", # Clear response B
|
| 636 |
+
"", # Clear code A
|
| 637 |
+
"", # Clear code B
|
| 638 |
+
gr.State(None), # Reset sandbox state A
|
| 639 |
+
gr.State(None), # Reset sandbox state B
|
| 640 |
+
"", # Clear sandbox view A
|
| 641 |
+
"", # Clear sandbox view B
|
| 642 |
+
gr.update(visible=False), # Hide sandbox component A
|
| 643 |
+
gr.update(visible=False), # Hide sandbox component B
|
| 644 |
+
"**Conversation:** 0 turns | **Total Messages:** 0", # Reset stats A
|
| 645 |
+
"**Conversation:** 0 turns | **Total Messages:** 0", # Reset stats B
|
| 646 |
+
"", # Clear sandbox view A
|
| 647 |
+
"", # Clear sandbox view B
|
| 648 |
+
None, # Reset state0_var
|
| 649 |
+
None, # Reset state1_var
|
| 650 |
+
f"**Model A:** {new_model_a}", # Update model display A
|
| 651 |
+
f"**Model B:** {new_model_b}", # Update model display B
|
| 652 |
+
)
|
| 653 |
+
|
| 654 |
+
refresh_models_btn.click(
|
| 655 |
+
fn=refresh_models,
|
| 656 |
+
inputs=[],
|
| 657 |
+
outputs=[
|
| 658 |
+
state0_var,
|
| 659 |
+
state1_var,
|
| 660 |
+
chatbot_a,
|
| 661 |
+
chatbot_b,
|
| 662 |
+
response_a,
|
| 663 |
+
response_b,
|
| 664 |
+
code_a,
|
| 665 |
+
code_b,
|
| 666 |
+
gr.State(None),
|
| 667 |
+
gr.State(None),
|
| 668 |
+
sandbox_view_a,
|
| 669 |
+
sandbox_view_b,
|
| 670 |
+
sandbox_component_a,
|
| 671 |
+
sandbox_component_b,
|
| 672 |
+
chat_stats_a,
|
| 673 |
+
chat_stats_b,
|
| 674 |
+
sandbox_view_a,
|
| 675 |
+
sandbox_view_b,
|
| 676 |
+
state0_var,
|
| 677 |
+
state1_var,
|
| 678 |
+
model_display_a, # Update model display A
|
| 679 |
+
model_display_b, # Update model display B
|
| 680 |
+
]
|
| 681 |
+
)
|
| 682 |
+
|
| 683 |
+
# Examples
|
| 684 |
+
gr.Examples(
|
| 685 |
+
examples=[
|
| 686 |
+
["使用SVG绘制春节主题的动态图案,包括:1)一个红色的灯笼,带有金色的流苏 2)一个金色的福字,使用书法字体 3)背景添加一些烟花效果 4)在灯笼和福字周围添加一些祥云图案。确保图案布局美观,颜色搭配符合春节传统风格。"],
|
| 687 |
+
["SVGを使用して日本の伝統的な和柄パターンを描画してください。1)波紋(さざなみ)模様 2)市松模様 3)麻の葉模様 4)雷文(らいもん)模様を含めてください。色は伝統的な日本の色(藍色、朱色、金色など)を使用し、レイアウトはバランスよく配置してください。"],
|
| 688 |
+
["Write HTML with P5.js that simulates 25 particles in a vacuum space of a cylindrical container, bouncing within its boundaries. Use different colors for each ball and ensure they leave a trail showing their movement. Add a slow rotation of the container to give better view of what's going on in the scene. Make sure to create proper collision detection and physic rules to ensure particles remain in the container. Add an external spherical container. Add a slow zoom in and zoom out effect to the whole scene."],
|
| 689 |
+
["Write a Python script to scrape NVIDIA's stock price for the past month using the yfinance library. Clean the data and create an interactive visualization using Matplotlib. Include: 1) A candlestick chart showing daily price movements 2) A line chart with 7-day and 30-day moving averages. Add hover tooltips showing exact values and date. Make the layout professional with proper titles and axis labels."],
|
| 690 |
+
["Write a Python script that uses the Gradio library to create a functional calculator. The calculator should support basic arithmetic operations: addition, subtraction, multiplication, and division. It should have two input fields for numbers and a dropdown menu to select the operation."],
|
| 691 |
+
["Write a Todo list app using React.js. The app should allow users to add, delete, and mark tasks as completed. Include features like filtering tasks by status (completed, active), sorting tasks by priority, and displaying the total number of tasks."],
|
| 692 |
+
["Write a Python script using the Streamlit library to create a web application for uploading and displaying files. The app should allow users to upload files of type .csv or .txt. If a .csv file is uploaded, display its contents as a table using Streamlit's st.dataframe() method. If a .txt file is uploaded, display its content as plain text."],
|
| 693 |
+
["Write a Python function to solve the Trapping Rain Water problem. The function should take a list of non-negative integers representing the height of bars in a histogram and return the total amount of water trapped between the bars after raining. Use an efficient algorithm with a time complexity of O(n)."],
|
| 694 |
+
["Create a simple Pygame script for a game where the player controls a bouncing ball that changes direction when it collides with the edges of the window. Add functionality for the player to control a paddle using arrow keys, aiming to keep the ball from touching the bottom of the screen. Include basic collision detection and a scoring system that increases as the ball bounces off the paddle. You need to add clickable buttons to start the game, and reset the game."],
|
| 695 |
+
["Create a financial management Dashboard using Vue.js, focusing on local data handling without APIs. Include features like a clean dashboard for tracking income and expenses, dynamic charts for visualizing finances, and a budget planner. Implement functionalities for adding, editing, and deleting transactions, as well as filtering by date or category. Ensure responsive design and smooth user interaction for an intuitive experience."],
|
| 696 |
+
["Create a Mermaid diagram to visualize a flowchart of a user login process. Include the following steps: User enters login credentials; Credentials are validated; If valid, the user is directed to the dashboard; If invalid, an error message is shown, and the user can retry or reset the password."],
|
| 697 |
+
["Write a Python function to calculate the Fibonacci sequence up to n numbers. Then write test cases to verify the function works correctly for edge cases like negative numbers, zero, and large inputs."],
|
| 698 |
+
["Build an HTML page for a Kanban board with three columns with Vue.js: To Do, In Progress, and Done. Each column should allow adding, moving, and deleting tasks. Implement drag-and-drop functionality using Vue Draggable and persist the state using Vuex."],
|
| 699 |
+
["Develop a Streamlit app that takes a CSV file as input and provides: 1) Basic statistics about the data 2) Interactive visualizations using Plotly 3) A data cleaning interface with options to handle missing values 4) An option to download the cleaned data."],
|
| 700 |
+
["Write an HTML page with embedded JavaScript that creates an interactive periodic table. Each element should display its properties on hover and allow filtering by category (metals, non-metals, etc.). Include a search bar to find elements by name or symbol."],
|
| 701 |
+
["Here's a Python function that sorts a list of dictionaries by a specified key:\n\n```python\ndef sort_dicts(data, key):\n return sorted(data, key=lambda x: x[key])\n```\n\nWrite test cases to verify the function works correctly for edge cases like empty lists, missing keys, and different data types. If you use unittest, please use `unittest.main(argv=['first-arg-is-ignored'], exit=False)` to run the tests."],
|
| 702 |
+
["Create a React component for a fitness tracker that shows: 1) Daily step count 2) Calories burned 3) Distance walked 4) A progress bar for daily goals."],
|
| 703 |
+
["Build a Vue.js dashboard for monitoring server health. Include: 1) Real-time CPU and memory usage graphs 2) Disk space visualization 3) Network activity monitor 4) Alerts for critical thresholds."],
|
| 704 |
+
["Write a C program that calculates and prints the first 100 prime numbers in a formatted table with 10 numbers per row. Include a function to check if a number is prime and use it in your solution."],
|
| 705 |
+
["Write a C++ program that implements a simple calculator using object-oriented programming. Create a Calculator class with methods for addition, subtraction, multiplication, and division. Include error handling for division by zero."],
|
| 706 |
+
["Write a Rust program that generates and prints a Pascal's Triangle with 10 rows. Format the output to center-align the numbers in each row."],
|
| 707 |
+
["Write a Java program that simulates a simple bank account system. Create a BankAccount class with methods for deposit, withdrawal, and balance inquiry. Include error handling for insufficient funds and demonstrate its usage with a few transactions."],
|
| 708 |
+
["Write a Go program that calculates and prints the Fibonacci sequence up to the 50th number. Format the output in a table with 5 numbers per row and include the index of each Fibonacci number."],
|
| 709 |
+
["Write a C program that calculates and prints a histogram of letter frequencies from a predefined string. Use ASCII art to display the histogram vertically."],
|
| 710 |
+
["Write a C++ program that implements a simple stack data structure with push, pop, and peek operations. Demonstrate its usage by reversing a predefined string using the stack."],
|
| 711 |
+
["Write a Rust program that calculates and prints the first 20 happy numbers. Include a function to check if a number is happy and use it in your solution."],
|
| 712 |
+
["Write a Java program that implements a simple binary search algorithm. Create a sorted array of integers and demonstrate searching for different values, including cases where the value is found and not found."],
|
| 713 |
+
["Write a Go program that generates and prints a multiplication table from 1 to 12. Format the output in a neat grid with proper alignment."],
|
| 714 |
+
],
|
| 715 |
+
example_labels=[
|
| 716 |
+
"🏮 春节主题图案",
|
| 717 |
+
"🎎 日本の伝統的な和柄パターン",
|
| 718 |
+
"🌐 Particles in a Spherical Container",
|
| 719 |
+
"💹 NVIDIA Stock Analysis with Matplotlib",
|
| 720 |
+
"🧮 Calculator with Gradio",
|
| 721 |
+
"📝 Todo List App with React.js",
|
| 722 |
+
"📂 File Upload Web App with Streamlit",
|
| 723 |
+
"💦 Solve Trapping Rain Water Problem",
|
| 724 |
+
"🎮 Pygame Bouncing Ball Game",
|
| 725 |
+
"💳 Financial Dashboard with Vue.js",
|
| 726 |
+
"🔑 User Login Process Flowchart",
|
| 727 |
+
"🔢 Fibonacci Sequence with Tests",
|
| 728 |
+
"📌 Vue Kanban Board",
|
| 729 |
+
"🧹 Streamlit Data Cleaning App",
|
| 730 |
+
"⚗️ Interactive Periodic Table with React",
|
| 731 |
+
"📚 Dictionary Sorting Tests in Python",
|
| 732 |
+
"🏋️♂️ Fitness Tracker with React",
|
| 733 |
+
"🖥️ Vue Server Monitoring",
|
| 734 |
+
"🔢 Prime Numbers in C",
|
| 735 |
+
"🧮 OOP Calculator in C++",
|
| 736 |
+
"🔷 Pascal's Triangle in Rust",
|
| 737 |
+
"🏛️ Bank Account Simulation in Java",
|
| 738 |
+
"🐰 Fibonacci Sequence in Go",
|
| 739 |
+
"📊 Letter Frequency Histogram in C",
|
| 740 |
+
"📦 Stack Implementation in C++",
|
| 741 |
+
"😄 Happy Numbers in Rust",
|
| 742 |
+
"🔎 Binary Search in Java",
|
| 743 |
+
"✖️ Multiplication Table in Go",
|
| 744 |
+
],
|
| 745 |
+
examples_per_page=100,
|
| 746 |
+
label="Example Prompts",
|
| 747 |
+
inputs=[text_input],
|
| 748 |
+
)
|
| 749 |
+
|
| 750 |
+
return demo
|
| 751 |
+
|
| 752 |
+
def main():
|
| 753 |
+
"""Main function to run the Simple BigCodeArena app"""
|
| 754 |
+
print("🚀 Starting Simple BigCodeArena...")
|
| 755 |
+
if available_models:
|
| 756 |
+
print(f"🔍 Available models: {', '.join(available_models)}")
|
| 757 |
+
# Get random models for this session
|
| 758 |
+
model_a, model_b = get_random_models()
|
| 759 |
+
print(f"🎲 Randomly selected models for this session:")
|
| 760 |
+
print(f" Model A: {model_a}")
|
| 761 |
+
print(f" Model B: {model_b}")
|
| 762 |
+
else:
|
| 763 |
+
print("⚠️ No models found in config!")
|
| 764 |
+
|
| 765 |
+
# Build the UI
|
| 766 |
+
demo = build_ui()
|
| 767 |
+
|
| 768 |
+
# Launch the app
|
| 769 |
+
demo.launch(
|
| 770 |
+
server_name="0.0.0.0",
|
| 771 |
+
server_port=7860,
|
| 772 |
+
share=False,
|
| 773 |
+
debug=True
|
| 774 |
+
)
|
| 775 |
+
|
| 776 |
+
if __name__ == "__main__":
|
| 777 |
+
main()
|
chat_state.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Chat State and Logging
|
| 3 |
+
'''
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
from typing import Any, Literal, Optional
|
| 8 |
+
from conversation import Conversation
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
import datetime
|
| 12 |
+
import uuid
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
LOG_DIR = os.getenv("LOGDIR", "./logs")
|
| 16 |
+
'''
|
| 17 |
+
The default output dir of log files
|
| 18 |
+
'''
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class ModelChatState:
|
| 22 |
+
'''
|
| 23 |
+
The state of a chat with a model.
|
| 24 |
+
'''
|
| 25 |
+
|
| 26 |
+
is_vision: bool
|
| 27 |
+
'''
|
| 28 |
+
Whether the model is vision based.
|
| 29 |
+
'''
|
| 30 |
+
|
| 31 |
+
conv: Conversation
|
| 32 |
+
'''
|
| 33 |
+
The conversation
|
| 34 |
+
'''
|
| 35 |
+
|
| 36 |
+
conv_id: str
|
| 37 |
+
'''
|
| 38 |
+
Unique identifier for the model conversation.
|
| 39 |
+
Unique per chat per model.
|
| 40 |
+
'''
|
| 41 |
+
|
| 42 |
+
chat_session_id: str
|
| 43 |
+
'''
|
| 44 |
+
Unique identifier for the chat session.
|
| 45 |
+
Unique per chat. The two battle models share the same chat session id.
|
| 46 |
+
'''
|
| 47 |
+
|
| 48 |
+
skip_next: bool
|
| 49 |
+
'''
|
| 50 |
+
Flag to indicate skipping the next operation.
|
| 51 |
+
'''
|
| 52 |
+
|
| 53 |
+
model_name: str
|
| 54 |
+
'''
|
| 55 |
+
Name of the model being used.
|
| 56 |
+
'''
|
| 57 |
+
|
| 58 |
+
oai_thread_id: Optional[str]
|
| 59 |
+
'''
|
| 60 |
+
Identifier for the OpenAI thread.
|
| 61 |
+
'''
|
| 62 |
+
|
| 63 |
+
has_csam_image: bool
|
| 64 |
+
'''
|
| 65 |
+
Indicates if a CSAM image has been uploaded.
|
| 66 |
+
'''
|
| 67 |
+
|
| 68 |
+
regen_support: bool
|
| 69 |
+
'''
|
| 70 |
+
Indicates if regeneration is supported for the model.
|
| 71 |
+
'''
|
| 72 |
+
|
| 73 |
+
chat_start_time: datetime.datetime
|
| 74 |
+
'''
|
| 75 |
+
Chat start time.
|
| 76 |
+
'''
|
| 77 |
+
|
| 78 |
+
chat_mode: Literal['battle_anony', 'battle_named', 'direct']
|
| 79 |
+
'''
|
| 80 |
+
Chat mode.
|
| 81 |
+
'''
|
| 82 |
+
|
| 83 |
+
curr_response_type: Literal['chat_multi', 'chat_single', 'regenerate_multi', 'regenerate_single'] | None
|
| 84 |
+
'''
|
| 85 |
+
Current response type. Used for logging.
|
| 86 |
+
'''
|
| 87 |
+
|
| 88 |
+
@staticmethod
|
| 89 |
+
def create_chat_session_id() -> str:
|
| 90 |
+
'''
|
| 91 |
+
Create a new chat session id.
|
| 92 |
+
'''
|
| 93 |
+
return uuid.uuid4().hex
|
| 94 |
+
|
| 95 |
+
@staticmethod
|
| 96 |
+
def create_battle_chat_states(
|
| 97 |
+
model_name_1: str, model_name_2: str,
|
| 98 |
+
chat_mode: Literal['battle_anony', 'battle_named'],
|
| 99 |
+
is_vision: bool,
|
| 100 |
+
) -> tuple['ModelChatState', 'ModelChatState']:
|
| 101 |
+
'''
|
| 102 |
+
Create two chat states for a battle.
|
| 103 |
+
'''
|
| 104 |
+
chat_session_id = ModelChatState.create_chat_session_id()
|
| 105 |
+
return (
|
| 106 |
+
ModelChatState(model_name_1, chat_mode,
|
| 107 |
+
is_vision=is_vision,
|
| 108 |
+
chat_session_id=chat_session_id),
|
| 109 |
+
ModelChatState(model_name_2, chat_mode,
|
| 110 |
+
is_vision=is_vision,
|
| 111 |
+
chat_session_id=chat_session_id),
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def __init__(self,
|
| 116 |
+
model_name: str,
|
| 117 |
+
chat_mode: Literal['battle_anony', 'battle_named', 'direct'],
|
| 118 |
+
is_vision: bool,
|
| 119 |
+
chat_session_id: str | None = None,
|
| 120 |
+
):
|
| 121 |
+
from fastchat.model.model_adapter import get_conversation_template
|
| 122 |
+
|
| 123 |
+
self.conv = get_conversation_template(model_name)
|
| 124 |
+
self.conv_id = uuid.uuid4().hex
|
| 125 |
+
# if no chat session id is provided, use the conversation id
|
| 126 |
+
self.chat_session_id = chat_session_id if chat_session_id else self.conv_id
|
| 127 |
+
self.chat_start_time = datetime.datetime.now()
|
| 128 |
+
self.chat_mode = chat_mode
|
| 129 |
+
|
| 130 |
+
self.skip_next = False
|
| 131 |
+
self.model_name = model_name
|
| 132 |
+
self.oai_thread_id = None
|
| 133 |
+
self.is_vision = is_vision
|
| 134 |
+
|
| 135 |
+
# NOTE(chris): This could be sort of a hack since it assumes the user only uploads one image. If they can upload multiple, we should store a list of image hashes.
|
| 136 |
+
self.has_csam_image = False
|
| 137 |
+
|
| 138 |
+
self.regen_support = True
|
| 139 |
+
if "browsing" in model_name:
|
| 140 |
+
self.regen_support = False
|
| 141 |
+
self.init_system_prompt(self.conv, is_vision)
|
| 142 |
+
|
| 143 |
+
def init_system_prompt(self, conv, is_vision):
|
| 144 |
+
system_prompt = conv.get_system_message(is_vision)
|
| 145 |
+
if len(system_prompt) == 0:
|
| 146 |
+
return
|
| 147 |
+
current_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
| 148 |
+
system_prompt = system_prompt.replace("{{currentDateTime}}", current_date)
|
| 149 |
+
|
| 150 |
+
current_date_v2 = datetime.datetime.now().strftime("%d %b %Y")
|
| 151 |
+
system_prompt = system_prompt.replace("{{currentDateTimev2}}", current_date_v2)
|
| 152 |
+
|
| 153 |
+
current_date_v3 = datetime.datetime.now().strftime("%B %Y")
|
| 154 |
+
system_prompt = system_prompt.replace("{{currentDateTimev3}}", current_date_v3)
|
| 155 |
+
conv.set_system_message(system_prompt)
|
| 156 |
+
|
| 157 |
+
def set_response_type(
|
| 158 |
+
self,
|
| 159 |
+
response_type: Literal['chat_multi', 'chat_single', 'regenerate_multi', 'regenerate_single']
|
| 160 |
+
):
|
| 161 |
+
'''
|
| 162 |
+
Set the response type for the chat state.
|
| 163 |
+
'''
|
| 164 |
+
self.curr_response_type = response_type
|
| 165 |
+
|
| 166 |
+
def to_gradio_chatbot(self):
|
| 167 |
+
'''
|
| 168 |
+
Convert to a Gradio chatbot.
|
| 169 |
+
'''
|
| 170 |
+
return self.conv.to_gradio_chatbot()
|
| 171 |
+
|
| 172 |
+
def get_conv_log_filepath(self, path_prefix: str):
|
| 173 |
+
'''
|
| 174 |
+
Get the filepath for the conversation log.
|
| 175 |
+
|
| 176 |
+
Expected directory structure:
|
| 177 |
+
softwarearenlog/
|
| 178 |
+
└── YEAR_MONTH_DAY/
|
| 179 |
+
├── conv_logs/
|
| 180 |
+
└── sandbox_logs/
|
| 181 |
+
'''
|
| 182 |
+
date_str = self.chat_start_time.strftime('%Y_%m_%d')
|
| 183 |
+
filepath = os.path.join(
|
| 184 |
+
path_prefix,
|
| 185 |
+
date_str,
|
| 186 |
+
'conv_logs',
|
| 187 |
+
self.chat_mode,
|
| 188 |
+
f"conv-log-{self.chat_session_id}.json"
|
| 189 |
+
)
|
| 190 |
+
return filepath
|
| 191 |
+
|
| 192 |
+
def to_dict(self):
|
| 193 |
+
base = self.conv.to_dict()
|
| 194 |
+
base.update(
|
| 195 |
+
{
|
| 196 |
+
"chat_session_id": self.chat_session_id,
|
| 197 |
+
"conv_id": self.conv_id,
|
| 198 |
+
"chat_mode": self.chat_mode,
|
| 199 |
+
"chat_start_time": self.chat_start_time,
|
| 200 |
+
"model_name": self.model_name,
|
| 201 |
+
}
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
if self.is_vision:
|
| 205 |
+
base.update({"has_csam_image": self.has_csam_image})
|
| 206 |
+
return base
|
| 207 |
+
|
| 208 |
+
def generate_vote_record(
|
| 209 |
+
self,
|
| 210 |
+
vote_type: str,
|
| 211 |
+
ip: str
|
| 212 |
+
) -> dict[str, Any]:
|
| 213 |
+
'''
|
| 214 |
+
Generate a vote record for telemertry.
|
| 215 |
+
'''
|
| 216 |
+
data = {
|
| 217 |
+
"tstamp": round(datetime.datetime.now().timestamp(), 4),
|
| 218 |
+
"type": vote_type,
|
| 219 |
+
"model": self.model_name,
|
| 220 |
+
"state": self.to_dict(),
|
| 221 |
+
"ip": ip,
|
| 222 |
+
}
|
| 223 |
+
return data
|
| 224 |
+
|
| 225 |
+
def generate_response_record(
|
| 226 |
+
self,
|
| 227 |
+
gen_params: dict[str, Any],
|
| 228 |
+
start_ts: float,
|
| 229 |
+
end_ts: float,
|
| 230 |
+
ip: str
|
| 231 |
+
) -> dict[str, Any]:
|
| 232 |
+
'''
|
| 233 |
+
Generate a vote record for telemertry.
|
| 234 |
+
'''
|
| 235 |
+
data = {
|
| 236 |
+
"tstamp": round(datetime.datetime.now().timestamp(), 4),
|
| 237 |
+
"type": self.curr_response_type,
|
| 238 |
+
"model": self.model_name,
|
| 239 |
+
"start_ts": round(start_ts, 4),
|
| 240 |
+
"end_ts": round(end_ts, 4),
|
| 241 |
+
"gen_params": gen_params,
|
| 242 |
+
"state": self.to_dict(),
|
| 243 |
+
"ip": ip,
|
| 244 |
+
}
|
| 245 |
+
return data
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def save_log_to_local(
|
| 249 |
+
log_data: dict[str, Any],
|
| 250 |
+
log_path: str,
|
| 251 |
+
write_mode: Literal['overwrite', 'append'] = 'append'
|
| 252 |
+
):
|
| 253 |
+
'''
|
| 254 |
+
Save the log locally.
|
| 255 |
+
'''
|
| 256 |
+
log_json = json.dumps(log_data, default=str)
|
| 257 |
+
os.makedirs(os.path.dirname(log_path), exist_ok=True)
|
| 258 |
+
with open(log_path, "w" if write_mode == 'overwrite' else 'a') as fout:
|
| 259 |
+
fout.write(log_json + "\n")
|
completion.py
ADDED
|
@@ -0,0 +1,1304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import time
|
| 4 |
+
import yaml
|
| 5 |
+
import random
|
| 6 |
+
import shortuuid
|
| 7 |
+
|
| 8 |
+
import requests
|
| 9 |
+
from typing import Optional
|
| 10 |
+
import boto3
|
| 11 |
+
|
| 12 |
+
from glob import glob
|
| 13 |
+
from tqdm import tqdm
|
| 14 |
+
|
| 15 |
+
# API setting constants
|
| 16 |
+
API_MAX_RETRY = 50
|
| 17 |
+
API_RETRY_SLEEP = 10
|
| 18 |
+
API_ERROR_OUTPUT = None
|
| 19 |
+
|
| 20 |
+
registered_api_completion = {}
|
| 21 |
+
registered_engine_completion = {}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def register_api(api_type):
|
| 25 |
+
def decorator(func):
|
| 26 |
+
registered_api_completion[api_type] = func
|
| 27 |
+
return func
|
| 28 |
+
|
| 29 |
+
return decorator
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def register_engine(engine_type):
|
| 33 |
+
def decorator(func):
|
| 34 |
+
registered_engine_completion[engine_type] = func
|
| 35 |
+
return func
|
| 36 |
+
|
| 37 |
+
return decorator
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def load_questions(question_file: str):
|
| 41 |
+
"""Load questions from a file."""
|
| 42 |
+
questions = []
|
| 43 |
+
with open(question_file, "r") as ques_file:
|
| 44 |
+
for line in ques_file:
|
| 45 |
+
if line:
|
| 46 |
+
questions.append(json.loads(line))
|
| 47 |
+
return questions
|
| 48 |
+
|
| 49 |
+
def load_model_answers(answer_dir: str):
|
| 50 |
+
"""Load model answers.
|
| 51 |
+
|
| 52 |
+
The return value is a python dict of type:
|
| 53 |
+
Dict[model_name: str -> Dict[uid: int -> answer: dict]]
|
| 54 |
+
"""
|
| 55 |
+
if not os.path.exists(answer_dir):
|
| 56 |
+
return {}
|
| 57 |
+
|
| 58 |
+
filenames = []
|
| 59 |
+
for folder in os.listdir(answer_dir):
|
| 60 |
+
if not os.path.isdir(os.path.join(answer_dir, folder)):
|
| 61 |
+
continue
|
| 62 |
+
if not os.path.exists(os.path.join(answer_dir, folder, "generation.jsonl")):
|
| 63 |
+
print(f"WARNING: {folder} does not have generation.jsonl, skip it.")
|
| 64 |
+
continue
|
| 65 |
+
filenames.append(os.path.join(answer_dir, folder, "generation.jsonl"))
|
| 66 |
+
|
| 67 |
+
filenames.sort()
|
| 68 |
+
model_answers = {}
|
| 69 |
+
for filename in filenames:
|
| 70 |
+
# Use parent directory name as model name
|
| 71 |
+
model_name = os.path.basename(os.path.dirname(filename))
|
| 72 |
+
answer = {}
|
| 73 |
+
with open(filename) as fin:
|
| 74 |
+
for line in fin:
|
| 75 |
+
line = json.loads(line)
|
| 76 |
+
answer[line["uid"]] = line
|
| 77 |
+
model_answers[model_name] = answer
|
| 78 |
+
return model_answers
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def load_model_judgements(answer_dir: str):
|
| 82 |
+
"""Load model judgements.
|
| 83 |
+
|
| 84 |
+
The return value is a python dict of type:
|
| 85 |
+
Dict[model_name: str -> Dict[uid: int -> answer: dict]]
|
| 86 |
+
"""
|
| 87 |
+
filenames = glob(os.path.join(answer_dir, "*.jsonl"))
|
| 88 |
+
filenames.sort()
|
| 89 |
+
model_answers = {}
|
| 90 |
+
|
| 91 |
+
for filename in filenames:
|
| 92 |
+
model_name = os.path.basename(filename)[:-6]
|
| 93 |
+
answer = {}
|
| 94 |
+
with open(filename) as fin:
|
| 95 |
+
for line in fin:
|
| 96 |
+
line = json.loads(line)
|
| 97 |
+
answer[line["uid"]] = line
|
| 98 |
+
model_answers[model_name] = answer
|
| 99 |
+
|
| 100 |
+
return model_answers
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def load_model_answers_and_execution_results(data_dir: str):
|
| 104 |
+
"""Load model answers and execution results.
|
| 105 |
+
|
| 106 |
+
The return value is a python dict of type:
|
| 107 |
+
Dict[model_name: str -> Dict[uid: int -> answer: dict]]
|
| 108 |
+
"""
|
| 109 |
+
filenames = []
|
| 110 |
+
for folder in os.listdir(data_dir):
|
| 111 |
+
if not os.path.isdir(os.path.join(data_dir, folder)):
|
| 112 |
+
continue
|
| 113 |
+
if not os.path.exists(os.path.join(data_dir, folder, "execution_results.jsonl")):
|
| 114 |
+
print(f"WARNING: {folder} does not have execution_results.jsonl, skip it.")
|
| 115 |
+
continue
|
| 116 |
+
filenames.append(os.path.join(data_dir, folder, "execution_results.jsonl"))
|
| 117 |
+
|
| 118 |
+
filenames.sort()
|
| 119 |
+
model_answers = {}
|
| 120 |
+
|
| 121 |
+
for filename in filenames:
|
| 122 |
+
# Use parent directory name as model name
|
| 123 |
+
model_name = os.path.basename(os.path.dirname(filename))
|
| 124 |
+
answer = {}
|
| 125 |
+
with open(filename) as fin:
|
| 126 |
+
for line in fin:
|
| 127 |
+
line = json.loads(line)
|
| 128 |
+
answer[line["uid"]] = line
|
| 129 |
+
model_answers[model_name] = answer
|
| 130 |
+
|
| 131 |
+
return model_answers
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def load_id_to_model_answers(answer_dir: str):
|
| 136 |
+
"""Load model answers.
|
| 137 |
+
|
| 138 |
+
The return value is a python dict of type:
|
| 139 |
+
Dict[model_name: str -> Dict[uid: int -> answer: dict]]
|
| 140 |
+
"""
|
| 141 |
+
filenames = glob(os.path.join(answer_dir, "*.jsonl"))
|
| 142 |
+
filenames.sort()
|
| 143 |
+
model_answers = {}
|
| 144 |
+
|
| 145 |
+
for filename in filenames:
|
| 146 |
+
model_name = os.path.basename(filename)[:-6]
|
| 147 |
+
|
| 148 |
+
with open(filename) as fin:
|
| 149 |
+
for line in fin:
|
| 150 |
+
line = json.loads(line)
|
| 151 |
+
|
| 152 |
+
if line["uid"] in model_answers:
|
| 153 |
+
model_answers[line["uid"]][model_name] = line
|
| 154 |
+
else:
|
| 155 |
+
model_answers[line["uid"]] = {model_name: line}
|
| 156 |
+
|
| 157 |
+
return model_answers
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def get_endpoint(endpoint_list):
|
| 161 |
+
if endpoint_list is None:
|
| 162 |
+
return None
|
| 163 |
+
assert endpoint_list is not None
|
| 164 |
+
# randomly pick one
|
| 165 |
+
api_dict = random.choices(
|
| 166 |
+
endpoint_list
|
| 167 |
+
)[0]
|
| 168 |
+
return api_dict
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
# load config args from config yaml files
|
| 172 |
+
def make_config(config_file: str) -> dict:
|
| 173 |
+
config_kwargs = {}
|
| 174 |
+
with open(config_file, "r") as f:
|
| 175 |
+
config_kwargs = yaml.load(f, Loader=yaml.SafeLoader)
|
| 176 |
+
|
| 177 |
+
return config_kwargs
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
@register_api("openai")
|
| 181 |
+
def chat_completion_openai(model, messages, temperature, max_tokens, api_dict=None, **kwargs):
|
| 182 |
+
import openai
|
| 183 |
+
if api_dict:
|
| 184 |
+
client = openai.OpenAI(
|
| 185 |
+
base_url=api_dict["api_base"],
|
| 186 |
+
api_key=api_dict["api_key"],
|
| 187 |
+
)
|
| 188 |
+
else:
|
| 189 |
+
client = openai.OpenAI()
|
| 190 |
+
|
| 191 |
+
if api_dict and "model_name" in api_dict:
|
| 192 |
+
model = api_dict["model_name"]
|
| 193 |
+
|
| 194 |
+
output = API_ERROR_OUTPUT
|
| 195 |
+
for _ in range(API_MAX_RETRY):
|
| 196 |
+
try:
|
| 197 |
+
completion = client.chat.completions.create(
|
| 198 |
+
model=model,
|
| 199 |
+
messages=messages,
|
| 200 |
+
temperature=temperature,
|
| 201 |
+
max_tokens=max_tokens,
|
| 202 |
+
)
|
| 203 |
+
output = {
|
| 204 |
+
"answer": completion.choices[0].message.content
|
| 205 |
+
}
|
| 206 |
+
break
|
| 207 |
+
except openai.RateLimitError as e:
|
| 208 |
+
print(type(e), e)
|
| 209 |
+
time.sleep(API_RETRY_SLEEP)
|
| 210 |
+
except openai.BadRequestError as e:
|
| 211 |
+
print("=== DEBUG: OpenAI BadRequestError ===")
|
| 212 |
+
print("Error type:", type(e))
|
| 213 |
+
print("Error message:", str(e))
|
| 214 |
+
print("=== Analyzing messages for image issues ===")
|
| 215 |
+
for i, msg in enumerate(messages):
|
| 216 |
+
print(f"Message {i} role: {msg.get('role', 'unknown')}")
|
| 217 |
+
if "content" in msg:
|
| 218 |
+
content = msg["content"]
|
| 219 |
+
if isinstance(content, list):
|
| 220 |
+
for j, item in enumerate(content):
|
| 221 |
+
if isinstance(item, dict) and item.get("type") == "image_url":
|
| 222 |
+
url = item.get("image_url", {}).get("url", "")
|
| 223 |
+
if url.startswith("data:image/png;base64,"):
|
| 224 |
+
base64_part = url[22:] # Remove "data:image/png;base64," prefix
|
| 225 |
+
print(f" Image {j}: base64 length = {len(base64_part)}")
|
| 226 |
+
if len(base64_part) < 50:
|
| 227 |
+
print(f" *** ISSUE: Image {j} has very short/empty base64: '{url}'")
|
| 228 |
+
elif url.startswith("data:image/"):
|
| 229 |
+
print(f" Image {j}: Non-PNG data URL: {url[:50]}...")
|
| 230 |
+
else:
|
| 231 |
+
print(f" Image {j}: Unexpected URL format: {url[:50]}...")
|
| 232 |
+
elif isinstance(item, dict) and item.get("type") == "text":
|
| 233 |
+
text_content = item.get("text", "")
|
| 234 |
+
print(f" Text {j}: {len(text_content)} chars")
|
| 235 |
+
else:
|
| 236 |
+
print(f" Content {j}: {type(item)} - {str(item)[:50]}...")
|
| 237 |
+
else:
|
| 238 |
+
print(f" Content: {type(content)} - {str(content)[:100]}...")
|
| 239 |
+
print("=== End debug info ===")
|
| 240 |
+
break
|
| 241 |
+
except KeyError:
|
| 242 |
+
print(type(e), e)
|
| 243 |
+
break
|
| 244 |
+
|
| 245 |
+
return output
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
@register_api("openai_streaming")
|
| 249 |
+
def chat_completion_openai_streaming(model, messages, temperature, max_tokens, api_dict=None, **kwargs):
|
| 250 |
+
"""Streaming version of OpenAI completion that yields tokens as they arrive"""
|
| 251 |
+
import openai
|
| 252 |
+
if api_dict:
|
| 253 |
+
client = openai.OpenAI(
|
| 254 |
+
base_url=api_dict["api_base"],
|
| 255 |
+
api_key=api_dict["api_key"],
|
| 256 |
+
)
|
| 257 |
+
else:
|
| 258 |
+
client = openai.OpenAI()
|
| 259 |
+
|
| 260 |
+
if api_dict and "model_name" in api_dict:
|
| 261 |
+
model = api_dict["model_name"]
|
| 262 |
+
|
| 263 |
+
try:
|
| 264 |
+
stream = client.chat.completions.create(
|
| 265 |
+
model=model,
|
| 266 |
+
messages=messages,
|
| 267 |
+
temperature=temperature,
|
| 268 |
+
max_tokens=max_tokens,
|
| 269 |
+
stream=True
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
for chunk in stream:
|
| 273 |
+
if chunk.choices[0].delta.content is not None:
|
| 274 |
+
yield chunk.choices[0].delta.content
|
| 275 |
+
|
| 276 |
+
except Exception as e:
|
| 277 |
+
print(f"Error in streaming completion: {e}")
|
| 278 |
+
yield f"Error: {str(e)}"
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
@register_api("openai_thinking")
|
| 282 |
+
def chat_completion_openai_thinking(model, messages, api_dict=None, **kwargs):
|
| 283 |
+
import openai
|
| 284 |
+
|
| 285 |
+
if api_dict:
|
| 286 |
+
client = openai.OpenAI(
|
| 287 |
+
api_key=api_dict["api_key"],
|
| 288 |
+
base_url=api_dict["api_base"],
|
| 289 |
+
)
|
| 290 |
+
else:
|
| 291 |
+
client = openai.OpenAI()
|
| 292 |
+
|
| 293 |
+
output = API_ERROR_OUTPUT
|
| 294 |
+
for i in range(API_MAX_RETRY):
|
| 295 |
+
try:
|
| 296 |
+
completion = client.chat.completions.create(
|
| 297 |
+
model=model,
|
| 298 |
+
messages=messages,
|
| 299 |
+
reasoning_effort=kwargs['reasoning_effort'] if 'reasoning_effort' in kwargs else 'medium',
|
| 300 |
+
|
| 301 |
+
)
|
| 302 |
+
output = {
|
| 303 |
+
"answer": completion.choices[0].message.content
|
| 304 |
+
}
|
| 305 |
+
break
|
| 306 |
+
except openai.RateLimitError as e:
|
| 307 |
+
print(type(e), e)
|
| 308 |
+
time.sleep(API_RETRY_SLEEP)
|
| 309 |
+
except openai.BadRequestError as e:
|
| 310 |
+
print("=== DEBUG: OpenAI BadRequestError ===")
|
| 311 |
+
print("Error type:", type(e))
|
| 312 |
+
print("Error message:", str(e))
|
| 313 |
+
print("=== Analyzing messages for image issues ===")
|
| 314 |
+
for i, msg in enumerate(messages):
|
| 315 |
+
print(f"Message {i} role: {msg.get('role', 'unknown')}")
|
| 316 |
+
if "content" in msg:
|
| 317 |
+
content = msg["content"]
|
| 318 |
+
if isinstance(content, list):
|
| 319 |
+
for j, item in enumerate(content):
|
| 320 |
+
if isinstance(item, dict) and item.get("type") == "image_url":
|
| 321 |
+
url = item.get("image_url", {}).get("url", "")
|
| 322 |
+
if url.startswith("data:image/png;base64,"):
|
| 323 |
+
base64_part = url[22:] # Remove "data:image/png;base64," prefix
|
| 324 |
+
print(f" Image {j}: base64 length = {len(base64_part)}")
|
| 325 |
+
if len(base64_part) < 50:
|
| 326 |
+
print(f" *** ISSUE: Image {j} has very short/empty base64: '{url}'")
|
| 327 |
+
elif url.startswith("data:image/"):
|
| 328 |
+
print(f" Image {j}: Non-PNG data URL: {url[:50]}...")
|
| 329 |
+
else:
|
| 330 |
+
print(f" Image {j}: Unexpected URL format: {url[:50]}...")
|
| 331 |
+
elif isinstance(item, dict) and item.get("type") == "text":
|
| 332 |
+
text_content = item.get("text", "")
|
| 333 |
+
print(f" Text {j}: {len(text_content)} chars")
|
| 334 |
+
else:
|
| 335 |
+
print(f" Content {j}: {type(item)} - {str(item)[:50]}...")
|
| 336 |
+
else:
|
| 337 |
+
print(f" Content: {type(content)} - {str(content)[:100]}...")
|
| 338 |
+
print("=== End debug info ===")
|
| 339 |
+
break
|
| 340 |
+
except KeyError:
|
| 341 |
+
print(type(e), e)
|
| 342 |
+
break
|
| 343 |
+
|
| 344 |
+
return output
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
@register_api("deepseek_reasoner")
|
| 348 |
+
def chat_completion_deepseek_reasoner(messages, api_dict, **kwargs):
|
| 349 |
+
import urllib.request
|
| 350 |
+
|
| 351 |
+
chat_endpoint_headers = {
|
| 352 |
+
"User-Agent": "curl/8.7.1",
|
| 353 |
+
"Authorization": "Bearer {}".format(api_dict['api_key']),
|
| 354 |
+
"Content-Type": "application/json",
|
| 355 |
+
"Accept": "application/json",
|
| 356 |
+
}
|
| 357 |
+
chat_endpoint_url = "https://api.deepseek.com/chat/completions"
|
| 358 |
+
|
| 359 |
+
req_body = {
|
| 360 |
+
"messages": messages,
|
| 361 |
+
"model": "deepseek-reasoner",
|
| 362 |
+
"stream": False,
|
| 363 |
+
}
|
| 364 |
+
req_data = json.dumps(req_body).encode("utf-8")
|
| 365 |
+
|
| 366 |
+
output = API_ERROR_OUTPUT
|
| 367 |
+
for i in range(API_MAX_RETRY):
|
| 368 |
+
try:
|
| 369 |
+
req = urllib.request.Request(
|
| 370 |
+
chat_endpoint_url,
|
| 371 |
+
headers = chat_endpoint_headers.copy(),
|
| 372 |
+
data = req_data,
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
with urllib.request.urlopen(req) as res:
|
| 376 |
+
res_data = res.read()
|
| 377 |
+
res_body = json.loads(res_data.decode("utf-8"))
|
| 378 |
+
|
| 379 |
+
output = {
|
| 380 |
+
"thought": res_body["choices"][0]["message"]["reasoning_content"],
|
| 381 |
+
"answer": res_body["choices"][0]["message"]["content"],
|
| 382 |
+
}
|
| 383 |
+
break
|
| 384 |
+
except Exception as e:
|
| 385 |
+
print(type(e), e)
|
| 386 |
+
time.sleep(API_RETRY_SLEEP)
|
| 387 |
+
|
| 388 |
+
return output
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
@register_api("deepseek")
|
| 392 |
+
def chat_completion_deepseek(messages, max_tokens, api_dict, **kwargs):
|
| 393 |
+
import urllib.request
|
| 394 |
+
|
| 395 |
+
chat_endpoint_headers = {
|
| 396 |
+
"User-Agent": "curl/8.7.1",
|
| 397 |
+
"Authorization": "Bearer {}".format(api_dict['api_key']),
|
| 398 |
+
"Content-Type": "application/json",
|
| 399 |
+
"Accept": "application/json",
|
| 400 |
+
}
|
| 401 |
+
chat_endpoint_url = "https://api.deepseek.com/chat/completions"
|
| 402 |
+
|
| 403 |
+
req_body = {
|
| 404 |
+
"messages": messages,
|
| 405 |
+
"model": "deepseek-chat",
|
| 406 |
+
"stream": False,
|
| 407 |
+
"max_tokens": max_tokens,
|
| 408 |
+
}
|
| 409 |
+
req_data = json.dumps(req_body).encode("utf-8")
|
| 410 |
+
|
| 411 |
+
output = API_ERROR_OUTPUT
|
| 412 |
+
for i in range(API_MAX_RETRY):
|
| 413 |
+
try:
|
| 414 |
+
req = urllib.request.Request(
|
| 415 |
+
chat_endpoint_url,
|
| 416 |
+
headers = chat_endpoint_headers.copy(),
|
| 417 |
+
data = req_data,
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
with urllib.request.urlopen(req) as res:
|
| 421 |
+
res_data = res.read()
|
| 422 |
+
res_body = json.loads(res_data.decode("utf-8"))
|
| 423 |
+
|
| 424 |
+
output = {
|
| 425 |
+
"answer": res_body["choices"][0]["message"]["content"],
|
| 426 |
+
}
|
| 427 |
+
break
|
| 428 |
+
except Exception as e:
|
| 429 |
+
print(type(e), e)
|
| 430 |
+
time.sleep(API_RETRY_SLEEP)
|
| 431 |
+
|
| 432 |
+
return output
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
@register_api("anthropic")
|
| 436 |
+
def chat_completion_anthropic(model, messages, temperature, max_tokens, api_dict=None, **kwargs):
|
| 437 |
+
import anthropic
|
| 438 |
+
|
| 439 |
+
if api_dict:
|
| 440 |
+
api_key = api_dict["api_key"]
|
| 441 |
+
else:
|
| 442 |
+
api_key = os.environ["ANTHROPIC_API_KEY"]
|
| 443 |
+
|
| 444 |
+
sys_msg = ""
|
| 445 |
+
if messages[0]["role"] == "system":
|
| 446 |
+
sys_msg = messages[0]["content"]
|
| 447 |
+
messages = messages[1:]
|
| 448 |
+
|
| 449 |
+
output = API_ERROR_OUTPUT
|
| 450 |
+
for _ in range(API_MAX_RETRY):
|
| 451 |
+
try:
|
| 452 |
+
c = anthropic.Anthropic(api_key=api_key)
|
| 453 |
+
response = c.messages.create(
|
| 454 |
+
model=model,
|
| 455 |
+
messages=messages,
|
| 456 |
+
stop_sequences=[anthropic.HUMAN_PROMPT],
|
| 457 |
+
max_tokens=max_tokens,
|
| 458 |
+
temperature=temperature,
|
| 459 |
+
system=sys_msg
|
| 460 |
+
)
|
| 461 |
+
output = {
|
| 462 |
+
"answer": response.content[0].text
|
| 463 |
+
}
|
| 464 |
+
break
|
| 465 |
+
except anthropic.APIError as e:
|
| 466 |
+
print(type(e), e)
|
| 467 |
+
time.sleep(API_RETRY_SLEEP)
|
| 468 |
+
return output
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
@register_api("anthropic_thinking")
|
| 472 |
+
def chat_completion_anthropic_thinking(model, messages, max_tokens, budget_tokens, **kwargs):
|
| 473 |
+
import anthropic
|
| 474 |
+
|
| 475 |
+
client = anthropic.Anthropic(
|
| 476 |
+
timeout=1200,
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
output = API_ERROR_OUTPUT
|
| 480 |
+
for _ in range(API_MAX_RETRY):
|
| 481 |
+
try:
|
| 482 |
+
response = client.messages.create(
|
| 483 |
+
model=model,
|
| 484 |
+
max_tokens=max_tokens,
|
| 485 |
+
thinking={
|
| 486 |
+
"type": "enabled",
|
| 487 |
+
"budget_tokens": budget_tokens
|
| 488 |
+
},
|
| 489 |
+
messages=messages,
|
| 490 |
+
)
|
| 491 |
+
output = {
|
| 492 |
+
"thought": response.content[0].thinking,
|
| 493 |
+
"answer": response.content[1].text,
|
| 494 |
+
}
|
| 495 |
+
break
|
| 496 |
+
except anthropic.APIError as e:
|
| 497 |
+
print(type(e), e)
|
| 498 |
+
time.sleep(API_RETRY_SLEEP)
|
| 499 |
+
|
| 500 |
+
return output
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
@register_api("mistral")
|
| 504 |
+
def chat_completion_mistral(model, messages, temperature, max_tokens, **kwargs):
|
| 505 |
+
from mistralai.client import MistralClient
|
| 506 |
+
from mistralai.models.chat_completion import ChatMessage
|
| 507 |
+
from mistralai.exceptions import MistralException
|
| 508 |
+
|
| 509 |
+
api_key = os.environ["MISTRAL_API_KEY"]
|
| 510 |
+
client = MistralClient(api_key=api_key)
|
| 511 |
+
|
| 512 |
+
prompts = [ChatMessage(role=message["role"], content=message["content"]) for message in messages]
|
| 513 |
+
|
| 514 |
+
output = API_ERROR_OUTPUT
|
| 515 |
+
for _ in range(API_MAX_RETRY):
|
| 516 |
+
try:
|
| 517 |
+
chat_response = client.chat(
|
| 518 |
+
model=model,
|
| 519 |
+
messages=prompts,
|
| 520 |
+
temperature=temperature,
|
| 521 |
+
max_tokens=max_tokens,
|
| 522 |
+
)
|
| 523 |
+
output = {
|
| 524 |
+
"answer": chat_response.choices[0].message.content
|
| 525 |
+
}
|
| 526 |
+
break
|
| 527 |
+
except MistralException as e:
|
| 528 |
+
print(type(e), e)
|
| 529 |
+
break
|
| 530 |
+
|
| 531 |
+
return output
|
| 532 |
+
|
| 533 |
+
|
| 534 |
+
@register_api("xai")
|
| 535 |
+
def chat_completion_xai(model, messages, temperature, max_tokens, api_dict=None, **kwargs):
|
| 536 |
+
import xai_sdk
|
| 537 |
+
|
| 538 |
+
client = xai_sdk.Client(api_key=api_dict['api_key'], api_host=api_dict['api_base']).compat
|
| 539 |
+
output = API_ERROR_OUTPUT
|
| 540 |
+
|
| 541 |
+
for _ in range(API_MAX_RETRY):
|
| 542 |
+
try:
|
| 543 |
+
stream = client.chat.completions.create(
|
| 544 |
+
model=model,
|
| 545 |
+
messages=messages,
|
| 546 |
+
stream=True,
|
| 547 |
+
max_tokens=max_tokens,
|
| 548 |
+
temperature=temperature,
|
| 549 |
+
top_p=0.95,
|
| 550 |
+
)
|
| 551 |
+
output_text = ""
|
| 552 |
+
for chunk in stream:
|
| 553 |
+
if chunk.choices[0].delta.content:
|
| 554 |
+
output_text += chunk.choices[0].delta.content
|
| 555 |
+
|
| 556 |
+
output = {
|
| 557 |
+
"answer": output_text
|
| 558 |
+
}
|
| 559 |
+
break
|
| 560 |
+
except Exception as e:
|
| 561 |
+
print(type(e), e)
|
| 562 |
+
time.sleep(API_RETRY_SLEEP)
|
| 563 |
+
|
| 564 |
+
return output
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
@register_api("litellm")
|
| 568 |
+
def chat_completion_litellm(model, messages, temperature, max_tokens, api_dict=None, **kwargs):
|
| 569 |
+
import litellm
|
| 570 |
+
|
| 571 |
+
output = API_ERROR_OUTPUT
|
| 572 |
+
for _ in range(API_MAX_RETRY):
|
| 573 |
+
try:
|
| 574 |
+
response = litellm.completion(
|
| 575 |
+
model=model,
|
| 576 |
+
messages=messages,
|
| 577 |
+
temperature=temperature,
|
| 578 |
+
max_tokens=max_tokens,
|
| 579 |
+
)
|
| 580 |
+
output = {
|
| 581 |
+
"answer": response.choices[0].message.content
|
| 582 |
+
}
|
| 583 |
+
break
|
| 584 |
+
except Exception as e:
|
| 585 |
+
print(type(e), e)
|
| 586 |
+
time.sleep(API_RETRY_SLEEP)
|
| 587 |
+
|
| 588 |
+
return output
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
@register_api("litellm_streaming")
|
| 592 |
+
def chat_completion_litellm_streaming(model, messages, temperature, max_tokens, api_dict=None, **kwargs):
|
| 593 |
+
"""Streaming version of litellm completion"""
|
| 594 |
+
import litellm
|
| 595 |
+
|
| 596 |
+
try:
|
| 597 |
+
response = litellm.completion(
|
| 598 |
+
model=model,
|
| 599 |
+
messages=messages,
|
| 600 |
+
temperature=temperature,
|
| 601 |
+
max_tokens=max_tokens,
|
| 602 |
+
stream=True
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
for chunk in response:
|
| 606 |
+
if chunk.choices[0].delta.content is not None:
|
| 607 |
+
yield chunk.choices[0].delta.content
|
| 608 |
+
|
| 609 |
+
except Exception as e:
|
| 610 |
+
print(f"Error in litellm streaming completion: {e}")
|
| 611 |
+
yield f"Error: {str(e)}"
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
@register_api("anthropic_streaming")
|
| 615 |
+
def chat_completion_anthropic_streaming(model, messages, temperature, max_tokens, api_dict=None, **kwargs):
|
| 616 |
+
"""Streaming version of Anthropic completion"""
|
| 617 |
+
import anthropic
|
| 618 |
+
|
| 619 |
+
if api_dict:
|
| 620 |
+
client = anthropic.Anthropic(api_key=api_dict["api_key"])
|
| 621 |
+
else:
|
| 622 |
+
client = anthropic.Anthropic()
|
| 623 |
+
|
| 624 |
+
try:
|
| 625 |
+
# Convert messages to Anthropic format
|
| 626 |
+
system_message = ""
|
| 627 |
+
conversation_messages = []
|
| 628 |
+
|
| 629 |
+
for msg in messages:
|
| 630 |
+
if msg["role"] == "system":
|
| 631 |
+
system_message = msg["content"]
|
| 632 |
+
else:
|
| 633 |
+
conversation_messages.append(msg)
|
| 634 |
+
|
| 635 |
+
stream = client.messages.create(
|
| 636 |
+
model=model,
|
| 637 |
+
max_tokens=max_tokens,
|
| 638 |
+
temperature=temperature,
|
| 639 |
+
system=system_message if system_message else None,
|
| 640 |
+
messages=conversation_messages,
|
| 641 |
+
stream=True
|
| 642 |
+
)
|
| 643 |
+
|
| 644 |
+
for chunk in stream:
|
| 645 |
+
if chunk.type == "content_block_delta" and chunk.delta.text:
|
| 646 |
+
yield chunk.delta.text
|
| 647 |
+
|
| 648 |
+
except Exception as e:
|
| 649 |
+
print(f"Error in Anthropic streaming completion: {e}")
|
| 650 |
+
yield f"Error: {str(e)}"
|
| 651 |
+
|
| 652 |
+
@register_api("gemini")
|
| 653 |
+
def http_completion_gemini(model, messages, **kwargs):
|
| 654 |
+
import requests
|
| 655 |
+
|
| 656 |
+
api_key = os.environ["GEMINI_API_KEY"]
|
| 657 |
+
|
| 658 |
+
safety_settings = [
|
| 659 |
+
{
|
| 660 |
+
"category": "HARM_CATEGORY_HARASSMENT",
|
| 661 |
+
"threshold": "BLOCK_NONE"
|
| 662 |
+
},
|
| 663 |
+
{
|
| 664 |
+
"category": "HARM_CATEGORY_HATE_SPEECH",
|
| 665 |
+
"threshold": "BLOCK_NONE"
|
| 666 |
+
},
|
| 667 |
+
{
|
| 668 |
+
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
| 669 |
+
"threshold": "BLOCK_NONE"
|
| 670 |
+
},
|
| 671 |
+
{
|
| 672 |
+
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
| 673 |
+
"threshold": "BLOCK_NONE"
|
| 674 |
+
},
|
| 675 |
+
]
|
| 676 |
+
|
| 677 |
+
sys_prompt = None
|
| 678 |
+
if messages[0]["role"] == "system":
|
| 679 |
+
sys_prompt = {
|
| 680 |
+
"parts":[
|
| 681 |
+
{"text": messages[0]["content"]}
|
| 682 |
+
]
|
| 683 |
+
}
|
| 684 |
+
messages = messages[1:]
|
| 685 |
+
|
| 686 |
+
role_map = {"user": "user",
|
| 687 |
+
"assistant": "model"}
|
| 688 |
+
|
| 689 |
+
conv = [{"parts":[{"text":turn["content"]}], "role":role_map[turn["role"]]} for turn in messages]
|
| 690 |
+
|
| 691 |
+
json_request = {
|
| 692 |
+
"contents": conv,
|
| 693 |
+
"safetySettings": safety_settings,
|
| 694 |
+
"systemInstruction": sys_prompt,
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
if "temperature" in kwargs and "max_tokens" in kwargs:
|
| 698 |
+
gen_config = {
|
| 699 |
+
"temperature": kwargs["temperature"],
|
| 700 |
+
"maxOutputTokens": kwargs["max_tokens"],
|
| 701 |
+
}
|
| 702 |
+
json_request["generationConfig"] = gen_config
|
| 703 |
+
elif "temperature" in kwargs:
|
| 704 |
+
gen_config = {
|
| 705 |
+
"temperature": kwargs["temperature"],
|
| 706 |
+
}
|
| 707 |
+
json_request["generationConfig"] = gen_config
|
| 708 |
+
elif "max_tokens" in kwargs:
|
| 709 |
+
gen_config = {
|
| 710 |
+
"maxOutputTokens": kwargs["max_tokens"],
|
| 711 |
+
}
|
| 712 |
+
json_request["generationConfig"] = gen_config
|
| 713 |
+
|
| 714 |
+
output = API_ERROR_OUTPUT
|
| 715 |
+
for _ in range(API_MAX_RETRY):
|
| 716 |
+
try:
|
| 717 |
+
response = requests.post(
|
| 718 |
+
f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}",
|
| 719 |
+
json=json_request,
|
| 720 |
+
)
|
| 721 |
+
except Exception as e:
|
| 722 |
+
print(f"**API REQUEST ERROR** Reason: {e}.")
|
| 723 |
+
time.sleep(API_RETRY_SLEEP)
|
| 724 |
+
if response.status_code != 200:
|
| 725 |
+
print(f"**API REQUEST ERROR** Reason: status code {response.status_code}.")
|
| 726 |
+
time.sleep(API_RETRY_SLEEP)
|
| 727 |
+
try:
|
| 728 |
+
output = {
|
| 729 |
+
"answer": response.json()["candidates"][0]["content"]["parts"][0]["text"],
|
| 730 |
+
}
|
| 731 |
+
except KeyError as e:
|
| 732 |
+
print(type(e), e)
|
| 733 |
+
print(response.json())
|
| 734 |
+
return output
|
| 735 |
+
|
| 736 |
+
|
| 737 |
+
@register_api("vertex")
|
| 738 |
+
def vertex_completion_gemini(model, messages, project_id, regions, **kwargs):
|
| 739 |
+
import requests
|
| 740 |
+
import subprocess
|
| 741 |
+
|
| 742 |
+
output = API_ERROR_OUTPUT
|
| 743 |
+
|
| 744 |
+
# Obtain the access token using gcloud CLI
|
| 745 |
+
access_token = subprocess.check_output(
|
| 746 |
+
["gcloud", "auth", "application-default", "print-access-token"],
|
| 747 |
+
text=True
|
| 748 |
+
).strip()
|
| 749 |
+
|
| 750 |
+
if messages[0]["role"] == "system":
|
| 751 |
+
data = {
|
| 752 |
+
"systemInstruction": {
|
| 753 |
+
"role": "system", # ignored by vertexi api (04/18/2025)
|
| 754 |
+
"parts": [{
|
| 755 |
+
"text": messages[0]["content"]
|
| 756 |
+
}]
|
| 757 |
+
},
|
| 758 |
+
}
|
| 759 |
+
messages = messages[1:]
|
| 760 |
+
else:
|
| 761 |
+
data = {}
|
| 762 |
+
|
| 763 |
+
role_map = {
|
| 764 |
+
"user": "user",
|
| 765 |
+
"assistant": "model"
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
messages = [{"parts":[{"text":turn["content"]}], "role":role_map[turn["role"]]} for turn in messages]
|
| 769 |
+
|
| 770 |
+
url = (
|
| 771 |
+
f"https://us-central1-aiplatform.googleapis.com/v1/projects/"
|
| 772 |
+
f"{project_id}/locations/{regions}/publishers/google/models/"
|
| 773 |
+
f"{model}:generateContent"
|
| 774 |
+
)
|
| 775 |
+
|
| 776 |
+
headers = {
|
| 777 |
+
"Authorization": f"Bearer {access_token}",
|
| 778 |
+
"Content-Type": "application/json",
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
+
data = data | {
|
| 782 |
+
"contents": messages,
|
| 783 |
+
}
|
| 784 |
+
|
| 785 |
+
if "temperature" in kwargs or "max_tokens" in kwargs:
|
| 786 |
+
gen_config = {}
|
| 787 |
+
if "temperature" in kwargs:
|
| 788 |
+
gen_config["temperature"] = kwargs["temperature"]
|
| 789 |
+
if "max_tokens" in kwargs:
|
| 790 |
+
gen_config["maxOutputTokens"] = kwargs["max_tokens"]
|
| 791 |
+
data["generationConfig"] = gen_config
|
| 792 |
+
|
| 793 |
+
response = requests.post(url, json=data, headers=headers)
|
| 794 |
+
|
| 795 |
+
try:
|
| 796 |
+
output = {
|
| 797 |
+
"answer": response.json()["candidates"][0]["content"]["parts"][0]["text"],
|
| 798 |
+
}
|
| 799 |
+
except KeyError as e:
|
| 800 |
+
print(type(e), e)
|
| 801 |
+
print(response.json())
|
| 802 |
+
|
| 803 |
+
return output
|
| 804 |
+
|
| 805 |
+
|
| 806 |
+
@register_api("cohere")
|
| 807 |
+
def chat_completion_cohere(model, messages, temperature, max_tokens, **kwargs):
|
| 808 |
+
import cohere
|
| 809 |
+
|
| 810 |
+
co = cohere.Client(os.environ["COHERE_API_KEY"])
|
| 811 |
+
assert len(messages) > 0
|
| 812 |
+
|
| 813 |
+
template_map = {"system":"SYSTEM",
|
| 814 |
+
"assistant":"CHATBOT",
|
| 815 |
+
"user":"USER"}
|
| 816 |
+
|
| 817 |
+
assert messages[-1]["role"] == "user"
|
| 818 |
+
prompt = messages[-1]["content"]
|
| 819 |
+
|
| 820 |
+
if len(messages) > 1:
|
| 821 |
+
history = []
|
| 822 |
+
for message in messages[:-1]:
|
| 823 |
+
history.append({"role":template_map[message["role"]], "message":message["content"]})
|
| 824 |
+
else:
|
| 825 |
+
history = None
|
| 826 |
+
|
| 827 |
+
output = API_ERROR_OUTPUT
|
| 828 |
+
for _ in range(API_MAX_RETRY):
|
| 829 |
+
try:
|
| 830 |
+
response = co.chat(
|
| 831 |
+
message=prompt,
|
| 832 |
+
model=model,
|
| 833 |
+
temperature=temperature,
|
| 834 |
+
max_tokens=max_tokens,
|
| 835 |
+
chat_history=history,
|
| 836 |
+
)
|
| 837 |
+
output = {
|
| 838 |
+
"answer": response.text
|
| 839 |
+
}
|
| 840 |
+
break
|
| 841 |
+
except cohere.core.api_error.ApiError as e:
|
| 842 |
+
print(type(e), e)
|
| 843 |
+
raise
|
| 844 |
+
except Exception as e:
|
| 845 |
+
print(type(e), e)
|
| 846 |
+
break
|
| 847 |
+
|
| 848 |
+
return output
|
| 849 |
+
|
| 850 |
+
|
| 851 |
+
@register_api("meta")
|
| 852 |
+
def chat_completion_meta(model, messages, temperature, max_tokens, api_dict, **kwargs):
|
| 853 |
+
assert api_dict
|
| 854 |
+
texts = [{"role": m["role"],
|
| 855 |
+
"text": m["content"]} for m in messages]
|
| 856 |
+
|
| 857 |
+
output = ""
|
| 858 |
+
for _ in range(API_MAX_RETRY):
|
| 859 |
+
try:
|
| 860 |
+
res = requests.post(
|
| 861 |
+
f"{api_dict['api_base']}/chat_stream_completions?access_token={api_dict['api_key']}",
|
| 862 |
+
stream=True,
|
| 863 |
+
headers={"Content-Type": "application/json"},
|
| 864 |
+
json={
|
| 865 |
+
"model": model,
|
| 866 |
+
"chunks_delimited": True,
|
| 867 |
+
"messages": texts,
|
| 868 |
+
"options": {
|
| 869 |
+
"max_tokens": max_tokens,
|
| 870 |
+
"generation_algorithm": "top_p",
|
| 871 |
+
"top_p": 1,
|
| 872 |
+
"temperature": temperature,
|
| 873 |
+
},
|
| 874 |
+
},
|
| 875 |
+
timeout=30,
|
| 876 |
+
)
|
| 877 |
+
if res.status_code == 200:
|
| 878 |
+
for line in res.iter_lines():
|
| 879 |
+
if line:
|
| 880 |
+
part = json.loads(line.decode("utf-8"))
|
| 881 |
+
if "text" in part:
|
| 882 |
+
output += part["text"]
|
| 883 |
+
break
|
| 884 |
+
else:
|
| 885 |
+
print(f"**API REQUEST ERROR** Code: {res.status_code}")
|
| 886 |
+
time.sleep(API_RETRY_SLEEP)
|
| 887 |
+
except Exception as e:
|
| 888 |
+
print("**API REQUEST ERROR** Reason: Unknown.")
|
| 889 |
+
time.sleep(API_RETRY_SLEEP)
|
| 890 |
+
continue
|
| 891 |
+
|
| 892 |
+
return {
|
| 893 |
+
"answer": output
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
|
| 897 |
+
def batch_submit_sglang(
|
| 898 |
+
executor,
|
| 899 |
+
tokenizer,
|
| 900 |
+
temperature,
|
| 901 |
+
max_tokens,
|
| 902 |
+
all_context,
|
| 903 |
+
max_context_length=None,
|
| 904 |
+
end_think_token=None,
|
| 905 |
+
):
|
| 906 |
+
print(f"DEBUG: sglang_completion_qwq: max_context_length: {max_context_length}")
|
| 907 |
+
|
| 908 |
+
sampling_params = {
|
| 909 |
+
"temperature": temperature,
|
| 910 |
+
"skip_special_tokens": False,
|
| 911 |
+
"max_new_tokens": max_tokens - 1,
|
| 912 |
+
"no_stop_trim": True,
|
| 913 |
+
}
|
| 914 |
+
|
| 915 |
+
batch_prompt_token_ids = []
|
| 916 |
+
batch_uids =[]
|
| 917 |
+
uid_to_prompt = {}
|
| 918 |
+
uid_to_response = {}
|
| 919 |
+
|
| 920 |
+
for context in all_context:
|
| 921 |
+
prompt_token_ids = tokenizer.apply_chat_template(
|
| 922 |
+
context['turns'],
|
| 923 |
+
add_generation_prompt=True,
|
| 924 |
+
tokenize=True,
|
| 925 |
+
)
|
| 926 |
+
|
| 927 |
+
if max_context_length and (len(prompt_token_ids) + max_tokens) > max_context_length:
|
| 928 |
+
print(f"DEBUG: sglang_completion_qwq: context length ({len(prompt_token_ids) + max_tokens}) > max_context_length ({max_context_length}), skip this context")
|
| 929 |
+
continue
|
| 930 |
+
|
| 931 |
+
batch_prompt_token_ids.append(prompt_token_ids)
|
| 932 |
+
batch_uids.append(context['uid'])
|
| 933 |
+
|
| 934 |
+
uid_to_prompt[context['uid']] = context['turns']
|
| 935 |
+
|
| 936 |
+
err_msg = f"ERROR: len(batch_prompt_token_ids): {len(batch_prompt_token_ids)} != len(batch_uids): {len(batch_uids)}"
|
| 937 |
+
assert len(batch_prompt_token_ids) == len(batch_uids), err_msg
|
| 938 |
+
|
| 939 |
+
_ = executor.submit(
|
| 940 |
+
prompt_token_ids=batch_prompt_token_ids,
|
| 941 |
+
sampling_params=[sampling_params] * len(batch_uids),
|
| 942 |
+
keys=batch_uids,
|
| 943 |
+
)
|
| 944 |
+
|
| 945 |
+
for request in tqdm(executor.as_completed(), total=len(batch_uids)):
|
| 946 |
+
uid = request.key()
|
| 947 |
+
result = request.result()
|
| 948 |
+
raw_response = tokenizer.decode(
|
| 949 |
+
result['output_ids'],
|
| 950 |
+
skip_special_tokens=True,
|
| 951 |
+
)
|
| 952 |
+
|
| 953 |
+
if end_think_token:
|
| 954 |
+
thought, _, ans = raw_response.partition(end_think_token)
|
| 955 |
+
if ans == "":
|
| 956 |
+
uid_to_response[uid] = {"thought": thought, "answer": raw_response}
|
| 957 |
+
else:
|
| 958 |
+
uid_to_response[uid] = {"thought": thought, "answer": ans}
|
| 959 |
+
else:
|
| 960 |
+
uid_to_response[uid] = {"answer": raw_response}
|
| 961 |
+
|
| 962 |
+
# assert len(uid_to_response) == len(all_context), f"ERROR: len output ({len(uid_to_response)}) != len input ({len(all_context)})"
|
| 963 |
+
return uid_to_response
|
| 964 |
+
|
| 965 |
+
|
| 966 |
+
def _infer_cuda_tp_world_size():
|
| 967 |
+
cuda_devices = os.environ.get("CUDA_VISIBLE_DEVICES", None)
|
| 968 |
+
if cuda_devices is None:
|
| 969 |
+
tp_world_size = 8
|
| 970 |
+
else:
|
| 971 |
+
tp_world_size = len(cuda_devices.split(","))
|
| 972 |
+
return tp_world_size
|
| 973 |
+
|
| 974 |
+
|
| 975 |
+
def download_model(model: str, max_workers: int = 64):
|
| 976 |
+
import subprocess
|
| 977 |
+
|
| 978 |
+
env = os.environ.copy()
|
| 979 |
+
env["HF_HUB_ENABLE_HF_TRANSFER"] = "0"
|
| 980 |
+
|
| 981 |
+
cmd = [
|
| 982 |
+
"huggingface-cli",
|
| 983 |
+
"download",
|
| 984 |
+
f"--max-workers={max_workers}",
|
| 985 |
+
model
|
| 986 |
+
]
|
| 987 |
+
|
| 988 |
+
try:
|
| 989 |
+
subprocess.run(cmd, env=env, check=True)
|
| 990 |
+
print(f"Successfully downloaded model '{model}' with {max_workers} max workers.")
|
| 991 |
+
except subprocess.CalledProcessError as e:
|
| 992 |
+
print(f"Error occurred while downloading the model: {e}")
|
| 993 |
+
|
| 994 |
+
|
| 995 |
+
@register_engine("sglang")
|
| 996 |
+
def sglang_completion(
|
| 997 |
+
model,
|
| 998 |
+
batch_context,
|
| 999 |
+
answer_file,
|
| 1000 |
+
temperature,
|
| 1001 |
+
max_tokens=32768,
|
| 1002 |
+
end_think_token=None,
|
| 1003 |
+
**kwargs,
|
| 1004 |
+
):
|
| 1005 |
+
from transformers import AutoTokenizer
|
| 1006 |
+
from utils.sglang_server import SGLangServerExecutor
|
| 1007 |
+
import re
|
| 1008 |
+
|
| 1009 |
+
tokenizer = AutoTokenizer.from_pretrained(model)
|
| 1010 |
+
|
| 1011 |
+
uids = [context['uid'] for context in batch_context]
|
| 1012 |
+
prompts = [context['instruction'] for context in batch_context]
|
| 1013 |
+
code_envs = [context['environment'] for context in batch_context]
|
| 1014 |
+
processed_context = [
|
| 1015 |
+
{
|
| 1016 |
+
"uid": uids[i],
|
| 1017 |
+
"turns": [{
|
| 1018 |
+
"content": prompts[i],
|
| 1019 |
+
"role": "user",
|
| 1020 |
+
}]
|
| 1021 |
+
}
|
| 1022 |
+
for i in tqdm(range(len(uids)))
|
| 1023 |
+
]
|
| 1024 |
+
download_model(model=model)
|
| 1025 |
+
|
| 1026 |
+
server_args = {
|
| 1027 |
+
"model_path": model,
|
| 1028 |
+
"dtype": "auto",
|
| 1029 |
+
"tp_size": _infer_cuda_tp_world_size(),
|
| 1030 |
+
"mem_fraction_static": 0.7,
|
| 1031 |
+
"max_prefill_tokens": max_tokens,
|
| 1032 |
+
"max_workers": 256,
|
| 1033 |
+
"server_port": 30000,
|
| 1034 |
+
}
|
| 1035 |
+
|
| 1036 |
+
executor = SGLangServerExecutor(
|
| 1037 |
+
**server_args,
|
| 1038 |
+
)
|
| 1039 |
+
|
| 1040 |
+
print(f"DEBUG: sglang_completion: model: {model}")
|
| 1041 |
+
|
| 1042 |
+
uid_to_response = batch_submit_sglang(
|
| 1043 |
+
executor=executor,
|
| 1044 |
+
tokenizer=tokenizer,
|
| 1045 |
+
temperature=temperature,
|
| 1046 |
+
max_tokens=max_tokens,
|
| 1047 |
+
all_context=processed_context,
|
| 1048 |
+
end_think_token=end_think_token,
|
| 1049 |
+
)
|
| 1050 |
+
|
| 1051 |
+
executor.join()
|
| 1052 |
+
print("DEBUG: sglang_completion: done, sleep 10 seconds...")
|
| 1053 |
+
time.sleep(10)
|
| 1054 |
+
|
| 1055 |
+
num_null = sum(
|
| 1056 |
+
[uid_to_response[uid]['answer'] is None for uid in uids if uid in uid_to_response]
|
| 1057 |
+
)
|
| 1058 |
+
print(f"Number of null responses: {num_null}")
|
| 1059 |
+
|
| 1060 |
+
records = []
|
| 1061 |
+
for i, context in enumerate(processed_context):
|
| 1062 |
+
uid = context['uid']
|
| 1063 |
+
if uid not in uid_to_response:
|
| 1064 |
+
continue
|
| 1065 |
+
|
| 1066 |
+
answer_data = uid_to_response[uid]
|
| 1067 |
+
|
| 1068 |
+
record = {
|
| 1069 |
+
"uid": uid,
|
| 1070 |
+
"ans_id": shortuuid.uuid(),
|
| 1071 |
+
"model": kwargs.get("model_display_name", model),
|
| 1072 |
+
"messages": context['turns'] + [
|
| 1073 |
+
{"content": answer_data, "role": "assistant"}
|
| 1074 |
+
],
|
| 1075 |
+
"environment": code_envs[i],
|
| 1076 |
+
"tstamp": time.time(),
|
| 1077 |
+
"metadata": {},
|
| 1078 |
+
}
|
| 1079 |
+
|
| 1080 |
+
records.append(record)
|
| 1081 |
+
|
| 1082 |
+
with open(answer_file, 'w', encoding='utf-8') as f:
|
| 1083 |
+
for rec in records:
|
| 1084 |
+
f.write(json.dumps(rec, ensure_ascii=True) + '\n')
|
| 1085 |
+
|
| 1086 |
+
|
| 1087 |
+
@register_api("aws_claude")
|
| 1088 |
+
def chat_completion_aws_bedrock_claude(messages, api_dict=None, aws_region="us-west-2", **kwargs):
|
| 1089 |
+
"""
|
| 1090 |
+
Call AWS Bedrock API for chat completion
|
| 1091 |
+
|
| 1092 |
+
Args:
|
| 1093 |
+
model (str): Model ID
|
| 1094 |
+
conv (object): Conversation object containing messages
|
| 1095 |
+
temperature (float): Temperature parameter for response generation
|
| 1096 |
+
max_tokens (int): Maximum tokens in response
|
| 1097 |
+
api_dict (dict, optional): API configuration dictionary
|
| 1098 |
+
aws_region (str, optional): AWS region, defaults to "us-west-2"
|
| 1099 |
+
|
| 1100 |
+
Returns:
|
| 1101 |
+
str: Generated response text or error message
|
| 1102 |
+
"""
|
| 1103 |
+
|
| 1104 |
+
# Configure AWS client if api_dict provided
|
| 1105 |
+
if api_dict is not None:
|
| 1106 |
+
bedrock_rt_client = boto3.client(
|
| 1107 |
+
service_name='bedrock-runtime',
|
| 1108 |
+
region_name=aws_region,
|
| 1109 |
+
aws_access_key_id=api_dict.get('aws_access_key_id'),
|
| 1110 |
+
aws_secret_access_key=api_dict.get('aws_secret_access_key')
|
| 1111 |
+
)
|
| 1112 |
+
else:
|
| 1113 |
+
bedrock_rt_client = boto3.client(
|
| 1114 |
+
service_name='bedrock-runtime',
|
| 1115 |
+
region_name=aws_region,)
|
| 1116 |
+
|
| 1117 |
+
output = API_ERROR_OUTPUT
|
| 1118 |
+
|
| 1119 |
+
#get kwargs from settings
|
| 1120 |
+
temperature= kwargs["temperature"]
|
| 1121 |
+
max_tokens= kwargs["max_tokens"]
|
| 1122 |
+
model = kwargs["model_id"]
|
| 1123 |
+
|
| 1124 |
+
sys_msg = ""
|
| 1125 |
+
if messages[0]["role"] == "system":
|
| 1126 |
+
sys_msg = messages[0]["content"]
|
| 1127 |
+
messages = messages[1:]
|
| 1128 |
+
else:
|
| 1129 |
+
prompt = messages[0]['content']
|
| 1130 |
+
|
| 1131 |
+
|
| 1132 |
+
# Retry logic for API calls
|
| 1133 |
+
for _ in range(API_MAX_RETRY):
|
| 1134 |
+
try:
|
| 1135 |
+
# Prepare request body
|
| 1136 |
+
prompt_json = {
|
| 1137 |
+
"system": sys_msg,
|
| 1138 |
+
"messages": messages,
|
| 1139 |
+
"max_tokens": max_tokens,
|
| 1140 |
+
"temperature": temperature,
|
| 1141 |
+
"anthropic_version": "bedrock-2023-05-31",
|
| 1142 |
+
"stop_sequences": ["Human"]
|
| 1143 |
+
}
|
| 1144 |
+
|
| 1145 |
+
# Call Bedrock API
|
| 1146 |
+
response = bedrock_rt_client.invoke_model(
|
| 1147 |
+
body=json.dumps(prompt_json),
|
| 1148 |
+
modelId=model,
|
| 1149 |
+
accept='application/json',
|
| 1150 |
+
contentType='application/json'
|
| 1151 |
+
)
|
| 1152 |
+
|
| 1153 |
+
# Parse response
|
| 1154 |
+
response_body = json.loads(response.get('body').read())
|
| 1155 |
+
output = {"answer":response_body.get("content")[0].get("text")}
|
| 1156 |
+
break
|
| 1157 |
+
|
| 1158 |
+
except Exception as e:
|
| 1159 |
+
print(type(e), e)
|
| 1160 |
+
time.sleep(API_RETRY_SLEEP)
|
| 1161 |
+
|
| 1162 |
+
return output
|
| 1163 |
+
|
| 1164 |
+
@register_api("aws_mistral")
|
| 1165 |
+
def chat_completion_aws_bedrock_mistral(messages, api_dict=None, aws_region="us-west-2", **kwargs):
|
| 1166 |
+
"""
|
| 1167 |
+
Call AWS Bedrock API for chat completion
|
| 1168 |
+
|
| 1169 |
+
Args:
|
| 1170 |
+
model (str): Model ID
|
| 1171 |
+
conv (object): Conversation object containing messages
|
| 1172 |
+
temperature (float): Temperature parameter for response generation
|
| 1173 |
+
max_tokens (int): Maximum tokens in response
|
| 1174 |
+
api_dict (dict, optional): API configuration dictionary
|
| 1175 |
+
aws_region (str, optional): AWS region, defaults to "us-west-2"
|
| 1176 |
+
|
| 1177 |
+
Returns:
|
| 1178 |
+
str: Generated response text or error message
|
| 1179 |
+
"""
|
| 1180 |
+
|
| 1181 |
+
# Configure AWS client if api_dict provided
|
| 1182 |
+
if api_dict is not None:
|
| 1183 |
+
bedrock_rt_client = boto3.client(
|
| 1184 |
+
service_name='bedrock-runtime',
|
| 1185 |
+
region_name=aws_region,
|
| 1186 |
+
aws_access_key_id=api_dict.get('aws_access_key_id'),
|
| 1187 |
+
aws_secret_access_key=api_dict.get('aws_secret_access_key')
|
| 1188 |
+
)
|
| 1189 |
+
else:
|
| 1190 |
+
bedrock_rt_client = boto3.client(
|
| 1191 |
+
service_name='bedrock-runtime',
|
| 1192 |
+
region_name=aws_region,)
|
| 1193 |
+
|
| 1194 |
+
output = API_ERROR_OUTPUT
|
| 1195 |
+
|
| 1196 |
+
#get kwargs from settings
|
| 1197 |
+
temperature= kwargs["temperature"]
|
| 1198 |
+
max_tokens= kwargs["max_tokens"]
|
| 1199 |
+
model = kwargs["model_id"]
|
| 1200 |
+
|
| 1201 |
+
# Retry logic for API calls
|
| 1202 |
+
for _ in range(API_MAX_RETRY):
|
| 1203 |
+
try:
|
| 1204 |
+
## =============== Format prompt ================
|
| 1205 |
+
prompt = "\n".join([content for message in messages for content in message["content"]])
|
| 1206 |
+
formatted_prompt = f"<s>[INST] {prompt.strip()} [/INST]"
|
| 1207 |
+
body = {
|
| 1208 |
+
"prompt": formatted_prompt,
|
| 1209 |
+
"max_tokens": max_tokens,
|
| 1210 |
+
"stop": ["Human:"],
|
| 1211 |
+
"temperature": temperature,
|
| 1212 |
+
}
|
| 1213 |
+
|
| 1214 |
+
# Call Bedrock API
|
| 1215 |
+
response = bedrock_rt_client.invoke_model(
|
| 1216 |
+
body=json.dumps(body),
|
| 1217 |
+
modelId=model,
|
| 1218 |
+
accept='application/json',
|
| 1219 |
+
contentType='application/json'
|
| 1220 |
+
)
|
| 1221 |
+
|
| 1222 |
+
# Parse response
|
| 1223 |
+
response_body = json.loads(response.get('body').read())
|
| 1224 |
+
|
| 1225 |
+
if "pixtral-large" in model: #us.mistral.pixtral-large-2502-v1:0
|
| 1226 |
+
output = {"answer": response_body.get("choices")[0].get("message").get("content")}
|
| 1227 |
+
else:
|
| 1228 |
+
output = {"answer": response_body.get("outputs")[0].get("text")}
|
| 1229 |
+
|
| 1230 |
+
break
|
| 1231 |
+
|
| 1232 |
+
except Exception as e:
|
| 1233 |
+
print(type(e), e)
|
| 1234 |
+
time.sleep(API_RETRY_SLEEP)
|
| 1235 |
+
|
| 1236 |
+
return output
|
| 1237 |
+
|
| 1238 |
+
|
| 1239 |
+
@register_api("mistral_streaming")
|
| 1240 |
+
def chat_completion_mistral_streaming(model, messages, temperature, max_tokens, api_dict=None, **kwargs):
|
| 1241 |
+
"""Streaming version of Mistral completion"""
|
| 1242 |
+
import openai
|
| 1243 |
+
|
| 1244 |
+
if api_dict:
|
| 1245 |
+
client = openai.OpenAI(
|
| 1246 |
+
base_url=api_dict["api_base"],
|
| 1247 |
+
api_key=api_dict["api_key"],
|
| 1248 |
+
)
|
| 1249 |
+
else:
|
| 1250 |
+
client = openai.OpenAI()
|
| 1251 |
+
|
| 1252 |
+
try:
|
| 1253 |
+
stream = client.chat.completions.create(
|
| 1254 |
+
model=model,
|
| 1255 |
+
messages=messages,
|
| 1256 |
+
temperature=temperature,
|
| 1257 |
+
max_tokens=max_tokens,
|
| 1258 |
+
stream=True
|
| 1259 |
+
)
|
| 1260 |
+
|
| 1261 |
+
for chunk in stream:
|
| 1262 |
+
if chunk.choices[0].delta.content is not None:
|
| 1263 |
+
yield chunk.choices[0].delta.content
|
| 1264 |
+
|
| 1265 |
+
except Exception as e:
|
| 1266 |
+
print(f"Error in Mistral streaming completion: {e}")
|
| 1267 |
+
yield f"Error: {str(e)}"
|
| 1268 |
+
|
| 1269 |
+
|
| 1270 |
+
@register_api("gemini_streaming")
|
| 1271 |
+
def chat_completion_gemini_streaming(model, messages, **kwargs):
|
| 1272 |
+
"""Streaming version of Gemini completion"""
|
| 1273 |
+
import google.generativeai as genai
|
| 1274 |
+
|
| 1275 |
+
try:
|
| 1276 |
+
# Configure the API
|
| 1277 |
+
genai.configure(api_key=os.environ.get("GEMINI_API_KEY"))
|
| 1278 |
+
|
| 1279 |
+
# Create model
|
| 1280 |
+
model_genai = genai.GenerativeModel(model)
|
| 1281 |
+
|
| 1282 |
+
# Convert messages to Gemini format
|
| 1283 |
+
conversation = model_genai.start_chat(history=[])
|
| 1284 |
+
|
| 1285 |
+
# Get the last user message
|
| 1286 |
+
last_user_message = None
|
| 1287 |
+
for msg in messages:
|
| 1288 |
+
if msg["role"] == "user":
|
| 1289 |
+
last_user_message = msg["content"]
|
| 1290 |
+
|
| 1291 |
+
if not last_user_message:
|
| 1292 |
+
yield "Error: No user message found"
|
| 1293 |
+
return
|
| 1294 |
+
|
| 1295 |
+
# Stream the response
|
| 1296 |
+
response = conversation.send_message(last_user_message, stream=True)
|
| 1297 |
+
|
| 1298 |
+
for chunk in response:
|
| 1299 |
+
if chunk.text:
|
| 1300 |
+
yield chunk.text
|
| 1301 |
+
|
| 1302 |
+
except Exception as e:
|
| 1303 |
+
print(f"Error in Gemini streaming completion: {e}")
|
| 1304 |
+
yield f"Error: {str(e)}"
|
conversation.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core dependencies for BigCodeArena
|
| 2 |
+
gradio
|
| 3 |
+
gradio-sandboxcomponent
|
| 4 |
+
# HTTP and API handling
|
| 5 |
+
requests
|
| 6 |
+
# AWS SDK
|
| 7 |
+
boto3
|
| 8 |
+
# YAML configuration
|
| 9 |
+
PyYAML
|
| 10 |
+
# Progress bars and utilities
|
| 11 |
+
tqdm
|
| 12 |
+
# UUID generation
|
| 13 |
+
shortuuid>=1.0.11
|
| 14 |
+
# Image processing (for vision models)
|
| 15 |
+
Pillow>=10.0.0
|
| 16 |
+
# Optional: FastChat (if using conversation templates)
|
| 17 |
+
# fastchat>=0.2.0
|
| 18 |
+
# Development and testing (optional)
|
| 19 |
+
pytest>=7.4.0
|
| 20 |
+
black>=23.0.0
|
| 21 |
+
flake8>=6.0.0
|
sandbox/__init__.py
ADDED
|
File without changes
|
sandbox/code_analyzer.py
ADDED
|
@@ -0,0 +1,935 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Module for analyzing code snippets to determine the environments, dependencies, and other information needed to run the code.
|
| 3 |
+
'''
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
from enum import StrEnum
|
| 7 |
+
from typing import Any, Generator, TypeAlias, TypedDict, Set
|
| 8 |
+
|
| 9 |
+
import base64
|
| 10 |
+
|
| 11 |
+
import ast
|
| 12 |
+
from tree_sitter import Language, Node, Parser
|
| 13 |
+
import tree_sitter_javascript
|
| 14 |
+
import tree_sitter_typescript
|
| 15 |
+
import sys
|
| 16 |
+
import re
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class SandboxEnvironment(StrEnum):
|
| 20 |
+
AUTO = 'Auto'
|
| 21 |
+
|
| 22 |
+
# Web UI Frameworks
|
| 23 |
+
HTML = 'HTML'
|
| 24 |
+
REACT = 'React'
|
| 25 |
+
VUE = 'Vue'
|
| 26 |
+
GRADIO = 'Gradio'
|
| 27 |
+
STREAMLIT = 'Streamlit'
|
| 28 |
+
PYGAME = 'PyGame'
|
| 29 |
+
MERMAID = 'Mermaid'
|
| 30 |
+
|
| 31 |
+
# Runner
|
| 32 |
+
PYTHON_RUNNER = 'Python Runner'
|
| 33 |
+
JAVASCRIPT_RUNNER = 'Javascript Runner'
|
| 34 |
+
|
| 35 |
+
# Compiler
|
| 36 |
+
C_RUNNER = 'C Runner'
|
| 37 |
+
CPP_RUNNER = 'C++ Runner'
|
| 38 |
+
# CSHARP_RUNNER = 'C# Runner'
|
| 39 |
+
JAVA_RUNNER = 'Java Runner'
|
| 40 |
+
RUST_RUNNER = 'Rust Runner'
|
| 41 |
+
GOLANG_RUNNER = 'Golang Runner'
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def extract_python_imports(code: str) -> list[str]:
|
| 45 |
+
'''
|
| 46 |
+
Extract Python package imports using AST parsing.
|
| 47 |
+
Returns a list of top-level package names.
|
| 48 |
+
'''
|
| 49 |
+
try:
|
| 50 |
+
tree = ast.parse(code)
|
| 51 |
+
except SyntaxError:
|
| 52 |
+
return []
|
| 53 |
+
|
| 54 |
+
packages: Set[str] = set()
|
| 55 |
+
|
| 56 |
+
for node in ast.walk(tree):
|
| 57 |
+
try:
|
| 58 |
+
if isinstance(node, ast.Import):
|
| 59 |
+
for name in node.names:
|
| 60 |
+
# Get the top-level package name from any dotted path
|
| 61 |
+
# e.g., 'foo.bar.baz' -> 'foo'
|
| 62 |
+
if name.name: # Ensure there's a name
|
| 63 |
+
packages.add(name.name.split('.')[0])
|
| 64 |
+
|
| 65 |
+
elif isinstance(node, ast.ImportFrom):
|
| 66 |
+
# Skip relative imports (those starting with dots)
|
| 67 |
+
if node.level == 0 and node.module:
|
| 68 |
+
# Get the top-level package name
|
| 69 |
+
# e.g., from foo.bar import baz -> 'foo'
|
| 70 |
+
packages.add(node.module.split('.')[0])
|
| 71 |
+
|
| 72 |
+
# Also check for common dynamic import patterns
|
| 73 |
+
elif isinstance(node, ast.Call):
|
| 74 |
+
if isinstance(node.func, ast.Name) and node.func.id == 'importlib':
|
| 75 |
+
# Handle importlib.import_module('package')
|
| 76 |
+
if len(node.args) > 0 and isinstance(node.args[0], ast.Str):
|
| 77 |
+
packages.add(node.args[0].s.split('.')[0])
|
| 78 |
+
elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
|
| 79 |
+
# Handle __import__('package') and importlib.import_module('package')
|
| 80 |
+
if node.func.value.id == 'importlib' and node.func.attr == 'import_module':
|
| 81 |
+
if len(node.args) > 0 and isinstance(node.args[0], ast.Str):
|
| 82 |
+
packages.add(node.args[0].s.split('.')[0])
|
| 83 |
+
elif node.func.attr == '__import__':
|
| 84 |
+
if len(node.args) > 0 and isinstance(node.args[0], ast.Str):
|
| 85 |
+
packages.add(node.args[0].s.split('.')[0])
|
| 86 |
+
except Exception as e:
|
| 87 |
+
print(f"Error processing node {type(node)}: {e}")
|
| 88 |
+
continue
|
| 89 |
+
|
| 90 |
+
# Filter out standard library modules using sys.stdlib_module_names
|
| 91 |
+
std_libs = set(sys.stdlib_module_names)
|
| 92 |
+
|
| 93 |
+
return list(packages - std_libs)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def extract_js_imports(code: str) -> list[str]:
|
| 97 |
+
'''
|
| 98 |
+
Extract npm package imports using Tree-sitter for robust parsing.
|
| 99 |
+
Handles both JavaScript and TypeScript code, including Vue SFC.
|
| 100 |
+
Returns a list of package names.
|
| 101 |
+
'''
|
| 102 |
+
try:
|
| 103 |
+
# For Vue SFC, extract the script section first
|
| 104 |
+
script_match = re.search(r'<script.*?>(.*?)</script>', code, re.DOTALL)
|
| 105 |
+
if script_match:
|
| 106 |
+
code = script_match.group(1).strip()
|
| 107 |
+
|
| 108 |
+
# Initialize parsers with language modules
|
| 109 |
+
ts_parser = Parser(Language(tree_sitter_typescript.language_tsx()))
|
| 110 |
+
js_parser = Parser(Language(tree_sitter_javascript.language()))
|
| 111 |
+
|
| 112 |
+
# Try parsing as TypeScript first, then JavaScript
|
| 113 |
+
code_bytes = bytes(code, "utf8")
|
| 114 |
+
try:
|
| 115 |
+
tree = ts_parser.parse(code_bytes)
|
| 116 |
+
except Exception as e:
|
| 117 |
+
print(f"TypeScript parsing failed: {e}")
|
| 118 |
+
try:
|
| 119 |
+
tree = js_parser.parse(code_bytes)
|
| 120 |
+
except Exception as e:
|
| 121 |
+
print(f"JavaScript parsing failed: {e}")
|
| 122 |
+
tree = None
|
| 123 |
+
|
| 124 |
+
if tree is None:
|
| 125 |
+
raise Exception("Both TypeScript and JavaScript parsing failed")
|
| 126 |
+
|
| 127 |
+
packages: Set[str] = set()
|
| 128 |
+
|
| 129 |
+
def extract_package_name(node: Node) -> str | None:
|
| 130 |
+
"""Extract npm package name from string or template string.
|
| 131 |
+
Returns None for local aliases like @/ or relative paths."""
|
| 132 |
+
if node.type in ['string', 'string_fragment']:
|
| 133 |
+
pkg_path = code[node.start_byte:node.end_byte].strip('"\'')
|
| 134 |
+
if pkg_path.startswith('.') or pkg_path.startswith('/') or pkg_path.startswith('@/'):
|
| 135 |
+
return None # relative, absolute, or alias path
|
| 136 |
+
|
| 137 |
+
# Scoped npm package: @scope/package/...
|
| 138 |
+
if pkg_path.startswith('@'):
|
| 139 |
+
parts = pkg_path.split('/')
|
| 140 |
+
if len(parts) >= 2:
|
| 141 |
+
return '/'.join(parts[:2])
|
| 142 |
+
|
| 143 |
+
# Regular npm package: "lodash/cloneDeep" -> "lodash"
|
| 144 |
+
return pkg_path.split('/')[0]
|
| 145 |
+
|
| 146 |
+
elif node.type == 'template_string':
|
| 147 |
+
content = ''
|
| 148 |
+
has_template_var = False
|
| 149 |
+
for child in node.children:
|
| 150 |
+
if child.type == 'string_fragment':
|
| 151 |
+
content += code[child.start_byte:child.end_byte]
|
| 152 |
+
elif child.type == 'template_substitution':
|
| 153 |
+
has_template_var = True
|
| 154 |
+
|
| 155 |
+
if not content or content.startswith('.') or content.startswith('/') or content.startswith('@/'):
|
| 156 |
+
return None
|
| 157 |
+
|
| 158 |
+
if has_template_var:
|
| 159 |
+
if content.endswith('-literal'):
|
| 160 |
+
return 'package-template-literal'
|
| 161 |
+
return None
|
| 162 |
+
|
| 163 |
+
if content.startswith('@'):
|
| 164 |
+
parts = content.split('/')
|
| 165 |
+
if len(parts) >= 2:
|
| 166 |
+
return '/'.join(parts[:2])
|
| 167 |
+
return content.split('/')[0]
|
| 168 |
+
|
| 169 |
+
return None
|
| 170 |
+
|
| 171 |
+
def visit_node(node: Node) -> None:
|
| 172 |
+
if node.type == 'import_statement':
|
| 173 |
+
# Handle ES6 imports
|
| 174 |
+
string_node = node.child_by_field_name('source')
|
| 175 |
+
if string_node:
|
| 176 |
+
pkg_name = extract_package_name(string_node)
|
| 177 |
+
if pkg_name:
|
| 178 |
+
packages.add(pkg_name)
|
| 179 |
+
|
| 180 |
+
elif node.type == 'export_statement':
|
| 181 |
+
# Handle re-exports
|
| 182 |
+
source = node.child_by_field_name('source')
|
| 183 |
+
if source:
|
| 184 |
+
pkg_name = extract_package_name(source)
|
| 185 |
+
if pkg_name:
|
| 186 |
+
packages.add(pkg_name)
|
| 187 |
+
|
| 188 |
+
elif node.type == 'call_expression':
|
| 189 |
+
# Handle require calls and dynamic imports
|
| 190 |
+
func_node = node.child_by_field_name('function')
|
| 191 |
+
if func_node and func_node.text:
|
| 192 |
+
func_name = func_node.text.decode('utf8')
|
| 193 |
+
if func_name in ['require', 'import']:
|
| 194 |
+
args = node.child_by_field_name('arguments')
|
| 195 |
+
if args and args.named_children:
|
| 196 |
+
arg = args.named_children[0]
|
| 197 |
+
pkg_name = extract_package_name(arg)
|
| 198 |
+
if pkg_name:
|
| 199 |
+
packages.add(pkg_name)
|
| 200 |
+
|
| 201 |
+
# Recursively visit children
|
| 202 |
+
for child in node.children:
|
| 203 |
+
visit_node(child)
|
| 204 |
+
|
| 205 |
+
visit_node(tree.root_node)
|
| 206 |
+
return list(packages)
|
| 207 |
+
|
| 208 |
+
except Exception as e:
|
| 209 |
+
print(f"Tree-sitter parsing failed: {e}")
|
| 210 |
+
# Fallback to basic regex parsing if tree-sitter fails
|
| 211 |
+
packages: Set[str] = set()
|
| 212 |
+
|
| 213 |
+
# First try to extract script section for Vue SFC
|
| 214 |
+
script_match = re.search(r'<script.*?>(.*?)</script>', code, re.DOTALL)
|
| 215 |
+
if script_match:
|
| 216 |
+
code = script_match.group(1).strip()
|
| 217 |
+
|
| 218 |
+
# Look for imports
|
| 219 |
+
import_patterns = [
|
| 220 |
+
# dynamic imports
|
| 221 |
+
r'(?:import|require)\s*\(\s*[\'"](@?[\w-]+(?:/[\w-]+)*)[\'"]',
|
| 222 |
+
# static imports
|
| 223 |
+
r'(?:import|from)\s+[\'"](@?[\w-]+(?:/[\w-]+)*)[\'"]',
|
| 224 |
+
# require statements
|
| 225 |
+
r'require\s*\(\s*[\'"](@?[\w-]+(?:/[\w-]+)*)[\'"]',
|
| 226 |
+
]
|
| 227 |
+
|
| 228 |
+
for pattern in import_patterns:
|
| 229 |
+
matches = re.finditer(pattern, code)
|
| 230 |
+
for match in matches:
|
| 231 |
+
pkg_name = match.group(1)
|
| 232 |
+
if not pkg_name.startswith('.'):
|
| 233 |
+
if pkg_name.startswith('@'):
|
| 234 |
+
parts = pkg_name.split('/')
|
| 235 |
+
if len(parts) >= 2:
|
| 236 |
+
packages.add('/'.join(parts[:2]))
|
| 237 |
+
else:
|
| 238 |
+
packages.add(pkg_name.split('/')[0])
|
| 239 |
+
|
| 240 |
+
return list(packages)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def determine_python_environment(code: str, imports: list[str]) -> SandboxEnvironment | None:
|
| 244 |
+
'''
|
| 245 |
+
Determine Python sandbox environment based on imports and AST analysis.
|
| 246 |
+
'''
|
| 247 |
+
try:
|
| 248 |
+
tree = ast.parse(code)
|
| 249 |
+
for node in ast.walk(tree):
|
| 250 |
+
# Check for specific framework usage patterns
|
| 251 |
+
if isinstance(node, ast.Name) and node.id == 'gr':
|
| 252 |
+
return SandboxEnvironment.GRADIO
|
| 253 |
+
elif isinstance(node, ast.Name) and node.id == 'st':
|
| 254 |
+
return SandboxEnvironment.STREAMLIT
|
| 255 |
+
except SyntaxError:
|
| 256 |
+
pass
|
| 257 |
+
|
| 258 |
+
# Check imports for framework detection
|
| 259 |
+
if 'pygame' in imports:
|
| 260 |
+
return SandboxEnvironment.PYGAME
|
| 261 |
+
elif 'gradio' in imports:
|
| 262 |
+
return SandboxEnvironment.GRADIO
|
| 263 |
+
elif 'streamlit' in imports:
|
| 264 |
+
return SandboxEnvironment.STREAMLIT
|
| 265 |
+
# elif 'nicegui' in imports:
|
| 266 |
+
# return SandboxEnvironment.NICEGUI
|
| 267 |
+
|
| 268 |
+
return SandboxEnvironment.PYTHON_RUNNER
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def determine_jsts_environment(code: str, imports: list[str]) -> SandboxEnvironment | None:
|
| 272 |
+
'''
|
| 273 |
+
Determine JavaScript/TypeScript sandbox environment based on imports and AST analysis.
|
| 274 |
+
'''
|
| 275 |
+
# First check for Vue SFC structure
|
| 276 |
+
if '<template>' in code or '<script setup' in code:
|
| 277 |
+
return SandboxEnvironment.VUE
|
| 278 |
+
|
| 279 |
+
# Check imports for framework detection
|
| 280 |
+
react_packages = {'react', '@react', 'next', '@next'}
|
| 281 |
+
vue_packages = {'vue', '@vue', 'nuxt', '@nuxt'}
|
| 282 |
+
|
| 283 |
+
if any(pkg in react_packages for pkg in imports):
|
| 284 |
+
return SandboxEnvironment.REACT
|
| 285 |
+
elif any(pkg in vue_packages for pkg in imports):
|
| 286 |
+
return SandboxEnvironment.VUE
|
| 287 |
+
|
| 288 |
+
try:
|
| 289 |
+
ts_parser = Parser(Language(tree_sitter_typescript.language_tsx()))
|
| 290 |
+
tree = ts_parser.parse(bytes(code, "utf8"))
|
| 291 |
+
|
| 292 |
+
def has_framework_patterns(node: Node) -> tuple[bool, str]:
|
| 293 |
+
# React JSX patterns
|
| 294 |
+
if node.type in ['jsx_element', 'jsx_self_closing_element']:
|
| 295 |
+
return True, 'react'
|
| 296 |
+
# Vue <template> AST element
|
| 297 |
+
elif node.type == 'template_element':
|
| 298 |
+
return True, 'vue'
|
| 299 |
+
# Vue template string with restricted patterns
|
| 300 |
+
elif node.type == 'template_string':
|
| 301 |
+
content = code[node.start_byte:node.end_byte]
|
| 302 |
+
vue_patterns = [
|
| 303 |
+
r'\bv-(if|else|for|bind|on|model|show|html|text)=', # Vue directives
|
| 304 |
+
r'@(?:click|change|input|submit|keyup|keydown)\s*=', # Event bindings
|
| 305 |
+
r':(?:class|style|src|href|value|disabled|checked)\s*=' # Attribute bindings
|
| 306 |
+
]
|
| 307 |
+
if any(re.search(p, content) for p in vue_patterns):
|
| 308 |
+
return True, 'vue'
|
| 309 |
+
return False, ''
|
| 310 |
+
|
| 311 |
+
cursor = tree.walk()
|
| 312 |
+
|
| 313 |
+
def visit_node() -> SandboxEnvironment | None:
|
| 314 |
+
is_framework, framework = has_framework_patterns(cursor.node)
|
| 315 |
+
if is_framework:
|
| 316 |
+
return SandboxEnvironment.REACT if framework == 'react' else SandboxEnvironment.VUE
|
| 317 |
+
if cursor.goto_first_child():
|
| 318 |
+
while True:
|
| 319 |
+
result = visit_node()
|
| 320 |
+
if result:
|
| 321 |
+
return result
|
| 322 |
+
if not cursor.goto_next_sibling():
|
| 323 |
+
break
|
| 324 |
+
cursor.goto_parent()
|
| 325 |
+
return None
|
| 326 |
+
|
| 327 |
+
result = visit_node()
|
| 328 |
+
if result:
|
| 329 |
+
return result
|
| 330 |
+
|
| 331 |
+
# More targeted Vue detection
|
| 332 |
+
vue_patterns = [
|
| 333 |
+
r'export\s+default\s+{[\s\S]*?(components|props|emits|data|methods|computed|watch)\s*:',
|
| 334 |
+
r'defineComponent\s*\(',
|
| 335 |
+
r'Vue\.extend\s*\(',
|
| 336 |
+
r'createApp\s*\(',
|
| 337 |
+
r'\b(ref|reactive|computed|watch|onMounted|onUnmounted|provide|inject)\s*\(',
|
| 338 |
+
r'defineProps\s*\(',
|
| 339 |
+
r'defineEmits\s*\(',
|
| 340 |
+
r'<[a-zA-Z][^>]+\s+(v-(if|else|for|bind|on|model|show|html|text)|@|:)[^>]*>' # in tag context
|
| 341 |
+
]
|
| 342 |
+
|
| 343 |
+
for pattern in vue_patterns:
|
| 344 |
+
if re.search(pattern, code, re.MULTILINE):
|
| 345 |
+
return SandboxEnvironment.VUE
|
| 346 |
+
|
| 347 |
+
except Exception as e:
|
| 348 |
+
print(f"Tree-sitter parsing error: {e}")
|
| 349 |
+
|
| 350 |
+
return SandboxEnvironment.JAVASCRIPT_RUNNER
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def detect_js_ts_code_lang(code: str) -> str:
|
| 354 |
+
'''
|
| 355 |
+
Detect whether code is JavaScript or TypeScript using Tree-sitter AST parsing.
|
| 356 |
+
Handles Vue SFC, React, and regular JS/TS files.
|
| 357 |
+
|
| 358 |
+
Args:
|
| 359 |
+
code (str): The code to analyze
|
| 360 |
+
|
| 361 |
+
Returns:
|
| 362 |
+
str: 'typescript' if TypeScript patterns are found, 'javascript' otherwise
|
| 363 |
+
'''
|
| 364 |
+
# Quick check for explicit TypeScript in Vue SFC
|
| 365 |
+
if '<script lang="ts">' in code or '<script lang="typescript">' in code:
|
| 366 |
+
return 'typescript'
|
| 367 |
+
|
| 368 |
+
try:
|
| 369 |
+
# Initialize TypeScript parser
|
| 370 |
+
ts_parser = Parser(Language(tree_sitter_typescript.language_tsx()))
|
| 371 |
+
|
| 372 |
+
# Parse the code
|
| 373 |
+
tree = ts_parser.parse(bytes(code, "utf8"))
|
| 374 |
+
|
| 375 |
+
def has_typescript_patterns(node: Node) -> bool:
|
| 376 |
+
# Check for TypeScript-specific syntax
|
| 377 |
+
if node.type in {
|
| 378 |
+
'type_annotation', # Type annotations
|
| 379 |
+
'type_alias_declaration', # type Foo = ...
|
| 380 |
+
'interface_declaration', # interface Foo
|
| 381 |
+
'enum_declaration', # enum Foo
|
| 382 |
+
'implements_clause', # implements Interface
|
| 383 |
+
'type_parameter', # Generic type parameters
|
| 384 |
+
'type_assertion', # Type assertions
|
| 385 |
+
'type_predicate', # Type predicates in functions
|
| 386 |
+
'type_arguments', # Generic type arguments
|
| 387 |
+
'readonly_type', # readonly keyword
|
| 388 |
+
'mapped_type', # Mapped types
|
| 389 |
+
'conditional_type', # Conditional types
|
| 390 |
+
'union_type', # Union types
|
| 391 |
+
'intersection_type', # Intersection types
|
| 392 |
+
'tuple_type', # Tuple types
|
| 393 |
+
'optional_parameter', # Optional parameters
|
| 394 |
+
'decorator', # Decorators
|
| 395 |
+
'ambient_declaration', # Ambient declarations
|
| 396 |
+
'declare_statement', # declare keyword
|
| 397 |
+
'accessibility_modifier', # private/protected/public
|
| 398 |
+
}:
|
| 399 |
+
return True
|
| 400 |
+
|
| 401 |
+
# Check for type annotations in variable declarations
|
| 402 |
+
if node.type == 'variable_declarator':
|
| 403 |
+
for child in node.children:
|
| 404 |
+
if child.type == 'type_annotation':
|
| 405 |
+
return True
|
| 406 |
+
|
| 407 |
+
# Check for return type annotations in functions
|
| 408 |
+
if node.type in {'function_declaration', 'method_definition', 'arrow_function'}:
|
| 409 |
+
for child in node.children:
|
| 410 |
+
if child.type == 'type_annotation':
|
| 411 |
+
return True
|
| 412 |
+
|
| 413 |
+
return False
|
| 414 |
+
|
| 415 |
+
# Walk the AST to find TypeScript patterns
|
| 416 |
+
cursor = tree.walk()
|
| 417 |
+
|
| 418 |
+
def visit_node() -> bool:
|
| 419 |
+
if has_typescript_patterns(cursor.node):
|
| 420 |
+
return True
|
| 421 |
+
|
| 422 |
+
# Check children
|
| 423 |
+
if cursor.goto_first_child():
|
| 424 |
+
while True:
|
| 425 |
+
if visit_node():
|
| 426 |
+
return True
|
| 427 |
+
if not cursor.goto_next_sibling():
|
| 428 |
+
break
|
| 429 |
+
cursor.goto_parent()
|
| 430 |
+
|
| 431 |
+
return False
|
| 432 |
+
|
| 433 |
+
if visit_node():
|
| 434 |
+
return 'typescript'
|
| 435 |
+
|
| 436 |
+
except Exception as e:
|
| 437 |
+
print(f"Tree-sitter parsing error: {e}")
|
| 438 |
+
# Fallback to basic checks if parsing fails
|
| 439 |
+
pass
|
| 440 |
+
|
| 441 |
+
return 'javascript'
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
def extract_inline_pip_install_commands(code: str) -> tuple[list[str], str]:
|
| 445 |
+
'''
|
| 446 |
+
Extracts pip install commands from inline code comments and returns both the packages and cleaned code.
|
| 447 |
+
This is useful for cases where pip install commands are written as comments in the code or
|
| 448 |
+
Jupyter notebook-style !pip install commands.
|
| 449 |
+
|
| 450 |
+
Args:
|
| 451 |
+
code (str): The code to analyze.
|
| 452 |
+
|
| 453 |
+
Returns:
|
| 454 |
+
tuple[list[str], str]: A tuple containing:
|
| 455 |
+
1. List of Python packages extracted from pip install commands in comments
|
| 456 |
+
2. Code with the pip install comments removed
|
| 457 |
+
'''
|
| 458 |
+
python_packages = []
|
| 459 |
+
cleaned_lines = []
|
| 460 |
+
|
| 461 |
+
# Regex patterns to match pip install commands in comments and Jupyter-style commands
|
| 462 |
+
pip_patterns = [
|
| 463 |
+
# Comments with pip install
|
| 464 |
+
r'#\s*(?:pip|pip3|python -m pip)\s+install\s+(?:(?:--upgrade|--user|--no-cache-dir|-U)\s+)*([^-\s][\w\-\[\]<>=~\.]+(?:\s+[^-\s][\w\-\[\]<>=~\.]+)*)',
|
| 465 |
+
# Jupyter-style !pip install
|
| 466 |
+
r'!\s*(?:pip|pip3|python -m pip)\s+install\s+(?:(?:--upgrade|--user|--no-cache-dir|-U)\s+)*([^-\s][\w\-\[\]<>=~\.]+(?:\s+[^-\s][\w\-\[\]<>=~\.]+)*)',
|
| 467 |
+
# Requirements file style pip install
|
| 468 |
+
r'(?:#|!)\s*(?:pip|pip3|python -m pip)\s+install\s+(?:-r\s+[\w\-\.\/]+\s+)*([^-\s][\w\-\[\]<>=~\.]+(?:\s+[^-\s][\w\-\[\]<>=~\.]+)*)'
|
| 469 |
+
]
|
| 470 |
+
|
| 471 |
+
# Process each line
|
| 472 |
+
for line in code.splitlines():
|
| 473 |
+
matched = False
|
| 474 |
+
for pattern in pip_patterns:
|
| 475 |
+
match = re.search(pattern, line)
|
| 476 |
+
if match:
|
| 477 |
+
matched = True
|
| 478 |
+
# Extract packages from the command
|
| 479 |
+
pkgs = match.group(1).strip().split()
|
| 480 |
+
# Clean package names (remove version specifiers)
|
| 481 |
+
cleaned_pkgs = [pkg.split('==')[0].split('>=')[0].split('<=')[
|
| 482 |
+
0].split('~=')[0] for pkg in pkgs]
|
| 483 |
+
python_packages.extend(cleaned_pkgs)
|
| 484 |
+
|
| 485 |
+
# Remove the pip install command from the line
|
| 486 |
+
cleaned_line = line[:match.start()].rstrip()
|
| 487 |
+
if cleaned_line: # Only add non-empty lines
|
| 488 |
+
cleaned_lines.append(cleaned_line)
|
| 489 |
+
break
|
| 490 |
+
|
| 491 |
+
if not matched:
|
| 492 |
+
cleaned_lines.append(line)
|
| 493 |
+
|
| 494 |
+
# Remove duplicates while preserving order
|
| 495 |
+
python_packages = list(dict.fromkeys(python_packages))
|
| 496 |
+
|
| 497 |
+
return python_packages, '\n'.join(cleaned_lines)
|
| 498 |
+
|
| 499 |
+
|
| 500 |
+
def extract_js_from_html_script_tags(code: str) -> list[str]:
|
| 501 |
+
'''
|
| 502 |
+
Extract JavaScript package names from HTML script tags.
|
| 503 |
+
Handles both CDN script tags and inline scripts.
|
| 504 |
+
|
| 505 |
+
Args:
|
| 506 |
+
code: HTML code containing script tags
|
| 507 |
+
|
| 508 |
+
Returns:
|
| 509 |
+
list[str]: List of package names
|
| 510 |
+
'''
|
| 511 |
+
packages: Set[str] = set()
|
| 512 |
+
|
| 513 |
+
# Extract packages from CDN script tags
|
| 514 |
+
script_patterns = [
|
| 515 |
+
# unpkg.com pattern
|
| 516 |
+
r'<script[^>]*src="https?://unpkg\.com/(@?[^@/"]+(?:/[^@/"]+)?(?:@[^/"]+)?)[^"]*"[^>]*>',
|
| 517 |
+
# cdn.jsdelivr.net pattern - explicitly handle /npm/ in the path
|
| 518 |
+
r'<script[^>]*src="https?://cdn\.jsdelivr\.net/npm/(@?[^@/"]+(?:/[^@/"]+)?(?:@[^/"]+)?)[^"]*"[^>]*>',
|
| 519 |
+
# Generic CDN pattern for any domain - exclude common path components
|
| 520 |
+
r'<script[^>]*src="https?://(?!(?:[^"]+/)?(?:npm|dist|lib|build|umd|esm|cjs|min)/)[^"]+?/(@?[\w-]+)(?:/[^"]*)?[^"]*"[^>]*>',
|
| 521 |
+
]
|
| 522 |
+
|
| 523 |
+
seen_packages = set() # Track packages we've already added to avoid duplicates
|
| 524 |
+
for pattern in script_patterns:
|
| 525 |
+
matches = re.finditer(pattern, code, re.IGNORECASE)
|
| 526 |
+
for match in matches:
|
| 527 |
+
pkg_name = match.group(1)
|
| 528 |
+
if pkg_name.startswith('@'):
|
| 529 |
+
# Handle scoped packages
|
| 530 |
+
parts = pkg_name.split('/')
|
| 531 |
+
if len(parts) >= 2:
|
| 532 |
+
pkg_name = '/'.join(parts[:2])
|
| 533 |
+
else:
|
| 534 |
+
# Remove version and path components from package name
|
| 535 |
+
pkg_name = pkg_name.split('/')[0].split('@')[0]
|
| 536 |
+
|
| 537 |
+
# Skip common path components and duplicates
|
| 538 |
+
if pkg_name and pkg_name not in seen_packages and not pkg_name.lower() in {'npm', 'dist', 'lib', 'build', 'umd', 'esm', 'cjs', 'min'}:
|
| 539 |
+
seen_packages.add(pkg_name)
|
| 540 |
+
packages.add(pkg_name)
|
| 541 |
+
|
| 542 |
+
# Extract packages from inline scripts
|
| 543 |
+
script_tags = re.finditer(
|
| 544 |
+
r'<script[^>]*>(.*?)</script>', code, re.DOTALL | re.IGNORECASE)
|
| 545 |
+
for script in script_tags:
|
| 546 |
+
script_content = script.group(1)
|
| 547 |
+
# Check for ES module imports with full URLs
|
| 548 |
+
es_module_patterns = [
|
| 549 |
+
# Match imports from CDN URLs, being careful to extract only the package name
|
| 550 |
+
r'import\s+[\w\s{},*]+\s+from\s+[\'"]https?://[^/]+/npm/([^/@"\s]+)[@/][^"]*[\'"]',
|
| 551 |
+
]
|
| 552 |
+
found_cdn_import = False
|
| 553 |
+
for pattern in es_module_patterns:
|
| 554 |
+
matches = re.finditer(pattern, script_content)
|
| 555 |
+
for match in matches:
|
| 556 |
+
pkg_name = match.group(1)
|
| 557 |
+
if pkg_name and pkg_name not in seen_packages and not pkg_name.lower() in {'npm', 'dist', 'lib', 'build', 'umd', 'esm', 'cjs', 'min', 'https', 'http'}:
|
| 558 |
+
seen_packages.add(pkg_name)
|
| 559 |
+
packages.add(pkg_name)
|
| 560 |
+
found_cdn_import = True
|
| 561 |
+
|
| 562 |
+
# Only check for regular imports if we didn't find a CDN import
|
| 563 |
+
if not found_cdn_import:
|
| 564 |
+
# Remove any URL imports before passing to extract_js_imports
|
| 565 |
+
cleaned_content = re.sub(
|
| 566 |
+
r'import\s+[\w\s{},*]+\s+from\s+[\'"]https?://[^"]+[\'"]', '', script_content)
|
| 567 |
+
packages.update(extract_js_imports(cleaned_content))
|
| 568 |
+
|
| 569 |
+
return list(packages)
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
def extract_code_from_markdown(message: str, enable_auto_env: bool = False) -> tuple[str, str, tuple[list[str], list[str]], SandboxEnvironment | None] | None:
|
| 573 |
+
'''
|
| 574 |
+
Extracts code from a markdown message by parsing code blocks directly.
|
| 575 |
+
Determines sandbox environment based on code content and frameworks used.
|
| 576 |
+
|
| 577 |
+
Returns:
|
| 578 |
+
tuple[str, str, tuple[list[str], list[str]], SandboxEnvironment | None]: A tuple:
|
| 579 |
+
1. code - the longest code block found
|
| 580 |
+
2. code language
|
| 581 |
+
3. sandbox python and npm dependencies (extracted using static analysis)
|
| 582 |
+
4. sandbox environment determined from code content
|
| 583 |
+
'''
|
| 584 |
+
code_block_regex = r'```(?P<code_lang>[\w\+\#\-\.]*)?[ \t]*\r?\n?(?P<code>.*?)```'
|
| 585 |
+
matches = list(re.finditer(code_block_regex, message, re.DOTALL))
|
| 586 |
+
|
| 587 |
+
if not matches:
|
| 588 |
+
return None
|
| 589 |
+
|
| 590 |
+
# Define a low-priority list for certain languages
|
| 591 |
+
low_priority_languages = ['bash', 'shell',
|
| 592 |
+
'sh', 'zsh', 'powershell', 'pwsh', '']
|
| 593 |
+
|
| 594 |
+
# Find the main code block by avoiding low-priority languages
|
| 595 |
+
main_code = None
|
| 596 |
+
main_code_lang = None
|
| 597 |
+
max_length = 0
|
| 598 |
+
|
| 599 |
+
for match in matches:
|
| 600 |
+
code = match.group('code').strip()
|
| 601 |
+
code_lang = (match.group('code_lang') or '').lower()
|
| 602 |
+
if code_lang not in low_priority_languages and len(code) > max_length:
|
| 603 |
+
main_code = code
|
| 604 |
+
main_code_lang = code_lang
|
| 605 |
+
max_length = len(code)
|
| 606 |
+
|
| 607 |
+
# Fallback to the longest code block if no main code was found
|
| 608 |
+
if not main_code:
|
| 609 |
+
longest_match = max(matches, key=lambda m: len(m.group('code')))
|
| 610 |
+
main_code = longest_match.group('code').strip()
|
| 611 |
+
main_code_lang = (longest_match.group('code_lang') or '').lower()
|
| 612 |
+
|
| 613 |
+
# Define language prefixes for each environment
|
| 614 |
+
python_prefixes = ['py', 'ipython', 'pygame', 'gradio', 'streamlit']
|
| 615 |
+
vue_prefixes = ['vue']
|
| 616 |
+
react_prefixes = ['react', 'next']
|
| 617 |
+
js_prefixes = ['js', 'javascript', 'jsx', 'coffee', 'ecma', 'node', 'es', 'svelte']
|
| 618 |
+
html_prefixes = ['html', 'xhtml', 'htm']
|
| 619 |
+
ts_prefixes = ['ts', 'typescript', 'tsx']
|
| 620 |
+
mermaid_prefixes = ['mermaid', 'mmd']
|
| 621 |
+
c_prefixes = ['c']
|
| 622 |
+
cpp_prefixes = ['cpp', 'c++']
|
| 623 |
+
go_prefixes = ['go', 'golang']
|
| 624 |
+
java_prefixes = ['java']
|
| 625 |
+
rust_prefixes = ['rust']
|
| 626 |
+
csharp_prefixes = ['cs', 'csharp', 'dotnet']
|
| 627 |
+
|
| 628 |
+
# Extract package dependencies from the main program
|
| 629 |
+
python_packages: list[str] = []
|
| 630 |
+
npm_packages: list[str] = []
|
| 631 |
+
|
| 632 |
+
# Helper function to check if any prefix matches
|
| 633 |
+
def matches_prefix(lang: str, prefixes: list[str]) -> bool:
|
| 634 |
+
return any(lang.lower().startswith(prefix) for prefix in prefixes)
|
| 635 |
+
|
| 636 |
+
if matches_prefix(main_code_lang, python_prefixes):
|
| 637 |
+
python_packages = extract_python_imports(main_code)
|
| 638 |
+
extra_python_packages, main_code = extract_inline_pip_install_commands(
|
| 639 |
+
main_code)
|
| 640 |
+
python_packages.extend(extra_python_packages)
|
| 641 |
+
sandbox_env_name = determine_python_environment(
|
| 642 |
+
main_code, python_packages)
|
| 643 |
+
elif matches_prefix(main_code_lang, vue_prefixes):
|
| 644 |
+
npm_packages = extract_js_imports(main_code)
|
| 645 |
+
sandbox_env_name = SandboxEnvironment.VUE
|
| 646 |
+
main_code_lang = detect_js_ts_code_lang(main_code)
|
| 647 |
+
elif matches_prefix(main_code_lang, react_prefixes):
|
| 648 |
+
npm_packages = extract_js_imports(main_code)
|
| 649 |
+
sandbox_env_name = SandboxEnvironment.REACT
|
| 650 |
+
main_code_lang = detect_js_ts_code_lang(main_code)
|
| 651 |
+
elif ('<!DOCTYPE html>' in main_code and ('<head' in main_code or '<body' in main_code)) or (main_code.strip().startswith('<svg')) or (not matches_prefix(main_code_lang, [*react_prefixes, *vue_prefixes, *js_prefixes, *ts_prefixes]) and ('<html' in main_code or '<!DOCTYPE html>' in main_code)):
|
| 652 |
+
npm_packages = extract_js_from_html_script_tags(main_code)
|
| 653 |
+
sandbox_env_name = SandboxEnvironment.HTML
|
| 654 |
+
main_code_lang = 'html'
|
| 655 |
+
elif matches_prefix(main_code_lang, js_prefixes):
|
| 656 |
+
main_code_lang = 'javascript'
|
| 657 |
+
npm_packages = extract_js_imports(main_code)
|
| 658 |
+
sandbox_env_name = determine_jsts_environment(main_code, npm_packages)
|
| 659 |
+
elif matches_prefix(main_code_lang, ts_prefixes):
|
| 660 |
+
main_code_lang = 'typescript'
|
| 661 |
+
npm_packages = extract_js_imports(main_code)
|
| 662 |
+
sandbox_env_name = determine_jsts_environment(main_code, npm_packages)
|
| 663 |
+
elif matches_prefix(main_code_lang, html_prefixes):
|
| 664 |
+
main_code_lang = detect_js_ts_code_lang(main_code)
|
| 665 |
+
npm_packages = extract_js_imports(main_code)
|
| 666 |
+
sandbox_env_name = determine_jsts_environment(main_code, npm_packages)
|
| 667 |
+
elif matches_prefix(main_code_lang, mermaid_prefixes):
|
| 668 |
+
main_code_lang = 'markdown'
|
| 669 |
+
sandbox_env_name = SandboxEnvironment.MERMAID
|
| 670 |
+
elif matches_prefix(main_code_lang, cpp_prefixes):
|
| 671 |
+
main_code_lang = 'cpp'
|
| 672 |
+
sandbox_env_name = SandboxEnvironment.CPP_RUNNER
|
| 673 |
+
elif matches_prefix(main_code_lang, go_prefixes):
|
| 674 |
+
main_code_lang = 'go'
|
| 675 |
+
sandbox_env_name = SandboxEnvironment.GOLANG_RUNNER
|
| 676 |
+
elif matches_prefix(main_code_lang, java_prefixes):
|
| 677 |
+
main_code_lang = 'java'
|
| 678 |
+
sandbox_env_name = SandboxEnvironment.JAVA_RUNNER
|
| 679 |
+
elif matches_prefix(main_code_lang, rust_prefixes):
|
| 680 |
+
main_code_lang = 'rust'
|
| 681 |
+
sandbox_env_name = SandboxEnvironment.RUST_RUNNER
|
| 682 |
+
elif main_code_lang == 'c':
|
| 683 |
+
main_code_lang = 'c'
|
| 684 |
+
sandbox_env_name = sandbox_env_name = SandboxEnvironment.C_RUNNER
|
| 685 |
+
else:
|
| 686 |
+
sandbox_env_name = None
|
| 687 |
+
|
| 688 |
+
all_python_packages: Set[str] = set(python_packages)
|
| 689 |
+
all_npm_packages: Set[str] = set(npm_packages)
|
| 690 |
+
|
| 691 |
+
for match in matches:
|
| 692 |
+
code = match.group('code').strip()
|
| 693 |
+
if code != main_code:
|
| 694 |
+
install_python_packages, install_npm_packages = extract_installation_commands(
|
| 695 |
+
code)
|
| 696 |
+
all_python_packages.update(install_python_packages)
|
| 697 |
+
all_npm_packages.update(install_npm_packages)
|
| 698 |
+
|
| 699 |
+
if not main_code_lang:
|
| 700 |
+
main_code_lang = 'markdown'
|
| 701 |
+
|
| 702 |
+
return main_code, main_code_lang, (list(all_python_packages), list(all_npm_packages)), sandbox_env_name
|
| 703 |
+
|
| 704 |
+
|
| 705 |
+
def create_placeholder_svg_data_url(width: int, height: int) -> str:
|
| 706 |
+
'''
|
| 707 |
+
Create a data URL for a placeholder image with given dimensions.
|
| 708 |
+
Uses SVG to create an elegant placeholder.
|
| 709 |
+
|
| 710 |
+
Args:
|
| 711 |
+
width: Width of the placeholder image
|
| 712 |
+
height: Height of the placeholder image
|
| 713 |
+
|
| 714 |
+
Returns:
|
| 715 |
+
str: Data URL containing the SVG image
|
| 716 |
+
'''
|
| 717 |
+
# Create SVG with gradient background and text
|
| 718 |
+
# Use simpler SVG structure for better browser compatibility
|
| 719 |
+
svg = f'''<svg width="{width}" height="{height}" xmlns="http://www.w3.org/2000/svg">
|
| 720 |
+
<rect width="100%" height="100%" fill="#f3f4f6"/>
|
| 721 |
+
<text
|
| 722 |
+
x="50%"
|
| 723 |
+
y="50%"
|
| 724 |
+
font-family="Arial, sans-serif"
|
| 725 |
+
font-size="{max(12, min(width, height) // 8)}"
|
| 726 |
+
fill="#6b7280"
|
| 727 |
+
text-anchor="middle"
|
| 728 |
+
dominant-baseline="middle">
|
| 729 |
+
{width} × {height}
|
| 730 |
+
</text>
|
| 731 |
+
</svg>'''
|
| 732 |
+
|
| 733 |
+
# Convert to base64 data URL
|
| 734 |
+
try:
|
| 735 |
+
encoded_svg = base64.b64encode(svg.encode('utf-8')).decode('utf-8')
|
| 736 |
+
return f'data:image/svg+xml;base64,{encoded_svg}'
|
| 737 |
+
except Exception as e:
|
| 738 |
+
print(f'Error encoding SVG: {e}')
|
| 739 |
+
# Fallback to a simple colored div
|
| 740 |
+
return f'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0ie3dpZHRofSIgaGVpZ2h0PSJ7aGVpZ2h0fSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjNmNGY2Ii8+PC9zdmc+'
|
| 741 |
+
|
| 742 |
+
|
| 743 |
+
def replace_placeholder_urls(code: str) -> str:
|
| 744 |
+
'''
|
| 745 |
+
Replace placeholder image URLs with SVG data URLs.
|
| 746 |
+
Only replaces exact matches of "/api/placeholder/{width}/{height}".
|
| 747 |
+
|
| 748 |
+
Args:
|
| 749 |
+
code: The source code containing placeholder URLs
|
| 750 |
+
|
| 751 |
+
Returns:
|
| 752 |
+
str: Code with placeholder URLs replaced with data URLs
|
| 753 |
+
'''
|
| 754 |
+
|
| 755 |
+
def replacer(match: re.Match) -> str:
|
| 756 |
+
try:
|
| 757 |
+
# Extract width and height from the URL using capturing groups
|
| 758 |
+
width = int(match.group(1))
|
| 759 |
+
height = int(match.group(2))
|
| 760 |
+
|
| 761 |
+
# Validate dimensions
|
| 762 |
+
if width <= 0 or height <= 0:
|
| 763 |
+
print(f'Warning: Invalid dimensions {width}x{height}, using default 100x100')
|
| 764 |
+
width, height = 100, 100
|
| 765 |
+
elif width > 10000 or height > 10000:
|
| 766 |
+
print(f'Warning: Dimensions {width}x{height} are very large, capping at 1000x1000')
|
| 767 |
+
width, height = min(width, 1000), min(height, 1000)
|
| 768 |
+
|
| 769 |
+
print(f'Replacing placeholder URL with SVG: {width}x{height}')
|
| 770 |
+
data_url = create_placeholder_svg_data_url(width, height)
|
| 771 |
+
return data_url
|
| 772 |
+
except Exception as e:
|
| 773 |
+
print(f'Error replacing placeholder URL: {e}')
|
| 774 |
+
# Return a simple fallback
|
| 775 |
+
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjNmNGY2Ii8+PC9zdmc+'
|
| 776 |
+
|
| 777 |
+
# Regular expression pattern to match placeholder URLs
|
| 778 |
+
pattern = r'/api/placeholder/(\d+)/(\d+)'
|
| 779 |
+
|
| 780 |
+
try:
|
| 781 |
+
# Replace all occurrences
|
| 782 |
+
result = re.sub(pattern, replacer, code)
|
| 783 |
+
print(f'Placeholder URL replacement completed successfully')
|
| 784 |
+
return result
|
| 785 |
+
except Exception as e:
|
| 786 |
+
print(f'Error during placeholder URL replacement: {e}')
|
| 787 |
+
return code # Return original code if replacement fails
|
| 788 |
+
|
| 789 |
+
|
| 790 |
+
def extract_installation_commands(code: str) -> tuple[list[str], list[str]]:
|
| 791 |
+
'''
|
| 792 |
+
Extracts package installation commands from the code block, preserving version information.
|
| 793 |
+
|
| 794 |
+
Args:
|
| 795 |
+
code (str): The code block to analyze.
|
| 796 |
+
|
| 797 |
+
Returns:
|
| 798 |
+
tuple[list[str], list[str]]: A tuple containing two lists:
|
| 799 |
+
1. Python packages from pip install commands (with versions if specified).
|
| 800 |
+
2. npm packages from npm install commands (with versions if specified).
|
| 801 |
+
'''
|
| 802 |
+
python_packages = []
|
| 803 |
+
npm_packages = []
|
| 804 |
+
|
| 805 |
+
# Process the code line by line to handle both pip and npm commands
|
| 806 |
+
lines = code.split('\n')
|
| 807 |
+
for line in lines:
|
| 808 |
+
line = line.strip()
|
| 809 |
+
|
| 810 |
+
# Skip empty lines and comments
|
| 811 |
+
if not line or line.startswith('#'):
|
| 812 |
+
continue
|
| 813 |
+
|
| 814 |
+
# Handle pip install commands
|
| 815 |
+
if any(x in line for x in ['pip install', 'pip3 install', 'python -m pip install']):
|
| 816 |
+
# Remove the command part and any flags
|
| 817 |
+
parts = line.split('install', 1)[1].strip()
|
| 818 |
+
# Handle flags at the start
|
| 819 |
+
while parts.startswith(('-', '--')):
|
| 820 |
+
parts = parts.split(None, 1)[1]
|
| 821 |
+
|
| 822 |
+
# Split by whitespace, respecting quotes
|
| 823 |
+
current = ''
|
| 824 |
+
in_quotes = False
|
| 825 |
+
quote_char = None
|
| 826 |
+
packages = []
|
| 827 |
+
|
| 828 |
+
for char in parts:
|
| 829 |
+
if char in '"\'':
|
| 830 |
+
if not in_quotes:
|
| 831 |
+
in_quotes = True
|
| 832 |
+
quote_char = char
|
| 833 |
+
elif char == quote_char:
|
| 834 |
+
in_quotes = False
|
| 835 |
+
quote_char = None
|
| 836 |
+
elif char.isspace() and not in_quotes:
|
| 837 |
+
if current:
|
| 838 |
+
packages.append(current)
|
| 839 |
+
current = ''
|
| 840 |
+
else:
|
| 841 |
+
current += char
|
| 842 |
+
if current:
|
| 843 |
+
packages.append(current)
|
| 844 |
+
|
| 845 |
+
# Add packages, stripping quotes and ignoring flags
|
| 846 |
+
for pkg in packages:
|
| 847 |
+
pkg = pkg.strip('"\'')
|
| 848 |
+
if pkg and not pkg.startswith(('-', '--')) and not pkg == '-r':
|
| 849 |
+
python_packages.append(pkg)
|
| 850 |
+
|
| 851 |
+
# Handle npm/yarn install commands
|
| 852 |
+
elif any(x in line for x in ['npm install', 'npm i', 'yarn add']):
|
| 853 |
+
# Remove the command part and any flags
|
| 854 |
+
if 'yarn add' in line:
|
| 855 |
+
parts = line.split('add', 1)[1]
|
| 856 |
+
else:
|
| 857 |
+
parts = line.split('install', 1)[
|
| 858 |
+
1] if 'install' in line else line.split('i', 1)[1]
|
| 859 |
+
parts = parts.strip()
|
| 860 |
+
|
| 861 |
+
# Handle flags at the start
|
| 862 |
+
while parts.startswith(('-', '--')):
|
| 863 |
+
parts = parts.split(None, 1)[1] if ' ' in parts else ''
|
| 864 |
+
|
| 865 |
+
# Process each package
|
| 866 |
+
for pkg in parts.split():
|
| 867 |
+
if pkg.startswith(('-', '--')) or pkg in ('install', 'i', 'add'):
|
| 868 |
+
continue
|
| 869 |
+
|
| 870 |
+
if pkg.startswith('@'):
|
| 871 |
+
# Handle scoped packages (e.g., @types/node@16.0.0)
|
| 872 |
+
if '@' in pkg[1:]: # Has version
|
| 873 |
+
pkg_parts = pkg.rsplit('@', 1)
|
| 874 |
+
base_pkg = pkg_parts[0] # @scope/name
|
| 875 |
+
version = pkg_parts[1] # version
|
| 876 |
+
npm_packages.append(f"{base_pkg}@{version}")
|
| 877 |
+
else:
|
| 878 |
+
npm_packages.append(pkg)
|
| 879 |
+
else:
|
| 880 |
+
npm_packages.append(pkg)
|
| 881 |
+
|
| 882 |
+
# Remove duplicates while preserving order
|
| 883 |
+
python_packages = list(dict.fromkeys(python_packages))
|
| 884 |
+
npm_packages = list(dict.fromkeys(npm_packages))
|
| 885 |
+
|
| 886 |
+
# Filter out npm command words
|
| 887 |
+
npm_packages = [p for p in npm_packages if p not in (
|
| 888 |
+
'npm', 'install', 'i', 'add')]
|
| 889 |
+
|
| 890 |
+
return python_packages, npm_packages
|
| 891 |
+
|
| 892 |
+
|
| 893 |
+
def validate_dependencies(dependencies: list) -> tuple[bool, str]:
|
| 894 |
+
"""
|
| 895 |
+
Validate dependency list format and values.
|
| 896 |
+
Allows empty rows but validates format when package name is specified.
|
| 897 |
+
"""
|
| 898 |
+
if not dependencies:
|
| 899 |
+
return True, ""
|
| 900 |
+
|
| 901 |
+
valid_types = ["python", "npm"]
|
| 902 |
+
for dep in dependencies:
|
| 903 |
+
# Skip validation for empty rows
|
| 904 |
+
if len(dep) != 3:
|
| 905 |
+
return False, "Each dependency must have type, package and version fields"
|
| 906 |
+
|
| 907 |
+
dep_type, pkg_name, version = dep
|
| 908 |
+
|
| 909 |
+
# Skip empty rows
|
| 910 |
+
if not pkg_name.strip():
|
| 911 |
+
continue
|
| 912 |
+
|
| 913 |
+
if dep_type.lower() not in valid_types:
|
| 914 |
+
return False, f"Invalid dependency type: {dep_type}"
|
| 915 |
+
|
| 916 |
+
# Validate version format if specified
|
| 917 |
+
if version.strip():
|
| 918 |
+
if dep_type.lower() == "python":
|
| 919 |
+
# Check for valid pip version specifiers
|
| 920 |
+
if not any(op in version for op in ['==', '>=', '<=', '~=', '>', '<']) and version.lower() != "latest":
|
| 921 |
+
return False, f"Invalid Python version format for {pkg_name}: {version}"
|
| 922 |
+
elif dep_type.lower() == "npm":
|
| 923 |
+
# Check for valid npm version format (starts with @ or valid semver-like)
|
| 924 |
+
if not (version.startswith('@') or version.lower() == "latest"):
|
| 925 |
+
return False, f"Invalid NPM version format for {pkg_name}: {version}"
|
| 926 |
+
|
| 927 |
+
return True, ""
|
| 928 |
+
|
| 929 |
+
|
| 930 |
+
def extract_java_class_name(java_code: str) -> str:
|
| 931 |
+
'''
|
| 932 |
+
Extract the class name from Java code.
|
| 933 |
+
'''
|
| 934 |
+
match = re.search(r'public\s+class\s+(\w+)', java_code)
|
| 935 |
+
return match.group(1) if match else "Main"
|
sandbox/code_runner.py
ADDED
|
@@ -0,0 +1,1612 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Run generated code in a sandbox environment.
|
| 3 |
+
|
| 4 |
+
Gradio will interact with this module.
|
| 5 |
+
'''
|
| 6 |
+
|
| 7 |
+
from typing import Any, Generator, Literal, TypeAlias, TypedDict, Set
|
| 8 |
+
import uuid
|
| 9 |
+
import gradio as gr
|
| 10 |
+
|
| 11 |
+
import base64
|
| 12 |
+
from e2b_code_interpreter import Sandbox as CodeSandbox
|
| 13 |
+
from gradio_sandboxcomponent import SandboxComponent
|
| 14 |
+
|
| 15 |
+
from sandbox.sandbox_state import ChatbotSandboxState
|
| 16 |
+
from sandbox.code_analyzer import SandboxEnvironment, extract_code_from_markdown, extract_installation_commands, extract_java_class_name, extract_js_imports, extract_python_imports, replace_placeholder_urls, validate_dependencies
|
| 17 |
+
from sandbox.prompts import (
|
| 18 |
+
DEFAULT_C_CODE_RUN_SANDBOX_INSTRUCTION, DEFAULT_CPP_CODE_RUN_SANDBOX_INSTRUCTION, DEFAULT_GOLANG_CODE_RUN_SANDBOX_INSTRUCTION, DEFAULT_GRADIO_SANDBOX_INSTRUCTION, DEFAULT_HTML_SANDBOX_INSTRUCTION, DEFAULT_JAVA_CODE_RUN_SANDBOX_INSTRUCTION, DEFAULT_JAVASCRIPT_RUNNER_INSTRUCTION, DEFAULT_MERMAID_SANDBOX_INSTRUCTION, DEFAULT_PYGAME_SANDBOX_INSTRUCTION, DEFAULT_PYTHON_RUNNER_INSTRUCTION, DEFAULT_REACT_SANDBOX_INSTRUCTION, DEFAULT_RUST_CODE_RUN_SANDBOX_INSTRUCTION, DEFAULT_STREAMLIT_SANDBOX_INSTRUCTION, DEFAULT_VUE_SANDBOX_INSTRUCTION, GENERAL_SANDBOX_INSTRUCTION
|
| 19 |
+
)
|
| 20 |
+
from sandbox.sandbox_telemetry import log_sandbox_telemetry_gradio_fn
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
from .constants import CODE_RUN_TIMEOUT_SECONDS, E2B_API_KEY, SANDBOX_TEMPLATE_ID, SANDBOX_NGINX_PORT
|
| 24 |
+
from .sandbox_manager import get_sandbox_app_url, create_sandbox, install_npm_dependencies, install_pip_dependencies, reuse_or_create_sandbox, run_background_command_with_timeout, run_command_in_sandbox
|
| 25 |
+
|
| 26 |
+
SUPPORTED_SANDBOX_ENVIRONMENTS: list[str] = [
|
| 27 |
+
env.value for env in SandboxEnvironment
|
| 28 |
+
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
WEB_UI_SANDBOX_ENVIRONMENTS = [
|
| 32 |
+
SandboxEnvironment.HTML,
|
| 33 |
+
SandboxEnvironment.REACT,
|
| 34 |
+
SandboxEnvironment.VUE,
|
| 35 |
+
SandboxEnvironment.GRADIO,
|
| 36 |
+
SandboxEnvironment.STREAMLIT,
|
| 37 |
+
# SandboxEnvironment.NICEGUI,
|
| 38 |
+
SandboxEnvironment.PYGAME,
|
| 39 |
+
SandboxEnvironment.MERMAID
|
| 40 |
+
]
|
| 41 |
+
'''
|
| 42 |
+
Sandbox environments that can be rendered in the web UI.
|
| 43 |
+
'''
|
| 44 |
+
|
| 45 |
+
VALID_GRADIO_CODE_LANGUAGES = [
|
| 46 |
+
'python', 'c', 'cpp', 'markdown', 'json', 'html', 'css', 'javascript', 'jinja2', 'typescript', 'yaml', 'dockerfile', 'shell', 'r', 'sql',
|
| 47 |
+
'sql-msSQL', 'sql-mySQL', 'sql-mariaDB', 'sql-sqlite', 'sql-cassandra', 'sql-plSQL', 'sql-hive', 'sql-pgSQL', 'sql-gql', 'sql-gpSQL', 'sql-sparkSQL',
|
| 48 |
+
'sql-esper'
|
| 49 |
+
]
|
| 50 |
+
'''
|
| 51 |
+
Languages that gradio code component can render.
|
| 52 |
+
'''
|
| 53 |
+
|
| 54 |
+
RUN_CODE_BUTTON_HTML = "<button style='background-color: #4CAF50; border: none; color: white; padding: 10px 24px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 12px;'>Click to Run in Sandbox</button>"
|
| 55 |
+
'''
|
| 56 |
+
Button in the chat to run the code in the sandbox.
|
| 57 |
+
'''
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
DEFAULT_SANDBOX_INSTRUCTIONS: dict[SandboxEnvironment, str] = {
|
| 61 |
+
SandboxEnvironment.AUTO: GENERAL_SANDBOX_INSTRUCTION.strip(),
|
| 62 |
+
SandboxEnvironment.PYTHON_RUNNER: DEFAULT_PYTHON_RUNNER_INSTRUCTION.strip(),
|
| 63 |
+
SandboxEnvironment.JAVASCRIPT_RUNNER: DEFAULT_JAVASCRIPT_RUNNER_INSTRUCTION.strip(),
|
| 64 |
+
SandboxEnvironment.HTML: DEFAULT_HTML_SANDBOX_INSTRUCTION.strip(),
|
| 65 |
+
SandboxEnvironment.REACT: DEFAULT_REACT_SANDBOX_INSTRUCTION.strip(),
|
| 66 |
+
SandboxEnvironment.VUE: DEFAULT_VUE_SANDBOX_INSTRUCTION.strip(),
|
| 67 |
+
SandboxEnvironment.GRADIO: DEFAULT_GRADIO_SANDBOX_INSTRUCTION.strip(),
|
| 68 |
+
SandboxEnvironment.STREAMLIT: DEFAULT_STREAMLIT_SANDBOX_INSTRUCTION.strip(),
|
| 69 |
+
SandboxEnvironment.PYGAME: DEFAULT_PYGAME_SANDBOX_INSTRUCTION.strip(),
|
| 70 |
+
SandboxEnvironment.MERMAID: DEFAULT_MERMAID_SANDBOX_INSTRUCTION.strip(),
|
| 71 |
+
# Runners
|
| 72 |
+
SandboxEnvironment.C_RUNNER: DEFAULT_C_CODE_RUN_SANDBOX_INSTRUCTION,
|
| 73 |
+
SandboxEnvironment.CPP_RUNNER: DEFAULT_CPP_CODE_RUN_SANDBOX_INSTRUCTION,
|
| 74 |
+
SandboxEnvironment.JAVA_RUNNER: DEFAULT_JAVA_CODE_RUN_SANDBOX_INSTRUCTION,
|
| 75 |
+
SandboxEnvironment.GOLANG_RUNNER: DEFAULT_GOLANG_CODE_RUN_SANDBOX_INSTRUCTION,
|
| 76 |
+
SandboxEnvironment.RUST_RUNNER: DEFAULT_RUST_CODE_RUN_SANDBOX_INSTRUCTION,
|
| 77 |
+
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
SandboxGradioSandboxComponents: TypeAlias = tuple[
|
| 82 |
+
gr.Markdown | Any, # sandbox_output_md
|
| 83 |
+
SandboxComponent | Any, # sandbox_ui
|
| 84 |
+
gr.Code | Any, # sandbox_code
|
| 85 |
+
Any
|
| 86 |
+
]
|
| 87 |
+
'''
|
| 88 |
+
Gradio components for the sandbox.
|
| 89 |
+
'''
|
| 90 |
+
|
| 91 |
+
class CodeRunResult(TypedDict):
|
| 92 |
+
'''
|
| 93 |
+
The result of running the code in the sandbox.
|
| 94 |
+
'''
|
| 95 |
+
sandbox_id: str
|
| 96 |
+
'''
|
| 97 |
+
The sandbox id to run the code.
|
| 98 |
+
'''
|
| 99 |
+
sandbox_url: str
|
| 100 |
+
'''
|
| 101 |
+
The sandbox url to access the rendered results.
|
| 102 |
+
'''
|
| 103 |
+
is_run_success: bool
|
| 104 |
+
'''
|
| 105 |
+
Whether the code run is successful.
|
| 106 |
+
'''
|
| 107 |
+
stderr: str
|
| 108 |
+
'''
|
| 109 |
+
The stderr output from the sandbox.
|
| 110 |
+
'''
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def create_chatbot_sandbox_state(btn_list_length: int = 5) -> ChatbotSandboxState:
|
| 114 |
+
'''
|
| 115 |
+
Create a new sandbox state for a chatbot.
|
| 116 |
+
'''
|
| 117 |
+
return {
|
| 118 |
+
'enable_sandbox': True, # Always enabled
|
| 119 |
+
'enabled_round': 0,
|
| 120 |
+
'sandbox_run_round': 0,
|
| 121 |
+
'edit_round': 0,
|
| 122 |
+
'sandbox_environment': SandboxEnvironment.AUTO,
|
| 123 |
+
'auto_selected_sandbox_environment': None,
|
| 124 |
+
'sandbox_instruction': DEFAULT_SANDBOX_INSTRUCTIONS[SandboxEnvironment.AUTO],
|
| 125 |
+
'code_to_execute': "",
|
| 126 |
+
'code_language': None,
|
| 127 |
+
'code_dependencies': ([], []),
|
| 128 |
+
'btn_list_length': btn_list_length,
|
| 129 |
+
'sandbox_id': None,
|
| 130 |
+
'chat_session_id': None,
|
| 131 |
+
'conv_id': None,
|
| 132 |
+
"sandbox_output": None,
|
| 133 |
+
"sandbox_error": None,
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def set_sandbox_state_ids(
|
| 138 |
+
sandbox_state: ChatbotSandboxState,
|
| 139 |
+
conv_id: str,
|
| 140 |
+
chat_session_id: str,
|
| 141 |
+
) -> ChatbotSandboxState:
|
| 142 |
+
'''
|
| 143 |
+
Set the conv_id and chat_session_id in the sandbox state.
|
| 144 |
+
'''
|
| 145 |
+
sandbox_state['conv_id'] = conv_id
|
| 146 |
+
sandbox_state['chat_session_id'] = chat_session_id
|
| 147 |
+
return sandbox_state
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def reset_sandbox_state(state: ChatbotSandboxState) -> ChatbotSandboxState:
|
| 151 |
+
'''
|
| 152 |
+
Reset the sandbox state.
|
| 153 |
+
Used when the chatbot session is reset.
|
| 154 |
+
'''
|
| 155 |
+
# reset rounds
|
| 156 |
+
state['enabled_round'] = 0
|
| 157 |
+
state['sandbox_run_round'] = 0
|
| 158 |
+
state['edit_round'] = 0
|
| 159 |
+
|
| 160 |
+
# state['sandbox_environment'] = SandboxEnvironment.AUTO
|
| 161 |
+
state['auto_selected_sandbox_environment'] = None
|
| 162 |
+
state['sandbox_instruction'] = DEFAULT_SANDBOX_INSTRUCTIONS[SandboxEnvironment.AUTO]
|
| 163 |
+
state['code_to_execute'] = ""
|
| 164 |
+
state['code_language'] = None
|
| 165 |
+
state['code_dependencies'] = ([], [])
|
| 166 |
+
state['sandbox_error'] = None
|
| 167 |
+
state['sandbox_output'] = None
|
| 168 |
+
|
| 169 |
+
# reset ids
|
| 170 |
+
state['sandbox_id'] = None
|
| 171 |
+
state['conv_id'] = None
|
| 172 |
+
state['chat_session_id'] = None
|
| 173 |
+
|
| 174 |
+
return state
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def update_sandbox_config_multi(
|
| 178 |
+
enable_sandbox: bool,
|
| 179 |
+
sandbox_environment: SandboxEnvironment,
|
| 180 |
+
*states: ChatbotSandboxState
|
| 181 |
+
) -> list[ChatbotSandboxState]:
|
| 182 |
+
'''
|
| 183 |
+
Fn to update sandbox config.
|
| 184 |
+
'''
|
| 185 |
+
return [
|
| 186 |
+
update_sandbox_config(enable_sandbox, sandbox_environment, state)
|
| 187 |
+
for state
|
| 188 |
+
in states
|
| 189 |
+
]
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def update_sandbox_state_system_prompt(sandbox_state: ChatbotSandboxState, system_prompt: str):
|
| 193 |
+
if sandbox_state['enabled_round'] == 0:
|
| 194 |
+
sandbox_state['sandbox_instruction'] = system_prompt
|
| 195 |
+
return sandbox_state
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def update_sandbox_config(
|
| 199 |
+
enable_sandbox: bool,
|
| 200 |
+
sandbox_environment: SandboxEnvironment,
|
| 201 |
+
state: ChatbotSandboxState
|
| 202 |
+
) -> ChatbotSandboxState:
|
| 203 |
+
'''
|
| 204 |
+
Fn to update sandbox config for single model.
|
| 205 |
+
'''
|
| 206 |
+
state["enable_sandbox"] = enable_sandbox
|
| 207 |
+
state["sandbox_environment"] = sandbox_environment
|
| 208 |
+
state['sandbox_instruction'] = DEFAULT_SANDBOX_INSTRUCTIONS.get(sandbox_environment, None)
|
| 209 |
+
return state
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def update_visibility(visible):
|
| 213 |
+
return [gr.update(visible=visible)] *14
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def update_visibility_for_single_model(visible: bool, component_cnt: int):
|
| 217 |
+
return [gr.update(visible=visible)] * component_cnt
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def mermaid_to_html(mermaid_code: str, theme: str = 'default') -> str:
|
| 221 |
+
"""
|
| 222 |
+
Convert Mermaid diagram code to a minimal HTML document.
|
| 223 |
+
|
| 224 |
+
Args:
|
| 225 |
+
mermaid_code: The Mermaid diagram syntax
|
| 226 |
+
theme: Theme name ('default', 'dark', 'forest', 'neutral', etc.)
|
| 227 |
+
|
| 228 |
+
Returns:
|
| 229 |
+
str: Complete HTML document with embedded Mermaid diagram
|
| 230 |
+
"""
|
| 231 |
+
html_template = f'''<!DOCTYPE html>
|
| 232 |
+
<html>
|
| 233 |
+
<head>
|
| 234 |
+
<meta charset="UTF-8">
|
| 235 |
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
|
| 236 |
+
<script>
|
| 237 |
+
mermaid.initialize({{
|
| 238 |
+
startOnLoad: true,
|
| 239 |
+
theme: '{theme}',
|
| 240 |
+
securityLevel: 'loose',
|
| 241 |
+
fontFamily: 'Arial, sans-serif'
|
| 242 |
+
}});
|
| 243 |
+
</script>
|
| 244 |
+
</head>
|
| 245 |
+
<body>
|
| 246 |
+
<div class="mermaid">
|
| 247 |
+
{mermaid_code}
|
| 248 |
+
</div>
|
| 249 |
+
</body>
|
| 250 |
+
</html>'''
|
| 251 |
+
return html_template
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def javascript_to_html(javascript_code: str) -> str:
|
| 255 |
+
"""
|
| 256 |
+
Convert JavaScript code to a minimal HTML document that executes the code.
|
| 257 |
+
|
| 258 |
+
Args:
|
| 259 |
+
javascript_code: The JavaScript code to embed
|
| 260 |
+
|
| 261 |
+
Returns:
|
| 262 |
+
str: Complete HTML document with embedded JavaScript code
|
| 263 |
+
"""
|
| 264 |
+
html_template = f'''<!DOCTYPE html>
|
| 265 |
+
<html>
|
| 266 |
+
<head>
|
| 267 |
+
<meta charset="UTF-8">
|
| 268 |
+
<title>JavaScript Code Execution</title>
|
| 269 |
+
<style>
|
| 270 |
+
body {{
|
| 271 |
+
font-family: Arial, sans-serif;
|
| 272 |
+
margin: 20px;
|
| 273 |
+
background-color: #f5f5f5;
|
| 274 |
+
}}
|
| 275 |
+
#output {{
|
| 276 |
+
background-color: white;
|
| 277 |
+
border: 1px solid #ddd;
|
| 278 |
+
border-radius: 4px;
|
| 279 |
+
padding: 15px;
|
| 280 |
+
margin-top: 20px;
|
| 281 |
+
white-space: pre-wrap;
|
| 282 |
+
font-family: monospace;
|
| 283 |
+
}}
|
| 284 |
+
.error {{
|
| 285 |
+
color: red;
|
| 286 |
+
}}
|
| 287 |
+
.log {{
|
| 288 |
+
color: black;
|
| 289 |
+
}}
|
| 290 |
+
</style>
|
| 291 |
+
</head>
|
| 292 |
+
<body>
|
| 293 |
+
<h1>JavaScript Code Execution</h1>
|
| 294 |
+
<div id="output"></div>
|
| 295 |
+
|
| 296 |
+
<script>
|
| 297 |
+
// Override console methods to capture output
|
| 298 |
+
const outputDiv = document.getElementById('output');
|
| 299 |
+
const originalConsole = {{}};
|
| 300 |
+
|
| 301 |
+
['log', 'error', 'warn', 'info'].forEach(function(method) {{
|
| 302 |
+
originalConsole[method] = console[method];
|
| 303 |
+
console[method] = function(...args) {{
|
| 304 |
+
const message = args.map(function(arg) {{
|
| 305 |
+
return typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg);
|
| 306 |
+
}}).join(' ');
|
| 307 |
+
|
| 308 |
+
const span = document.createElement('span');
|
| 309 |
+
span.className = method === 'error' ? 'error' : 'log';
|
| 310 |
+
span.textContent = '[' + method.toUpperCase() + '] ' + message + '\\n';
|
| 311 |
+
outputDiv.appendChild(span);
|
| 312 |
+
|
| 313 |
+
// Also call original console method
|
| 314 |
+
originalConsole[method].apply(console, args);
|
| 315 |
+
}};
|
| 316 |
+
}});
|
| 317 |
+
|
| 318 |
+
// Capture uncaught errors
|
| 319 |
+
window.addEventListener('error', function(e) {{
|
| 320 |
+
const span = document.createElement('span');
|
| 321 |
+
span.className = 'error';
|
| 322 |
+
span.textContent = '[ERROR] ' + e.message + ' at line ' + e.lineno + '\\n';
|
| 323 |
+
outputDiv.appendChild(span);
|
| 324 |
+
}});
|
| 325 |
+
|
| 326 |
+
try {{
|
| 327 |
+
// Execute the user's JavaScript code
|
| 328 |
+
{javascript_code}
|
| 329 |
+
}} catch (error) {{
|
| 330 |
+
console.error('Execution error:', error.message);
|
| 331 |
+
}}
|
| 332 |
+
</script>
|
| 333 |
+
</body>
|
| 334 |
+
</html>'''
|
| 335 |
+
return html_template
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def render_result(result):
|
| 339 |
+
if result.png:
|
| 340 |
+
if isinstance(result.png, str):
|
| 341 |
+
img_str = result.png
|
| 342 |
+
else:
|
| 343 |
+
img_str = base64.b64encode(result.png).decode()
|
| 344 |
+
return f""
|
| 345 |
+
elif result.jpeg:
|
| 346 |
+
if isinstance(result.jpeg, str):
|
| 347 |
+
img_str = result.jpeg
|
| 348 |
+
else:
|
| 349 |
+
img_str = base64.b64encode(result.jpeg).decode()
|
| 350 |
+
return f""
|
| 351 |
+
elif result.svg:
|
| 352 |
+
if isinstance(result.svg, str):
|
| 353 |
+
svg_data = result.svg
|
| 354 |
+
else:
|
| 355 |
+
svg_data = result.svg.decode()
|
| 356 |
+
svg_base64 = base64.b64encode(svg_data.encode()).decode()
|
| 357 |
+
return f""
|
| 358 |
+
elif result.html:
|
| 359 |
+
return result.html
|
| 360 |
+
elif result.markdown:
|
| 361 |
+
return f"```markdown\n{result.markdown}\n```"
|
| 362 |
+
elif result.latex:
|
| 363 |
+
return f"```latex\n{result.latex}\n```"
|
| 364 |
+
elif result.json:
|
| 365 |
+
return f"```json\n{result.json}\n```"
|
| 366 |
+
elif result.javascript:
|
| 367 |
+
return result.javascript # Return raw JavaScript
|
| 368 |
+
else:
|
| 369 |
+
return str(result)
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def run_code_interpreter(code: str, code_language: str | None, code_dependencies: tuple[list[str], list[str]]) -> tuple[str, str]:
|
| 373 |
+
"""
|
| 374 |
+
Executes the provided code within a sandboxed environment and returns the output.
|
| 375 |
+
|
| 376 |
+
Args:
|
| 377 |
+
code (str): The code to be executed.
|
| 378 |
+
"""
|
| 379 |
+
sandbox = CodeSandbox(
|
| 380 |
+
api_key=E2B_API_KEY,
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
sandbox.commands.run("pip install uv",
|
| 384 |
+
timeout=60 * 3,
|
| 385 |
+
on_stderr=lambda message: print(message),)
|
| 386 |
+
|
| 387 |
+
stderrs = []
|
| 388 |
+
|
| 389 |
+
python_dependencies, npm_dependencies = code_dependencies
|
| 390 |
+
pip_install_errs = install_pip_dependencies(sandbox, python_dependencies)
|
| 391 |
+
npm_install_errs = install_npm_dependencies(sandbox, npm_dependencies)
|
| 392 |
+
|
| 393 |
+
stderrs.extend(pip_install_errs)
|
| 394 |
+
stderrs.extend(npm_install_errs)
|
| 395 |
+
|
| 396 |
+
execution = sandbox.run_code(
|
| 397 |
+
code=code,
|
| 398 |
+
language=code_language
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
# collect stdout, stderr from sandbox
|
| 402 |
+
stdout = "\n".join(execution.logs.stdout)
|
| 403 |
+
stderr = "\n".join(execution.logs.stderr)
|
| 404 |
+
if execution.error:
|
| 405 |
+
stderr += f"\n{execution.error.name}: {execution.error.value}"
|
| 406 |
+
output = ""
|
| 407 |
+
if stdout:
|
| 408 |
+
output += f"### Stdout:\n```markdown\n{stdout}\n```\n\n"
|
| 409 |
+
|
| 410 |
+
stderrs.append(stderr)
|
| 411 |
+
|
| 412 |
+
results = []
|
| 413 |
+
for result in execution.results:
|
| 414 |
+
if result.html or result.javascript:
|
| 415 |
+
# TODO: fix this
|
| 416 |
+
continue
|
| 417 |
+
# with open('html_code.html', 'w') as f:
|
| 418 |
+
# f.write(result.html)
|
| 419 |
+
# url, _ = run_html_sandbox(result.html, ([], extract_js_imports(result.html)))
|
| 420 |
+
else:
|
| 421 |
+
rendered_result = render_result(result)
|
| 422 |
+
results.append(rendered_result)
|
| 423 |
+
if results:
|
| 424 |
+
output += "\n### Results:\n" + "\n".join(results)
|
| 425 |
+
|
| 426 |
+
stderrs = '\n'.join(stderrs)
|
| 427 |
+
return output, "" if output else stderrs
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
def run_html_sandbox(code: str, code_dependencies: tuple[list[str], list[str]], existing_sandbox_id: str | None = None) -> tuple[str, str, str]:
|
| 431 |
+
"""
|
| 432 |
+
Executes the provided code within a sandboxed environment and returns the output.
|
| 433 |
+
Supports both React and Vue.js rendering in HTML files.
|
| 434 |
+
|
| 435 |
+
Args:
|
| 436 |
+
code (str): The code to be executed.
|
| 437 |
+
code_dependencies: Tuple of (python_deps, npm_deps)
|
| 438 |
+
|
| 439 |
+
Returns:
|
| 440 |
+
tuple: (sandbox_url, sandbox_id, stderr)
|
| 441 |
+
"""
|
| 442 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 443 |
+
project_root = "~/html_app"
|
| 444 |
+
sandbox.files.make_dir(project_root)
|
| 445 |
+
|
| 446 |
+
# HTML does not support dependencies for now
|
| 447 |
+
# _, npm_dependencies = code_dependencies
|
| 448 |
+
# install_npm_dependencies(sandbox, npm_dependencies, project_root=project_root)
|
| 449 |
+
|
| 450 |
+
# replace placeholder URLs with SVG data URLs
|
| 451 |
+
code = replace_placeholder_urls(code)
|
| 452 |
+
|
| 453 |
+
file_path = f"{project_root}/index.html"
|
| 454 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 455 |
+
|
| 456 |
+
sandbox_url = get_sandbox_app_url(sandbox, 'html')
|
| 457 |
+
return (sandbox_url, sandbox.sandbox_id, '')
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
def run_react_sandbox(code: str, code_dependencies: tuple[list[str], list[str]], existing_sandbox_id: str | None = None) -> CodeRunResult:
|
| 461 |
+
"""
|
| 462 |
+
Executes the provided code within a sandboxed environment and returns the output.
|
| 463 |
+
|
| 464 |
+
Args:
|
| 465 |
+
code (str): The code to be executed.
|
| 466 |
+
|
| 467 |
+
Returns:
|
| 468 |
+
url for remote sandbox
|
| 469 |
+
"""
|
| 470 |
+
project_root = "~/react_app"
|
| 471 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 472 |
+
|
| 473 |
+
stderrs: list[str] = [] # to collect errors
|
| 474 |
+
|
| 475 |
+
_, npm_dependencies = code_dependencies
|
| 476 |
+
if npm_dependencies:
|
| 477 |
+
print(f"Installing NPM dependencies...: {npm_dependencies}")
|
| 478 |
+
install_errs = install_npm_dependencies(sandbox, npm_dependencies, project_root=project_root)
|
| 479 |
+
stderrs.extend(install_errs)
|
| 480 |
+
print("NPM dependencies installed. " + "Errors: " + str(install_errs))
|
| 481 |
+
|
| 482 |
+
# replace placeholder URLs with SVG data URLs
|
| 483 |
+
code = replace_placeholder_urls(code)
|
| 484 |
+
|
| 485 |
+
# set up the sandbox
|
| 486 |
+
print("Setting up sandbox directory structure...")
|
| 487 |
+
file_path = "~/react_app/src/App.tsx"
|
| 488 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 489 |
+
print("Code files written successfully.")
|
| 490 |
+
|
| 491 |
+
is_run_success, _, build_stderrs = run_command_in_sandbox(
|
| 492 |
+
sandbox=sandbox,
|
| 493 |
+
command="npm run build --loglevel=error -- --mode development --logLevel error",
|
| 494 |
+
working_directory=project_root,
|
| 495 |
+
)
|
| 496 |
+
stderrs.extend(build_stderrs)
|
| 497 |
+
|
| 498 |
+
sandbox_url = get_sandbox_app_url(sandbox, 'react')
|
| 499 |
+
return {
|
| 500 |
+
'sandbox_id': sandbox.sandbox_id,
|
| 501 |
+
'sandbox_url': sandbox_url,
|
| 502 |
+
'is_run_success': is_run_success,
|
| 503 |
+
'stderr': '\n'.join(stderrs),
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
def run_vue_sandbox(code: str, code_dependencies: tuple[list[str], list[str]], existing_sandbox_id: str | None = None) -> CodeRunResult:
|
| 508 |
+
"""
|
| 509 |
+
Executes the provided Vue code within a sandboxed environment and returns the output.
|
| 510 |
+
|
| 511 |
+
Args:
|
| 512 |
+
code (str): The Vue code to be executed.
|
| 513 |
+
|
| 514 |
+
Returns:
|
| 515 |
+
url for remote sandbox
|
| 516 |
+
"""
|
| 517 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 518 |
+
project_root = "~/vue_app"
|
| 519 |
+
|
| 520 |
+
stderrs: list[str] = [] # to collect errors
|
| 521 |
+
|
| 522 |
+
# replace placeholder URLs with SVG data URLs
|
| 523 |
+
code = replace_placeholder_urls(code)
|
| 524 |
+
|
| 525 |
+
# Set up the sandbox
|
| 526 |
+
file_path = "~/vue_app/src/App.vue"
|
| 527 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 528 |
+
|
| 529 |
+
_, npm_dependencies = code_dependencies
|
| 530 |
+
if npm_dependencies:
|
| 531 |
+
print(f"Installing NPM dependencies...: {npm_dependencies}")
|
| 532 |
+
install_errs = install_npm_dependencies(sandbox, npm_dependencies, project_root=project_root)
|
| 533 |
+
stderrs.extend(install_errs)
|
| 534 |
+
print("NPM dependencies installed. " + "Errors: " + str(install_errs))
|
| 535 |
+
|
| 536 |
+
is_run_success, _, build_stderrs = run_command_in_sandbox(
|
| 537 |
+
sandbox=sandbox,
|
| 538 |
+
command="npm run build --loglevel=error -- --mode development --logLevel error",
|
| 539 |
+
working_directory=project_root,
|
| 540 |
+
)
|
| 541 |
+
stderrs.extend(build_stderrs)
|
| 542 |
+
|
| 543 |
+
sandbox_url = get_sandbox_app_url(sandbox, 'vue')
|
| 544 |
+
return {
|
| 545 |
+
'sandbox_id': sandbox.sandbox_id,
|
| 546 |
+
'sandbox_url': sandbox_url,
|
| 547 |
+
'is_run_success': is_run_success,
|
| 548 |
+
'stderr': '\n'.join(stderrs),
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
|
| 552 |
+
def run_pygame_sandbox(code: str, code_dependencies: tuple[list[str], list[str]], existing_sandbox_id: str | None = None) -> CodeRunResult:
|
| 553 |
+
"""
|
| 554 |
+
Executes the provided code within a sandboxed environment and returns the output.
|
| 555 |
+
|
| 556 |
+
Args:
|
| 557 |
+
code (str): The code to be executed.
|
| 558 |
+
|
| 559 |
+
Returns:
|
| 560 |
+
url for remote sandbox
|
| 561 |
+
"""
|
| 562 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 563 |
+
project_root = "~/pygame_app"
|
| 564 |
+
file_path = f"{project_root}/main.py"
|
| 565 |
+
|
| 566 |
+
stderrs = []
|
| 567 |
+
|
| 568 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 569 |
+
|
| 570 |
+
python_dependencies, _ = code_dependencies
|
| 571 |
+
install_errs = install_pip_dependencies(sandbox, python_dependencies)
|
| 572 |
+
stderrs.extend(install_errs)
|
| 573 |
+
|
| 574 |
+
# build the pygame code
|
| 575 |
+
is_run_success, _, build_stderrs = run_command_in_sandbox(
|
| 576 |
+
sandbox=sandbox,
|
| 577 |
+
command="pygbag --build ~/pygame_app",
|
| 578 |
+
)
|
| 579 |
+
stderrs.extend(build_stderrs)
|
| 580 |
+
|
| 581 |
+
sandbox_url = get_sandbox_app_url(sandbox, 'pygame')
|
| 582 |
+
return {
|
| 583 |
+
'sandbox_id': sandbox.sandbox_id,
|
| 584 |
+
'sandbox_url': sandbox_url,
|
| 585 |
+
'is_run_success': is_run_success,
|
| 586 |
+
'stderr': '\n'.join(stderrs),
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
|
| 590 |
+
def run_gradio_sandbox(code: str, code_dependencies: tuple[list[str], list[str]], existing_sandbox_id: str | None = None) -> tuple[str, str, str]:
|
| 591 |
+
"""
|
| 592 |
+
Executes the provided code within a sandboxed environment and returns the output.
|
| 593 |
+
|
| 594 |
+
Args:
|
| 595 |
+
code (str): The code to be executed.
|
| 596 |
+
|
| 597 |
+
Returns:
|
| 598 |
+
url for remote sandbox and sandbox id
|
| 599 |
+
"""
|
| 600 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 601 |
+
|
| 602 |
+
file_path = "~/gradio_app/main.py"
|
| 603 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 604 |
+
|
| 605 |
+
stderrs = []
|
| 606 |
+
|
| 607 |
+
python_dependencies, _ = code_dependencies
|
| 608 |
+
install_stderr = install_pip_dependencies(sandbox, python_dependencies)
|
| 609 |
+
stderrs.extend(install_stderr)
|
| 610 |
+
|
| 611 |
+
stderr = run_background_command_with_timeout(
|
| 612 |
+
sandbox,
|
| 613 |
+
f"python {file_path}",
|
| 614 |
+
timeout=10,
|
| 615 |
+
)
|
| 616 |
+
stderrs.append(stderr)
|
| 617 |
+
|
| 618 |
+
sandbox_url = 'https://' + sandbox.get_host(7860)
|
| 619 |
+
|
| 620 |
+
return (sandbox_url, sandbox.sandbox_id, '\n'.join(stderrs))
|
| 621 |
+
|
| 622 |
+
|
| 623 |
+
def run_streamlit_sandbox(code: str, code_dependencies: tuple[list[str], list[str]], existing_sandbox_id: str | None = None) -> tuple[str, str, str]:
|
| 624 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 625 |
+
|
| 626 |
+
stderrs = []
|
| 627 |
+
|
| 628 |
+
sandbox.files.make_dir('mystreamlit')
|
| 629 |
+
file_path = "~/mystreamlit/app.py"
|
| 630 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 631 |
+
|
| 632 |
+
python_dependencies, _ = code_dependencies
|
| 633 |
+
install_stderr = install_pip_dependencies(sandbox, python_dependencies)
|
| 634 |
+
stderrs.extend(install_stderr)
|
| 635 |
+
|
| 636 |
+
stderr = run_background_command_with_timeout(
|
| 637 |
+
sandbox,
|
| 638 |
+
r"sudo kill -9 $(ss -lptn 'sport = :8501' | grep -oP '(?<=pid=)\d+'); streamlit run ~/mystreamlit/app.py --server.port 8501 --server.headless true",
|
| 639 |
+
timeout=8,
|
| 640 |
+
)
|
| 641 |
+
stderrs.append(stderr)
|
| 642 |
+
|
| 643 |
+
host = sandbox.get_host(port=8501)
|
| 644 |
+
url = f"https://{host}"
|
| 645 |
+
return (url, sandbox.sandbox_id, '\n'.join(stderrs))
|
| 646 |
+
|
| 647 |
+
|
| 648 |
+
def run_c_code(code: str, existing_sandbox_id: str | None = None) -> tuple[str, str]:
|
| 649 |
+
"""
|
| 650 |
+
Executes the provided C code within a sandboxed environment and returns the output.
|
| 651 |
+
|
| 652 |
+
Args:
|
| 653 |
+
code (str): The C code to be executed.
|
| 654 |
+
|
| 655 |
+
Returns:
|
| 656 |
+
tuple: (stdout, stderr)
|
| 657 |
+
"""
|
| 658 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 659 |
+
|
| 660 |
+
file_path = "~/main.c"
|
| 661 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 662 |
+
|
| 663 |
+
is_success, stdouts, stderrs = run_command_in_sandbox(
|
| 664 |
+
sandbox=sandbox,
|
| 665 |
+
command=f"gcc {file_path} -o ~/main && ./main",
|
| 666 |
+
timeout=CODE_RUN_TIMEOUT_SECONDS,
|
| 667 |
+
)
|
| 668 |
+
|
| 669 |
+
# collect stdout, stderr from sandbox
|
| 670 |
+
stdout = "\n".join(stdouts)
|
| 671 |
+
stderr = "\n".join(stderrs)
|
| 672 |
+
return stdout, stderr
|
| 673 |
+
|
| 674 |
+
|
| 675 |
+
def run_cpp_code(code: str, existing_sandbox_id: str | None = None) -> tuple[str, str]:
|
| 676 |
+
"""
|
| 677 |
+
Executes the provided C++ code within a sandboxed environment and returns the output.
|
| 678 |
+
|
| 679 |
+
Args:
|
| 680 |
+
code (str): The C++ code to be executed.
|
| 681 |
+
|
| 682 |
+
Returns:
|
| 683 |
+
tuple: (stdout, stderr)
|
| 684 |
+
"""
|
| 685 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 686 |
+
|
| 687 |
+
file_path = "~/main.cpp"
|
| 688 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 689 |
+
|
| 690 |
+
is_success, stdouts, stderrs = run_command_in_sandbox(
|
| 691 |
+
sandbox=sandbox,
|
| 692 |
+
command=f"g++ {file_path} -o ~/main && ./main",
|
| 693 |
+
timeout=CODE_RUN_TIMEOUT_SECONDS,
|
| 694 |
+
)
|
| 695 |
+
|
| 696 |
+
# collect stdout, stderr from sandbox
|
| 697 |
+
stdout = "\n".join(stdouts)
|
| 698 |
+
stderr = "\n".join(stderrs)
|
| 699 |
+
return stdout, stderr
|
| 700 |
+
|
| 701 |
+
|
| 702 |
+
def run_java_code(code: str, existing_sandbox_id: str | None = None) -> tuple[str, str]:
|
| 703 |
+
"""
|
| 704 |
+
Executes the provided Java code within a sandboxed environment and returns the output.
|
| 705 |
+
|
| 706 |
+
Args:
|
| 707 |
+
code (str): The Java code to be executed.
|
| 708 |
+
|
| 709 |
+
Returns:
|
| 710 |
+
tuple: (stdout, stderr)
|
| 711 |
+
"""
|
| 712 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 713 |
+
|
| 714 |
+
class_name = extract_java_class_name(code)
|
| 715 |
+
file_path = f"~/{class_name}.java"
|
| 716 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 717 |
+
|
| 718 |
+
is_success, stdouts, stderrs = run_command_in_sandbox(
|
| 719 |
+
sandbox=sandbox,
|
| 720 |
+
command=f"javac {file_path} && java {class_name}",
|
| 721 |
+
timeout=CODE_RUN_TIMEOUT_SECONDS,
|
| 722 |
+
)
|
| 723 |
+
|
| 724 |
+
# collect stdout, stderr from sandbox
|
| 725 |
+
stdout = "\n".join(stdouts)
|
| 726 |
+
stderr = "\n".join(stderrs)
|
| 727 |
+
return stdout, stderr
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
def run_golang_code(code: str, existing_sandbox_id: str | None = None) -> tuple[str, str]:
|
| 731 |
+
"""
|
| 732 |
+
Executes the provided Go code within a sandboxed environment and returns the output.
|
| 733 |
+
|
| 734 |
+
Args:
|
| 735 |
+
code (str): The Go code to be executed
|
| 736 |
+
|
| 737 |
+
Returns:
|
| 738 |
+
tuple: (stdout, stderr)
|
| 739 |
+
"""
|
| 740 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 741 |
+
|
| 742 |
+
file_path = "~/main.go"
|
| 743 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 744 |
+
|
| 745 |
+
is_success, stdouts, stderrs = run_command_in_sandbox(
|
| 746 |
+
sandbox=sandbox,
|
| 747 |
+
command=f"go run {file_path}",
|
| 748 |
+
timeout=CODE_RUN_TIMEOUT_SECONDS,
|
| 749 |
+
)
|
| 750 |
+
|
| 751 |
+
# collect stdout, stderr from sandbox
|
| 752 |
+
stdout = "\n".join(stdouts)
|
| 753 |
+
stderr = "\n".join(stderrs)
|
| 754 |
+
return stdout, stderr
|
| 755 |
+
|
| 756 |
+
|
| 757 |
+
# def run_csharp_code(code: str, existing_sandbox_id: str | None = None) -> tuple[str, str]:
|
| 758 |
+
# """
|
| 759 |
+
# Executes the provided C# code within a sandboxed environment and returns the output.
|
| 760 |
+
|
| 761 |
+
# Args:
|
| 762 |
+
# code (str): The C# code to be executed
|
| 763 |
+
|
| 764 |
+
# Returns:
|
| 765 |
+
# tuple: (stdout, stderr)
|
| 766 |
+
# """
|
| 767 |
+
# sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 768 |
+
|
| 769 |
+
# file_path = "~/main.cs"
|
| 770 |
+
# sandbox.files.write(file_path, code, "user", 60)
|
| 771 |
+
|
| 772 |
+
# is_success, stdouts, stderrs = run_command_in_sandbox(
|
| 773 |
+
# sandbox=sandbox,
|
| 774 |
+
# command=f"mcs {file_path} && mono main.exe",
|
| 775 |
+
# timeout=CODE_RUN_TIMEOUT_SECONDS,
|
| 776 |
+
# )
|
| 777 |
+
|
| 778 |
+
# # collect stdout, stderr from sandbox
|
| 779 |
+
# stdout = "\n".join(stdouts)
|
| 780 |
+
# stderr = "\n".join(stderrs)
|
| 781 |
+
# return stdout, stderr
|
| 782 |
+
|
| 783 |
+
|
| 784 |
+
def run_rust_code(code: str, existing_sandbox_id: str | None = None) -> tuple[str, str]:
|
| 785 |
+
"""
|
| 786 |
+
Executes the provided Rust code within a sandboxed environment and returns the output.
|
| 787 |
+
|
| 788 |
+
Args:
|
| 789 |
+
code (str): The Rust code to be executed
|
| 790 |
+
|
| 791 |
+
Returns:
|
| 792 |
+
tuple: (stdout, stderr)
|
| 793 |
+
"""
|
| 794 |
+
sandbox = reuse_or_create_sandbox(sandbox_id=existing_sandbox_id)
|
| 795 |
+
|
| 796 |
+
file_path = "~/main.rs"
|
| 797 |
+
sandbox.files.write(file_path, code, "user", 60)
|
| 798 |
+
|
| 799 |
+
is_success, stdouts, stderrs = run_command_in_sandbox(
|
| 800 |
+
sandbox=sandbox,
|
| 801 |
+
command=f"rustc {file_path} && ./main",
|
| 802 |
+
timeout=CODE_RUN_TIMEOUT_SECONDS,
|
| 803 |
+
)
|
| 804 |
+
|
| 805 |
+
# collect stdout, stderr from sandbox
|
| 806 |
+
stdout = "\n".join(stdouts)
|
| 807 |
+
stderr = "\n".join(stderrs)
|
| 808 |
+
return stdout, stderr
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
def on_edit_code(
|
| 812 |
+
state,
|
| 813 |
+
sandbox_state: ChatbotSandboxState,
|
| 814 |
+
sandbox_output_md: gr.Markdown,
|
| 815 |
+
sandbox_ui: SandboxComponent,
|
| 816 |
+
sandbox_code: str,
|
| 817 |
+
sandbox_dependency: gr.Dataframe,
|
| 818 |
+
) -> Generator[tuple[Any, Any, Any, Any], None, None]:
|
| 819 |
+
'''
|
| 820 |
+
Gradio Handler when code is edited manually by users.
|
| 821 |
+
'''
|
| 822 |
+
if sandbox_state['enable_sandbox'] is False:
|
| 823 |
+
yield None, None, None, None
|
| 824 |
+
return
|
| 825 |
+
if len(sandbox_code.strip()) == 0 or sandbox_code == sandbox_state['code_to_execute']:
|
| 826 |
+
yield gr.skip(), gr.skip(), gr.skip(), gr.skip()
|
| 827 |
+
return
|
| 828 |
+
sandbox_state['code_to_execute'] = sandbox_code
|
| 829 |
+
|
| 830 |
+
# Extract packages from imports (without versions)
|
| 831 |
+
python_deps_from_imports = set(extract_python_imports(sandbox_code))
|
| 832 |
+
npm_deps_from_imports = set(extract_js_imports(sandbox_code))
|
| 833 |
+
|
| 834 |
+
# Get existing dependencies with versions from state
|
| 835 |
+
existing_python_deps, existing_npm_deps = sandbox_state["code_dependencies"]
|
| 836 |
+
|
| 837 |
+
# Create dictionaries to track package versions
|
| 838 |
+
python_deps_dict = {} # pkg_name -> version
|
| 839 |
+
npm_deps_dict = {} # pkg_name -> version
|
| 840 |
+
|
| 841 |
+
# First add existing dependencies with their specific versions
|
| 842 |
+
for dep in existing_python_deps:
|
| 843 |
+
pkg_name = dep.split('==')[0].split('>=')[0].split('<=')[0].split('~=')[0]
|
| 844 |
+
version = dep[len(pkg_name):]
|
| 845 |
+
if version: # If it has a specific version
|
| 846 |
+
python_deps_dict[pkg_name] = version
|
| 847 |
+
elif pkg_name in python_deps_from_imports: # Only keep packages that are still imported
|
| 848 |
+
python_deps_dict[pkg_name] = "latest"
|
| 849 |
+
|
| 850 |
+
for dep in existing_npm_deps:
|
| 851 |
+
if '@' in dep and not dep.startswith('@'):
|
| 852 |
+
pkg_name = dep.split('@')[0]
|
| 853 |
+
version = '@' + dep.split('@')[1]
|
| 854 |
+
elif '@' in dep[1:]: # Handle scoped packages
|
| 855 |
+
pkg_name, version = dep.rsplit('@', 1)
|
| 856 |
+
version = '@' + version
|
| 857 |
+
else:
|
| 858 |
+
pkg_name = dep
|
| 859 |
+
version = "latest"
|
| 860 |
+
if version != "latest": # If it has a specific version
|
| 861 |
+
npm_deps_dict[pkg_name] = version
|
| 862 |
+
elif pkg_name in npm_deps_from_imports: # Only keep packages that are still imported
|
| 863 |
+
npm_deps_dict[pkg_name] = "latest"
|
| 864 |
+
|
| 865 |
+
# Add new dependencies from imports with "latest" if not already present
|
| 866 |
+
for dep in python_deps_from_imports:
|
| 867 |
+
if dep not in python_deps_dict:
|
| 868 |
+
python_deps_dict[dep] = "latest"
|
| 869 |
+
|
| 870 |
+
for dep in npm_deps_from_imports:
|
| 871 |
+
if dep not in npm_deps_dict:
|
| 872 |
+
npm_deps_dict[dep] = "latest"
|
| 873 |
+
|
| 874 |
+
# Convert to dataframe format
|
| 875 |
+
dependencies = []
|
| 876 |
+
|
| 877 |
+
# Add Python packages
|
| 878 |
+
for pkg_name, version in python_deps_dict.items():
|
| 879 |
+
dependencies.append(["python", pkg_name, version])
|
| 880 |
+
|
| 881 |
+
# Add NPM packages
|
| 882 |
+
for pkg_name, version in npm_deps_dict.items():
|
| 883 |
+
dependencies.append(["npm", pkg_name, version])
|
| 884 |
+
|
| 885 |
+
# If no dependencies found, provide default empty rows
|
| 886 |
+
if not dependencies:
|
| 887 |
+
dependencies = [["python", "", ""], ["npm", "", ""]]
|
| 888 |
+
|
| 889 |
+
# Update dependencies in sandbox state
|
| 890 |
+
sandbox_state["code_dependencies"] = (
|
| 891 |
+
[f"{pkg}{ver}" if ver != "latest" else pkg for pkg, ver in python_deps_dict.items()],
|
| 892 |
+
[f"{pkg}{ver}" if ver != "latest" else pkg for pkg, ver in npm_deps_dict.items()]
|
| 893 |
+
)
|
| 894 |
+
|
| 895 |
+
yield (
|
| 896 |
+
gr.skip(), # sandbox_output_md
|
| 897 |
+
gr.skip(), # sandbox_ui
|
| 898 |
+
gr.skip(), # sandbox_code
|
| 899 |
+
gr.update(value=dependencies), # sandbox_dependency
|
| 900 |
+
)
|
| 901 |
+
yield from on_run_code(
|
| 902 |
+
state,
|
| 903 |
+
sandbox_state,
|
| 904 |
+
sandbox_output_md,
|
| 905 |
+
sandbox_ui,
|
| 906 |
+
sandbox_code,
|
| 907 |
+
sandbox_dependency,
|
| 908 |
+
)
|
| 909 |
+
|
| 910 |
+
|
| 911 |
+
def on_edit_dependency(
|
| 912 |
+
state,
|
| 913 |
+
sandbox_state: ChatbotSandboxState,
|
| 914 |
+
sandbox_dependency: gr.Dataframe,
|
| 915 |
+
sandbox_output_md: gr.Markdown,
|
| 916 |
+
sandbox_ui: SandboxComponent,
|
| 917 |
+
sandbox_code: str,
|
| 918 |
+
) -> Generator[tuple[Any, Any, Any, Any], None, None]:
|
| 919 |
+
"""
|
| 920 |
+
Gradio Handler when dependencies are edited manually by users.
|
| 921 |
+
Handles version specifications and dependency removal.
|
| 922 |
+
"""
|
| 923 |
+
if sandbox_state["enable_sandbox"] is False:
|
| 924 |
+
yield None, None, None, None
|
| 925 |
+
return
|
| 926 |
+
|
| 927 |
+
# Validate dependencies format
|
| 928 |
+
is_valid, error_msg = validate_dependencies(sandbox_dependency)
|
| 929 |
+
if not is_valid:
|
| 930 |
+
yield (
|
| 931 |
+
gr.Markdown(f"Invalid dependencies: {error_msg}"),
|
| 932 |
+
gr.skip(),
|
| 933 |
+
gr.skip(),
|
| 934 |
+
sandbox_dependency, # Return original dataframe
|
| 935 |
+
)
|
| 936 |
+
return
|
| 937 |
+
|
| 938 |
+
# Convert dataframe format to separate python and npm lists
|
| 939 |
+
python_deps = []
|
| 940 |
+
npm_deps = []
|
| 941 |
+
for dep in sandbox_dependency:
|
| 942 |
+
dep_type, pkg_name, version = dep
|
| 943 |
+
pkg_name = pkg_name.strip()
|
| 944 |
+
version = version.strip()
|
| 945 |
+
|
| 946 |
+
# Skip empty rows
|
| 947 |
+
if not pkg_name:
|
| 948 |
+
continue
|
| 949 |
+
|
| 950 |
+
if dep_type.lower() == "python":
|
| 951 |
+
# Handle Python package with version
|
| 952 |
+
if version and version.lower() != "latest":
|
| 953 |
+
if not any(op in version for op in ["==", ">=", "<=", "~=", ">", "<"]):
|
| 954 |
+
python_deps.append(f"{pkg_name}=={version}")
|
| 955 |
+
else:
|
| 956 |
+
python_deps.append(f"{pkg_name}{version}")
|
| 957 |
+
else:
|
| 958 |
+
python_deps.append(pkg_name)
|
| 959 |
+
|
| 960 |
+
elif dep_type.lower() == "npm":
|
| 961 |
+
# Handle NPM package with version
|
| 962 |
+
if version and version.lower() != "latest":
|
| 963 |
+
if not version.startswith("@"):
|
| 964 |
+
version = "@" + version
|
| 965 |
+
npm_deps.append(f"{pkg_name}{version}")
|
| 966 |
+
else:
|
| 967 |
+
npm_deps.append(pkg_name)
|
| 968 |
+
|
| 969 |
+
# Update sandbox state with new dependencies
|
| 970 |
+
sandbox_state["code_dependencies"] = (python_deps, npm_deps)
|
| 971 |
+
|
| 972 |
+
# increase edit round
|
| 973 |
+
sandbox_state['edit_round'] += 1
|
| 974 |
+
|
| 975 |
+
# First yield: Update UI with success message
|
| 976 |
+
yield (
|
| 977 |
+
gr.Markdown("Dependencies updated successfully"),
|
| 978 |
+
gr.skip(), # sandbox_ui
|
| 979 |
+
gr.skip(), # sandbox_code
|
| 980 |
+
sandbox_dependency, # Return the same dataframe
|
| 981 |
+
)
|
| 982 |
+
|
| 983 |
+
# Second yield: Run code with new dependencies
|
| 984 |
+
yield from on_run_code(
|
| 985 |
+
state,
|
| 986 |
+
sandbox_state,
|
| 987 |
+
sandbox_output_md,
|
| 988 |
+
sandbox_ui,
|
| 989 |
+
sandbox_code,
|
| 990 |
+
sandbox_dependency,
|
| 991 |
+
)
|
| 992 |
+
|
| 993 |
+
|
| 994 |
+
def on_click_code_message_run(
|
| 995 |
+
state,
|
| 996 |
+
sandbox_state: ChatbotSandboxState,
|
| 997 |
+
sandbox_output_md: gr.Markdown,
|
| 998 |
+
sandbox_ui: SandboxComponent,
|
| 999 |
+
sandbox_code: str,
|
| 1000 |
+
sandbox_dependency: gr.Dataframe,
|
| 1001 |
+
evt: gr.SelectData
|
| 1002 |
+
) -> Generator[SandboxGradioSandboxComponents, None, None]:
|
| 1003 |
+
'''
|
| 1004 |
+
Gradio Handler when run code button in message is clicked. Update Sandbox components.
|
| 1005 |
+
'''
|
| 1006 |
+
print("on_click_code_message_run")
|
| 1007 |
+
|
| 1008 |
+
if sandbox_state['enable_sandbox'] is False:
|
| 1009 |
+
yield None, None, None, None
|
| 1010 |
+
return
|
| 1011 |
+
if not evt.value.endswith(RUN_CODE_BUTTON_HTML):
|
| 1012 |
+
yield gr.skip(), gr.skip(), gr.skip(), gr.skip()
|
| 1013 |
+
return
|
| 1014 |
+
|
| 1015 |
+
message = evt.value.replace(RUN_CODE_BUTTON_HTML, "").strip()
|
| 1016 |
+
extract_result = extract_code_from_markdown(
|
| 1017 |
+
message=message,
|
| 1018 |
+
enable_auto_env=sandbox_state['sandbox_environment'] == SandboxEnvironment.AUTO
|
| 1019 |
+
)
|
| 1020 |
+
if extract_result is None:
|
| 1021 |
+
yield gr.skip(), gr.skip(), gr.skip(), gr.skip()
|
| 1022 |
+
return
|
| 1023 |
+
|
| 1024 |
+
code, code_language, code_dependencies, env_selection = extract_result
|
| 1025 |
+
|
| 1026 |
+
# As sandbox is reused, no need to skip
|
| 1027 |
+
# if sandbox_state['code_to_execute'] == code and sandbox_state['code_language'] == code_language:
|
| 1028 |
+
# # skip if no changes
|
| 1029 |
+
# yield gr.skip(), gr.skip(), gr.skip(), gr.skip()
|
| 1030 |
+
# return
|
| 1031 |
+
|
| 1032 |
+
if code_language == 'tsx':
|
| 1033 |
+
code_language = 'typescript'
|
| 1034 |
+
code_language = code_language.lower()
|
| 1035 |
+
gradio_code_language = code_language.lower() if code_language and code_language.lower(
|
| 1036 |
+
# ensure gradio supports the code language
|
| 1037 |
+
) in VALID_GRADIO_CODE_LANGUAGES else None
|
| 1038 |
+
|
| 1039 |
+
python_deps, npm_deps = code_dependencies
|
| 1040 |
+
|
| 1041 |
+
# Convert to dataframe format
|
| 1042 |
+
dependencies = []
|
| 1043 |
+
|
| 1044 |
+
# Add Python packages with versions
|
| 1045 |
+
for dep in python_deps:
|
| 1046 |
+
# Check if package has version specifier
|
| 1047 |
+
if any(op in dep for op in ['==', '>=', '<=', '~=']):
|
| 1048 |
+
# Split on first occurrence of version operator
|
| 1049 |
+
pkg_name = dep.split('==')[0].split('>=')[0].split('<=')[0].split('~=')[0]
|
| 1050 |
+
version = dep[len(pkg_name):] # Get everything after package name
|
| 1051 |
+
dependencies.append(["python", pkg_name, version])
|
| 1052 |
+
else:
|
| 1053 |
+
dependencies.append(["python", dep, "latest"])
|
| 1054 |
+
|
| 1055 |
+
# Add NPM packages with versions
|
| 1056 |
+
for dep in npm_deps:
|
| 1057 |
+
# Check if package has version specifier
|
| 1058 |
+
if '@' in dep and not dep.startswith('@'):
|
| 1059 |
+
# Handle non-scoped packages with version
|
| 1060 |
+
pkg_name, version = dep.split('@', 1)
|
| 1061 |
+
dependencies.append(["npm", pkg_name, '@' + version])
|
| 1062 |
+
elif '@' in dep[1:]: # Handle scoped packages with version
|
| 1063 |
+
# Split on last @ for scoped packages
|
| 1064 |
+
pkg_parts = dep.rsplit('@', 1)
|
| 1065 |
+
dependencies.append(["npm", pkg_parts[0], '@' + pkg_parts[1]])
|
| 1066 |
+
else:
|
| 1067 |
+
dependencies.append(["npm", dep, "latest"])
|
| 1068 |
+
|
| 1069 |
+
# If no dependencies found, provide default empty rows
|
| 1070 |
+
if not dependencies:
|
| 1071 |
+
dependencies = [["python", "", ""], ["npm", "", ""]]
|
| 1072 |
+
|
| 1073 |
+
sandbox_state['code_to_execute'] = code
|
| 1074 |
+
sandbox_state['code_language'] = code_language
|
| 1075 |
+
sandbox_state["code_dependencies"] = code_dependencies
|
| 1076 |
+
if sandbox_state['sandbox_environment'] == SandboxEnvironment.AUTO:
|
| 1077 |
+
sandbox_state['auto_selected_sandbox_environment'] = env_selection
|
| 1078 |
+
|
| 1079 |
+
# reset edit round
|
| 1080 |
+
sandbox_state['edit_round'] = 0
|
| 1081 |
+
|
| 1082 |
+
yield (
|
| 1083 |
+
gr.skip(), # sandbox_output_md
|
| 1084 |
+
gr.skip(), # sandbox_ui
|
| 1085 |
+
gr.update(value=code, language=gradio_code_language), # sandbox_code
|
| 1086 |
+
gr.update(value=dependencies) # sandbox_dependency
|
| 1087 |
+
)
|
| 1088 |
+
|
| 1089 |
+
yield from on_run_code(
|
| 1090 |
+
state,
|
| 1091 |
+
sandbox_state,
|
| 1092 |
+
sandbox_output_md,
|
| 1093 |
+
sandbox_ui,
|
| 1094 |
+
sandbox_code,
|
| 1095 |
+
sandbox_dependency,
|
| 1096 |
+
)
|
| 1097 |
+
|
| 1098 |
+
|
| 1099 |
+
def on_run_code(
|
| 1100 |
+
state,
|
| 1101 |
+
sandbox_state: ChatbotSandboxState,
|
| 1102 |
+
sandbox_output_md: gr.Markdown,
|
| 1103 |
+
sandbox_ui: SandboxComponent,
|
| 1104 |
+
sandbox_code: str,
|
| 1105 |
+
sandbox_dependency: gr.Dataframe,
|
| 1106 |
+
) -> Generator[tuple[Any, Any, Any, Any], None, None]:
|
| 1107 |
+
'''
|
| 1108 |
+
gradio fn when run code button is clicked. Update Sandbox components.
|
| 1109 |
+
'''
|
| 1110 |
+
print("on_run_code")
|
| 1111 |
+
|
| 1112 |
+
if sandbox_state['enable_sandbox'] is False:
|
| 1113 |
+
yield None, None, None, None
|
| 1114 |
+
return
|
| 1115 |
+
|
| 1116 |
+
# validate e2b api key
|
| 1117 |
+
if not E2B_API_KEY:
|
| 1118 |
+
raise ValueError("E2B_API_KEY is not set in env vars.")
|
| 1119 |
+
|
| 1120 |
+
# hide and change value of the current sandbox UI to force refresh the sandbox
|
| 1121 |
+
# otherwise the sandbox might not change if the url is same
|
| 1122 |
+
yield (
|
| 1123 |
+
gr.skip(),
|
| 1124 |
+
SandboxComponent(
|
| 1125 |
+
value=('', False, []),
|
| 1126 |
+
label="Example",
|
| 1127 |
+
visible=False,
|
| 1128 |
+
),
|
| 1129 |
+
gr.skip(),
|
| 1130 |
+
gr.skip(),
|
| 1131 |
+
)
|
| 1132 |
+
|
| 1133 |
+
code, code_language = sandbox_state['code_to_execute'], sandbox_state['code_language']
|
| 1134 |
+
if code is None or code_language is None:
|
| 1135 |
+
yield None, None, None, None
|
| 1136 |
+
return
|
| 1137 |
+
|
| 1138 |
+
gradio_code_language = code_language.lower() if code_language and code_language.lower(
|
| 1139 |
+
# ensure gradio supports the code language
|
| 1140 |
+
) in VALID_GRADIO_CODE_LANGUAGES else None
|
| 1141 |
+
|
| 1142 |
+
# Use dependencies from sandbox_state instead of re-extracting
|
| 1143 |
+
code_dependencies = sandbox_state['code_dependencies']
|
| 1144 |
+
python_deps, npm_deps = code_dependencies
|
| 1145 |
+
|
| 1146 |
+
# Helper function to extract package name without version
|
| 1147 |
+
def get_base_package_name(pkg: str) -> str:
|
| 1148 |
+
# For Python packages
|
| 1149 |
+
if any(op in pkg for op in ['==', '>=', '<=', '~=', '>', '<']):
|
| 1150 |
+
return pkg.split('==')[0].split('>=')[0].split('<=')[0].split('~=')[0].split('>')[0].split('<')[0]
|
| 1151 |
+
# For NPM packages
|
| 1152 |
+
if '@' in pkg and not pkg.startswith('@'):
|
| 1153 |
+
return pkg.split('@')[0]
|
| 1154 |
+
elif '@' in pkg[1:]: # Handle scoped packages
|
| 1155 |
+
return pkg.rsplit('@', 1)[0]
|
| 1156 |
+
return pkg
|
| 1157 |
+
|
| 1158 |
+
# Helper function to extract version from package string
|
| 1159 |
+
def get_package_version(pkg: str) -> str:
|
| 1160 |
+
# For Python packages
|
| 1161 |
+
if any(op in pkg for op in ['==', '>=', '<=', '~=', '>', '<']):
|
| 1162 |
+
base_name = get_base_package_name(pkg)
|
| 1163 |
+
return pkg[len(base_name):]
|
| 1164 |
+
# For NPM packages
|
| 1165 |
+
if '@' in pkg and not pkg.startswith('@'):
|
| 1166 |
+
return '@' + pkg.split('@', 1)[1]
|
| 1167 |
+
elif '@' in pkg[1:]: # Handle scoped packages
|
| 1168 |
+
_, version = pkg.rsplit('@', 1)
|
| 1169 |
+
return '@' + version
|
| 1170 |
+
return "latest"
|
| 1171 |
+
|
| 1172 |
+
# Create unified dependency dictionaries to avoid duplicates
|
| 1173 |
+
python_deps_dict = {} # pkg_name -> version
|
| 1174 |
+
npm_deps_dict = {} # pkg_name -> version
|
| 1175 |
+
|
| 1176 |
+
# Process Python dependencies
|
| 1177 |
+
for dep in python_deps:
|
| 1178 |
+
base_name = get_base_package_name(dep)
|
| 1179 |
+
version = get_package_version(dep)
|
| 1180 |
+
# Only update if we don't have a version yet or if we're replacing 'latest'
|
| 1181 |
+
if base_name not in python_deps_dict or python_deps_dict[base_name] == "latest":
|
| 1182 |
+
python_deps_dict[base_name] = version
|
| 1183 |
+
|
| 1184 |
+
# Process NPM dependencies
|
| 1185 |
+
for dep in npm_deps:
|
| 1186 |
+
base_name = get_base_package_name(dep)
|
| 1187 |
+
version = get_package_version(dep)
|
| 1188 |
+
# Only update if we don't have a version yet or if we're replacing 'latest'
|
| 1189 |
+
if base_name not in npm_deps_dict or npm_deps_dict[base_name] == "latest":
|
| 1190 |
+
npm_deps_dict[base_name] = version
|
| 1191 |
+
|
| 1192 |
+
# Convert unified dictionaries to dataframe format
|
| 1193 |
+
dependencies = []
|
| 1194 |
+
for pkg_name, version in python_deps_dict.items():
|
| 1195 |
+
dependencies.append(["python", pkg_name, version])
|
| 1196 |
+
for pkg_name, version in npm_deps_dict.items():
|
| 1197 |
+
dependencies.append(["npm", pkg_name, version])
|
| 1198 |
+
|
| 1199 |
+
# If no dependencies found, provide default empty rows
|
| 1200 |
+
if not dependencies:
|
| 1201 |
+
dependencies = [["python", "", ""], ["npm", "", ""]]
|
| 1202 |
+
|
| 1203 |
+
# Initialize output with loading message
|
| 1204 |
+
markdown_output_text = "### Sandbox Execution Log\n\n"
|
| 1205 |
+
yield (
|
| 1206 |
+
gr.Markdown(
|
| 1207 |
+
value=markdown_output_text + "🔄 Initializing sandbox environment...", visible=True
|
| 1208 |
+
),
|
| 1209 |
+
SandboxComponent(visible=False),
|
| 1210 |
+
gr.Code(value=code, language=gradio_code_language, visible=True),
|
| 1211 |
+
gr.update(value=dependencies, visible=True), # Update with unified dependencies
|
| 1212 |
+
)
|
| 1213 |
+
|
| 1214 |
+
# Use auto_selected_sandbox_environment only when in AUTO mode, otherwise use sandbox_environment
|
| 1215 |
+
sandbox_env = (
|
| 1216 |
+
sandbox_state['auto_selected_sandbox_environment']
|
| 1217 |
+
if sandbox_state['sandbox_environment'] == SandboxEnvironment.AUTO
|
| 1218 |
+
else sandbox_state['sandbox_environment']
|
| 1219 |
+
)
|
| 1220 |
+
|
| 1221 |
+
def update_markdown_output(message: str, clear_output: bool = False):
|
| 1222 |
+
nonlocal markdown_output_text
|
| 1223 |
+
if clear_output:
|
| 1224 |
+
markdown_output_text = ""
|
| 1225 |
+
markdown_output_text += f"\n{message}"
|
| 1226 |
+
return (
|
| 1227 |
+
gr.Markdown(value=markdown_output_text, visible=True, sanitize_html=False),
|
| 1228 |
+
gr.skip(),
|
| 1229 |
+
gr.skip(),
|
| 1230 |
+
gr.skip() # Always include dependencies update
|
| 1231 |
+
)
|
| 1232 |
+
|
| 1233 |
+
sandbox_id: str | None = sandbox_state["sandbox_id"] # the sandbox id
|
| 1234 |
+
sandbox_output: str = "" # stdout from sandbox
|
| 1235 |
+
sandbox_error: str = "" # stderr from sandbox
|
| 1236 |
+
print(f"sandbox_env: {sandbox_env}")
|
| 1237 |
+
match sandbox_env:
|
| 1238 |
+
case SandboxEnvironment.HTML:
|
| 1239 |
+
yield update_markdown_output("🔄 Setting up HTML sandbox...")
|
| 1240 |
+
sandbox_url, sandbox_id, sandbox_error = run_html_sandbox(
|
| 1241 |
+
code=code,
|
| 1242 |
+
code_dependencies=code_dependencies,
|
| 1243 |
+
existing_sandbox_id=sandbox_state['sandbox_id'],
|
| 1244 |
+
)
|
| 1245 |
+
if sandbox_error:
|
| 1246 |
+
yield update_markdown_output("❌ HTML sandbox failed to run!", clear_output=True)
|
| 1247 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1248 |
+
else:
|
| 1249 |
+
yield update_markdown_output("✅ HTML sandbox is ready!", clear_output=True)
|
| 1250 |
+
yield (
|
| 1251 |
+
gr.Markdown(value=markdown_output_text, visible=True),
|
| 1252 |
+
SandboxComponent(
|
| 1253 |
+
value=(sandbox_url, True, []),
|
| 1254 |
+
label="Example",
|
| 1255 |
+
visible=True,
|
| 1256 |
+
key="newsandbox",
|
| 1257 |
+
),
|
| 1258 |
+
gr.skip(),
|
| 1259 |
+
gr.skip(),
|
| 1260 |
+
)
|
| 1261 |
+
case SandboxEnvironment.REACT:
|
| 1262 |
+
yield update_markdown_output("🔄 Setting up React sandbox...")
|
| 1263 |
+
code_run_result = run_react_sandbox(
|
| 1264 |
+
code=code,
|
| 1265 |
+
code_dependencies=code_dependencies,
|
| 1266 |
+
existing_sandbox_id=sandbox_state['sandbox_id'],
|
| 1267 |
+
)
|
| 1268 |
+
sandbox_id, sandbox_error = code_run_result['sandbox_id'], code_run_result['stderr']
|
| 1269 |
+
if code_run_result['is_run_success'] is False and sandbox_error:
|
| 1270 |
+
yield update_markdown_output("❌ React sandbox failed to run!", clear_output=True)
|
| 1271 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1272 |
+
else:
|
| 1273 |
+
yield update_markdown_output("✅ React sandbox is ready!", clear_output=True)
|
| 1274 |
+
yield (
|
| 1275 |
+
gr.Markdown(value=markdown_output_text, visible=True),
|
| 1276 |
+
SandboxComponent(
|
| 1277 |
+
value=(code_run_result['sandbox_url'], True, []),
|
| 1278 |
+
label="Example",
|
| 1279 |
+
visible=True,
|
| 1280 |
+
key="newsandbox",
|
| 1281 |
+
),
|
| 1282 |
+
gr.skip(),
|
| 1283 |
+
gr.skip(),
|
| 1284 |
+
)
|
| 1285 |
+
case SandboxEnvironment.VUE:
|
| 1286 |
+
yield update_markdown_output("🔄 Setting up Vue sandbox...")
|
| 1287 |
+
code_run_result = run_vue_sandbox(
|
| 1288 |
+
code=code,
|
| 1289 |
+
code_dependencies=code_dependencies,
|
| 1290 |
+
existing_sandbox_id=sandbox_state['sandbox_id'],
|
| 1291 |
+
)
|
| 1292 |
+
sandbox_id, sandbox_error = code_run_result['sandbox_id'], code_run_result['stderr']
|
| 1293 |
+
if code_run_result['is_run_success'] is False and code_run_result['stderr']:
|
| 1294 |
+
yield update_markdown_output("❌ Vue sandbox failed to run!", clear_output=True)
|
| 1295 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{code_run_result['stderr']}\n```\n\n")
|
| 1296 |
+
else:
|
| 1297 |
+
yield update_markdown_output("✅ Vue sandbox is ready!", clear_output=True)
|
| 1298 |
+
yield (
|
| 1299 |
+
gr.Markdown(value=markdown_output_text, visible=True),
|
| 1300 |
+
SandboxComponent(
|
| 1301 |
+
value=(code_run_result['sandbox_url'], True, []),
|
| 1302 |
+
label="Example",
|
| 1303 |
+
visible=True,
|
| 1304 |
+
key="newsandbox",
|
| 1305 |
+
),
|
| 1306 |
+
gr.skip(),
|
| 1307 |
+
gr.skip(),
|
| 1308 |
+
)
|
| 1309 |
+
case SandboxEnvironment.PYGAME:
|
| 1310 |
+
yield update_markdown_output("🔄 Setting up PyGame sandbox...")
|
| 1311 |
+
code_run_result = run_pygame_sandbox(
|
| 1312 |
+
code=code,
|
| 1313 |
+
code_dependencies=code_dependencies,
|
| 1314 |
+
existing_sandbox_id=sandbox_state['sandbox_id'],
|
| 1315 |
+
)
|
| 1316 |
+
sandbox_id, sandbox_error = code_run_result['sandbox_id'], code_run_result['stderr']
|
| 1317 |
+
if code_run_result['is_run_success'] is False and code_run_result['stderr']:
|
| 1318 |
+
yield update_markdown_output("❌ PyGame sandbox failed to run!", clear_output=True)
|
| 1319 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{code_run_result['stderr']}\n```\n\n")
|
| 1320 |
+
else:
|
| 1321 |
+
yield update_markdown_output("✅ PyGame sandbox is ready!", clear_output=True)
|
| 1322 |
+
yield (
|
| 1323 |
+
gr.Markdown(value=markdown_output_text, visible=True),
|
| 1324 |
+
SandboxComponent(
|
| 1325 |
+
value=(code_run_result['sandbox_url'], True, []),
|
| 1326 |
+
label="Example",
|
| 1327 |
+
visible=True,
|
| 1328 |
+
key="newsandbox",
|
| 1329 |
+
),
|
| 1330 |
+
gr.skip(),
|
| 1331 |
+
gr.skip(),
|
| 1332 |
+
)
|
| 1333 |
+
case SandboxEnvironment.GRADIO:
|
| 1334 |
+
yield update_markdown_output("🔄 Setting up Gradio sandbox...")
|
| 1335 |
+
sandbox_url, sandbox_id, sandbox_error = run_gradio_sandbox(
|
| 1336 |
+
code=code,
|
| 1337 |
+
code_dependencies=code_dependencies,
|
| 1338 |
+
existing_sandbox_id=sandbox_state['sandbox_id'],
|
| 1339 |
+
)
|
| 1340 |
+
if sandbox_error:
|
| 1341 |
+
yield update_markdown_output("❌ Gradio sandbox failed to run!", clear_output=True)
|
| 1342 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1343 |
+
else:
|
| 1344 |
+
yield update_markdown_output("✅ Gradio sandbox is ready!", clear_output=True)
|
| 1345 |
+
yield (
|
| 1346 |
+
gr.Markdown(value=markdown_output_text, visible=True),
|
| 1347 |
+
SandboxComponent(
|
| 1348 |
+
value=(sandbox_url, True, []),
|
| 1349 |
+
label="Example",
|
| 1350 |
+
visible=True,
|
| 1351 |
+
key="newsandbox",
|
| 1352 |
+
),
|
| 1353 |
+
gr.skip(),
|
| 1354 |
+
gr.skip(),
|
| 1355 |
+
)
|
| 1356 |
+
case SandboxEnvironment.STREAMLIT:
|
| 1357 |
+
yield update_markdown_output("🔄 Setting up Streamlit sandbox...")
|
| 1358 |
+
sandbox_url, sandbox_id, sandbox_error = run_streamlit_sandbox(
|
| 1359 |
+
code=code,
|
| 1360 |
+
code_dependencies=code_dependencies,
|
| 1361 |
+
existing_sandbox_id=sandbox_state['sandbox_id'],
|
| 1362 |
+
)
|
| 1363 |
+
if sandbox_error:
|
| 1364 |
+
yield update_markdown_output("❌ Streamlit sandbox failed to run!", clear_output=True)
|
| 1365 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1366 |
+
else:
|
| 1367 |
+
yield update_markdown_output("✅ Streamlit sandbox is ready!", clear_output=True)
|
| 1368 |
+
yield (
|
| 1369 |
+
gr.Markdown(value=markdown_output_text, visible=True),
|
| 1370 |
+
SandboxComponent(
|
| 1371 |
+
value=(sandbox_url, True, []),
|
| 1372 |
+
label="Example",
|
| 1373 |
+
visible=True,
|
| 1374 |
+
key="newsandbox",
|
| 1375 |
+
),
|
| 1376 |
+
gr.skip(),
|
| 1377 |
+
gr.skip(),
|
| 1378 |
+
)
|
| 1379 |
+
case SandboxEnvironment.MERMAID:
|
| 1380 |
+
yield update_markdown_output("🔄 Setting up Mermaid visualization...")
|
| 1381 |
+
# Convert Mermaid to HTML at execution time
|
| 1382 |
+
html_code = mermaid_to_html(code, theme='light')
|
| 1383 |
+
sandbox_url, sandbox_id, sandbox_error = run_html_sandbox(
|
| 1384 |
+
code=html_code,
|
| 1385 |
+
code_dependencies=code_dependencies,
|
| 1386 |
+
existing_sandbox_id=sandbox_state['sandbox_id'],
|
| 1387 |
+
)
|
| 1388 |
+
if sandbox_error:
|
| 1389 |
+
yield update_markdown_output("❌ Mermaid visualization failed to render!", clear_output=True)
|
| 1390 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1391 |
+
else:
|
| 1392 |
+
yield update_markdown_output("✅ Mermaid visualization is ready!", clear_output=True)
|
| 1393 |
+
yield (
|
| 1394 |
+
gr.Markdown(value=markdown_output_text, visible=True),
|
| 1395 |
+
SandboxComponent(
|
| 1396 |
+
value=(sandbox_url, True, []),
|
| 1397 |
+
label="Mermaid Diagram",
|
| 1398 |
+
visible=True,
|
| 1399 |
+
key="newsandbox",
|
| 1400 |
+
),
|
| 1401 |
+
gr.skip(),
|
| 1402 |
+
gr.skip(),
|
| 1403 |
+
)
|
| 1404 |
+
case SandboxEnvironment.PYTHON_RUNNER:
|
| 1405 |
+
yield update_markdown_output("🔄 Running Python Runner...", clear_output=True)
|
| 1406 |
+
sandbox_output, sandbox_error = run_code_interpreter(
|
| 1407 |
+
code=code, code_language='python', code_dependencies=code_dependencies
|
| 1408 |
+
)
|
| 1409 |
+
if sandbox_error:
|
| 1410 |
+
yield update_markdown_output("❌ Python Runner failed to run!", clear_output=True)
|
| 1411 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1412 |
+
else:
|
| 1413 |
+
yield update_markdown_output("✅ Code execution is ready!", clear_output=True)
|
| 1414 |
+
yield (
|
| 1415 |
+
gr.Markdown(
|
| 1416 |
+
value=markdown_output_text + "\n\n" + sandbox_output,
|
| 1417 |
+
sanitize_html=False,
|
| 1418 |
+
visible=True,
|
| 1419 |
+
),
|
| 1420 |
+
SandboxComponent(
|
| 1421 |
+
value=("", False, []),
|
| 1422 |
+
label="Example",
|
| 1423 |
+
visible=False,
|
| 1424 |
+
key="newsandbox",
|
| 1425 |
+
),
|
| 1426 |
+
gr.skip(),
|
| 1427 |
+
gr.skip(),
|
| 1428 |
+
)
|
| 1429 |
+
case SandboxEnvironment.JAVASCRIPT_RUNNER:
|
| 1430 |
+
yield update_markdown_output("🔄 Running JavaScript Runner...", clear_output=True)
|
| 1431 |
+
sandbox_output, sandbox_error = run_code_interpreter(
|
| 1432 |
+
code=code, code_language='javascript', code_dependencies=code_dependencies
|
| 1433 |
+
)
|
| 1434 |
+
if sandbox_error:
|
| 1435 |
+
yield update_markdown_output("❌ JavaScript Runner failed to run!", clear_output=True)
|
| 1436 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1437 |
+
else:
|
| 1438 |
+
yield update_markdown_output("✅ Code execution is ready!", clear_output=True)
|
| 1439 |
+
yield (
|
| 1440 |
+
gr.Markdown(
|
| 1441 |
+
value=markdown_output_text + "\n\n" + sandbox_output,
|
| 1442 |
+
sanitize_html=False,
|
| 1443 |
+
visible=True,
|
| 1444 |
+
),
|
| 1445 |
+
SandboxComponent(
|
| 1446 |
+
value=("", False, []),
|
| 1447 |
+
label="Example",
|
| 1448 |
+
visible=False,
|
| 1449 |
+
key="newsandbox",
|
| 1450 |
+
),
|
| 1451 |
+
gr.skip(),
|
| 1452 |
+
gr.skip(),
|
| 1453 |
+
)
|
| 1454 |
+
case SandboxEnvironment.C_RUNNER:
|
| 1455 |
+
yield update_markdown_output("🔄 Running C Runner...", clear_output=True)
|
| 1456 |
+
sandbox_output, sandbox_error = run_c_code(
|
| 1457 |
+
code=code, existing_sandbox_id=sandbox_state['sandbox_id']
|
| 1458 |
+
)
|
| 1459 |
+
if sandbox_error:
|
| 1460 |
+
yield update_markdown_output("❌ C Runner failed to run!", clear_output=True)
|
| 1461 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1462 |
+
else:
|
| 1463 |
+
yield update_markdown_output("✅ Code execution is ready!", clear_output=True)
|
| 1464 |
+
yield (
|
| 1465 |
+
gr.Markdown(
|
| 1466 |
+
value=markdown_output_text + "\n\n" + f"```markdown\n{sandbox_output}\n```",
|
| 1467 |
+
sanitize_html=False,
|
| 1468 |
+
visible=True,
|
| 1469 |
+
),
|
| 1470 |
+
SandboxComponent(
|
| 1471 |
+
value=("", False, []),
|
| 1472 |
+
label="Example",
|
| 1473 |
+
visible=False,
|
| 1474 |
+
key="newsandbox",
|
| 1475 |
+
),
|
| 1476 |
+
gr.skip(),
|
| 1477 |
+
gr.skip(),
|
| 1478 |
+
)
|
| 1479 |
+
case SandboxEnvironment.CPP_RUNNER:
|
| 1480 |
+
yield update_markdown_output("🔄 Running C++ Runner...", clear_output=True)
|
| 1481 |
+
sandbox_output, sandbox_error = run_cpp_code(
|
| 1482 |
+
code=code, existing_sandbox_id=sandbox_state['sandbox_id']
|
| 1483 |
+
)
|
| 1484 |
+
if sandbox_error:
|
| 1485 |
+
yield update_markdown_output("❌ C++ Runner failed to run!", clear_output=True)
|
| 1486 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1487 |
+
else:
|
| 1488 |
+
yield update_markdown_output("✅ Code execution is ready!", clear_output=True)
|
| 1489 |
+
yield (
|
| 1490 |
+
gr.Markdown(
|
| 1491 |
+
value=markdown_output_text + "\n\n" + f"```markdown\n{sandbox_output}\n```",
|
| 1492 |
+
sanitize_html=False,
|
| 1493 |
+
visible=True,
|
| 1494 |
+
),
|
| 1495 |
+
SandboxComponent(
|
| 1496 |
+
value=("", False, []),
|
| 1497 |
+
label="Example",
|
| 1498 |
+
visible=False,
|
| 1499 |
+
key="newsandbox",
|
| 1500 |
+
),
|
| 1501 |
+
gr.skip(),
|
| 1502 |
+
gr.skip(),
|
| 1503 |
+
)
|
| 1504 |
+
case SandboxEnvironment.JAVA_RUNNER:
|
| 1505 |
+
yield update_markdown_output("🔄 Running Java Runner...", clear_output=True)
|
| 1506 |
+
sandbox_output, sandbox_error = run_java_code(
|
| 1507 |
+
code=code, existing_sandbox_id=sandbox_state['sandbox_id']
|
| 1508 |
+
)
|
| 1509 |
+
if sandbox_error:
|
| 1510 |
+
yield update_markdown_output("❌ Java Runner failed to run!", clear_output=True)
|
| 1511 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1512 |
+
else:
|
| 1513 |
+
yield update_markdown_output("✅ Code execution is ready!", clear_output=True)
|
| 1514 |
+
yield (
|
| 1515 |
+
gr.Markdown(
|
| 1516 |
+
value=markdown_output_text + "\n\n" + f"```markdown\n{sandbox_output}\n```",
|
| 1517 |
+
sanitize_html=False,
|
| 1518 |
+
visible=True,
|
| 1519 |
+
),
|
| 1520 |
+
SandboxComponent(
|
| 1521 |
+
value=("", False, []),
|
| 1522 |
+
label="Example",
|
| 1523 |
+
visible=False,
|
| 1524 |
+
key="newsandbox",
|
| 1525 |
+
),
|
| 1526 |
+
gr.skip(),
|
| 1527 |
+
gr.skip(),
|
| 1528 |
+
)
|
| 1529 |
+
case SandboxEnvironment.GOLANG_RUNNER:
|
| 1530 |
+
yield update_markdown_output("🔄 Running Go Runner...", clear_output=True)
|
| 1531 |
+
sandbox_output, sandbox_error = run_golang_code(
|
| 1532 |
+
code=code, existing_sandbox_id=sandbox_state['sandbox_id']
|
| 1533 |
+
)
|
| 1534 |
+
if sandbox_error:
|
| 1535 |
+
yield update_markdown_output("❌ Go Runner failed to run!", clear_output=True)
|
| 1536 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1537 |
+
else:
|
| 1538 |
+
yield update_markdown_output("✅ Code execution is ready!", clear_output=True)
|
| 1539 |
+
yield (
|
| 1540 |
+
gr.Markdown(
|
| 1541 |
+
value=markdown_output_text + "\n\n" + f"```markdown\n{sandbox_output}\n```",
|
| 1542 |
+
sanitize_html=False,
|
| 1543 |
+
visible=True,
|
| 1544 |
+
),
|
| 1545 |
+
SandboxComponent(
|
| 1546 |
+
value=("", False, []),
|
| 1547 |
+
label="Example",
|
| 1548 |
+
visible=False,
|
| 1549 |
+
key="newsandbox",
|
| 1550 |
+
),
|
| 1551 |
+
gr.skip(),
|
| 1552 |
+
gr.skip(),
|
| 1553 |
+
)
|
| 1554 |
+
# case SandboxEnvironment.CSHARP_RUNNER:
|
| 1555 |
+
# yield update_markdown_output("🔄 Running C# Runner...", clear_output=True)
|
| 1556 |
+
# output, stderr = run_csharp_code(
|
| 1557 |
+
# code=code, existing_sandbox_id=sandbox_state['sandbox_id']
|
| 1558 |
+
# )
|
| 1559 |
+
# yield update_markdown_output("✅ Code execution is ready!", clear_output=True)
|
| 1560 |
+
# if output:
|
| 1561 |
+
# yield update_markdown_output(f"### Stdout:\n```markdown\n{output}\n```\n\n")
|
| 1562 |
+
# if stderr:
|
| 1563 |
+
# yield update_markdown_output(f"### Stderr:\n```markdown\n{stderr}\n```\n\n")
|
| 1564 |
+
case SandboxEnvironment.RUST_RUNNER:
|
| 1565 |
+
yield update_markdown_output("🔄 Running Rust Runner...", clear_output=True)
|
| 1566 |
+
sandbox_output, sandbox_error = run_rust_code(
|
| 1567 |
+
code=code, existing_sandbox_id=sandbox_state['sandbox_id']
|
| 1568 |
+
)
|
| 1569 |
+
if sandbox_error:
|
| 1570 |
+
yield update_markdown_output("❌ Rust Runner failed to run!", clear_output=True)
|
| 1571 |
+
yield update_markdown_output(f"### Stderr:\n```markdown\n{sandbox_error}\n```\n\n")
|
| 1572 |
+
else:
|
| 1573 |
+
yield update_markdown_output("✅ Code execution is ready!", clear_output=True)
|
| 1574 |
+
yield (
|
| 1575 |
+
gr.Markdown(
|
| 1576 |
+
value=markdown_output_text + "\n\n" + f"```markdown\n{sandbox_output}\n```",
|
| 1577 |
+
sanitize_html=False,
|
| 1578 |
+
visible=True,
|
| 1579 |
+
),
|
| 1580 |
+
SandboxComponent(
|
| 1581 |
+
value=("", False, []),
|
| 1582 |
+
label="Example",
|
| 1583 |
+
visible=False,
|
| 1584 |
+
key="newsandbox",
|
| 1585 |
+
),
|
| 1586 |
+
gr.skip(),
|
| 1587 |
+
gr.skip(),
|
| 1588 |
+
)
|
| 1589 |
+
case _:
|
| 1590 |
+
yield (
|
| 1591 |
+
gr.Markdown(value=code, visible=True),
|
| 1592 |
+
SandboxComponent(
|
| 1593 |
+
value=("", False, []),
|
| 1594 |
+
label="Example",
|
| 1595 |
+
visible=False,
|
| 1596 |
+
key="newsandbox",
|
| 1597 |
+
),
|
| 1598 |
+
gr.skip(),
|
| 1599 |
+
gr.skip(),
|
| 1600 |
+
)
|
| 1601 |
+
|
| 1602 |
+
sandbox_state['sandbox_run_round'] += 1
|
| 1603 |
+
sandbox_state["sandbox_output"] = sandbox_output # record sandbox output if exists
|
| 1604 |
+
sandbox_state["sandbox_error"] = sandbox_error # record sandbox error if exists
|
| 1605 |
+
# generate a random sandbox id if not exists as some code runners might not return sandbox id
|
| 1606 |
+
sandbox_state['sandbox_id'] = sandbox_id if sandbox_id else str(uuid.uuid4())
|
| 1607 |
+
log_sandbox_telemetry_gradio_fn(
|
| 1608 |
+
sandbox_state=sandbox_state,
|
| 1609 |
+
sandbox_ui_value=None,
|
| 1610 |
+
)
|
| 1611 |
+
|
| 1612 |
+
print("on_run_code done")
|
sandbox/constants.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Constants for sandbox.
|
| 3 |
+
'''
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
E2B_API_KEY = os.environ.get("E2B_API_KEY")
|
| 8 |
+
'''
|
| 9 |
+
API key for the e2b API.
|
| 10 |
+
'''
|
| 11 |
+
|
| 12 |
+
AZURE_BLOB_STORAGE_CONNECTION_STRING = os.environ.get("AZURE_STORAGE_CONNECTION_STRING")
|
| 13 |
+
'''
|
| 14 |
+
API key for the Azure Blob Storage.
|
| 15 |
+
'''
|
| 16 |
+
|
| 17 |
+
AZURE_BLOB_STORAGE_CONTAINER_NAME = "softwarearenalogs"
|
| 18 |
+
'''
|
| 19 |
+
Contianer name for the Azure Blob Storage.
|
| 20 |
+
'''
|
| 21 |
+
|
| 22 |
+
SANDBOX_TEMPLATE_ID: str = "bxq9sha9l55ytsyfturr"
|
| 23 |
+
'''
|
| 24 |
+
Template ID for the sandbox.
|
| 25 |
+
'''
|
| 26 |
+
|
| 27 |
+
SANDBOX_NGINX_PORT: int = 8000
|
| 28 |
+
'''
|
| 29 |
+
Nginx port for the sandbox.
|
| 30 |
+
'''
|
| 31 |
+
|
| 32 |
+
SANDBOX_TIMEOUT_SECONDS: int = 1 * 60
|
| 33 |
+
'''
|
| 34 |
+
Timeout in seconds for created sandboxes to expire.
|
| 35 |
+
'''
|
| 36 |
+
|
| 37 |
+
CODE_RUN_TIMEOUT_SECONDS: int = 60
|
| 38 |
+
'''
|
| 39 |
+
Timeout in seconds for code execution.
|
| 40 |
+
'''
|
| 41 |
+
|
| 42 |
+
SANDBOX_RETRY_COUNT: int = 3
|
| 43 |
+
'''
|
| 44 |
+
Number of times to retry the sandbox creation.
|
| 45 |
+
'''
|
| 46 |
+
|
| 47 |
+
INSTALLED_PYPI_PACKAGES = [
|
| 48 |
+
"boto3",
|
| 49 |
+
"botocore",
|
| 50 |
+
"urllib3",
|
| 51 |
+
"setuptools",
|
| 52 |
+
"requests",
|
| 53 |
+
"certifi",
|
| 54 |
+
"idna",
|
| 55 |
+
"charset-normalizer",
|
| 56 |
+
"packaging",
|
| 57 |
+
"typing-extensions",
|
| 58 |
+
"python-dateutil",
|
| 59 |
+
"aiobotocore",
|
| 60 |
+
"s3transfer",
|
| 61 |
+
"grpcio-status",
|
| 62 |
+
"pyyaml",
|
| 63 |
+
"six",
|
| 64 |
+
"fsspec",
|
| 65 |
+
"s3fs",
|
| 66 |
+
"numpy",
|
| 67 |
+
"wheel",
|
| 68 |
+
"pip",
|
| 69 |
+
"cryptography",
|
| 70 |
+
"awscli",
|
| 71 |
+
"pydantic",
|
| 72 |
+
"cffi",
|
| 73 |
+
"attrs",
|
| 74 |
+
"google-api-core",
|
| 75 |
+
"pycparser",
|
| 76 |
+
"pandas",
|
| 77 |
+
"importlib-metadata",
|
| 78 |
+
"jmespath",
|
| 79 |
+
"click",
|
| 80 |
+
"zipp",
|
| 81 |
+
"rsa",
|
| 82 |
+
"pyasn1",
|
| 83 |
+
"markupsafe",
|
| 84 |
+
"pytz",
|
| 85 |
+
"colorama",
|
| 86 |
+
"protobuf",
|
| 87 |
+
"platformdirs",
|
| 88 |
+
"jinja2",
|
| 89 |
+
"rich",
|
| 90 |
+
"tomli",
|
| 91 |
+
"pytest",
|
| 92 |
+
"pydantic-core",
|
| 93 |
+
"pyjwt",
|
| 94 |
+
"pluggy",
|
| 95 |
+
"aiohttp",
|
| 96 |
+
"virtualenv",
|
| 97 |
+
"jsonschema",
|
| 98 |
+
"googleapis-common-protos",
|
| 99 |
+
"cachetools",
|
| 100 |
+
"google-auth",
|
| 101 |
+
"filelock",
|
| 102 |
+
"wrapt",
|
| 103 |
+
"sqlalchemy",
|
| 104 |
+
"docutils",
|
| 105 |
+
"pyasn1-modules",
|
| 106 |
+
"pyarrow",
|
| 107 |
+
"greenlet",
|
| 108 |
+
"iniconfig",
|
| 109 |
+
"pygments",
|
| 110 |
+
"annotated-types",
|
| 111 |
+
"yarl",
|
| 112 |
+
"requests-oauthlib",
|
| 113 |
+
"tzdata",
|
| 114 |
+
"psutil",
|
| 115 |
+
"multidict",
|
| 116 |
+
"pyparsing",
|
| 117 |
+
"requests-toolbelt",
|
| 118 |
+
"exceptiongroup",
|
| 119 |
+
"werkzeug",
|
| 120 |
+
"soupsieve",
|
| 121 |
+
"oauthlib",
|
| 122 |
+
"beautifulsoup4",
|
| 123 |
+
"frozenlist",
|
| 124 |
+
"more-itertools",
|
| 125 |
+
"distlib",
|
| 126 |
+
"tomlkit",
|
| 127 |
+
"pathspec",
|
| 128 |
+
"aiosignal",
|
| 129 |
+
"grpcio",
|
| 130 |
+
"tqdm",
|
| 131 |
+
"scipy",
|
| 132 |
+
"async-timeout",
|
| 133 |
+
"pillow",
|
| 134 |
+
"isodate",
|
| 135 |
+
"anyio",
|
| 136 |
+
"sortedcontainers",
|
| 137 |
+
"decorator",
|
| 138 |
+
"markdown-it-py",
|
| 139 |
+
"deprecated",
|
| 140 |
+
"mypy-extensions",
|
| 141 |
+
"sniffio",
|
| 142 |
+
"httpx",
|
| 143 |
+
"coverage",
|
| 144 |
+
"openpyxl",
|
| 145 |
+
"flask",
|
| 146 |
+
"rpds-py",
|
| 147 |
+
"et-xmlfile"
|
| 148 |
+
]
|
sandbox/prompts.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Prompts for the sandbox.
|
| 3 |
+
'''
|
| 4 |
+
|
| 5 |
+
GENERAL_SANDBOX_INSTRUCTION = """\
|
| 6 |
+
You are an expert Software Engineer, UI/UX designer, and product manager. Your task is to generate self-contained, executable code for a single file or block that can run directly in a sandbox environment. Feel free to ask questions or explain your reasoning.
|
| 7 |
+
If you do a great job based on the instructions, you will be rewarded with a high salary and a promotion.
|
| 8 |
+
|
| 9 |
+
Your code must be written using one of these supported development frameworks and environments:
|
| 10 |
+
- React (JavaScript/TypeScript)
|
| 11 |
+
- Vue (JavaScript/TypeScript)
|
| 12 |
+
- HTML (Vanilla HTML)
|
| 13 |
+
- Gradio (Python)
|
| 14 |
+
- Streamlit (Python)
|
| 15 |
+
- PyGame (Python)
|
| 16 |
+
- Mermaid (Markdown)
|
| 17 |
+
- Python Runner
|
| 18 |
+
- JavaScript Runner
|
| 19 |
+
- Command Line Code Runner (C/C++/Go/Java/Rust)
|
| 20 |
+
|
| 21 |
+
All web framework code (React, Vue, HTML) must be directly rendered in a browser and immediately executable without additional setup. DO NOT create separate CSS files
|
| 22 |
+
Python-based frameworks should be directly executable in a browser environment.
|
| 23 |
+
The code to be executed in Runners must be plain Python or JavaScript programs that do not require web UI frameworks or standard user input.
|
| 24 |
+
|
| 25 |
+
The code must be in the markdown format:
|
| 26 |
+
```<language>
|
| 27 |
+
<code>
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 31 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 32 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 33 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 34 |
+
- You CAN NOT split your program into multiple files or multiple code blocks
|
| 35 |
+
- If you use any external libraries, make sure to specify them for the installation command in either `pip install` or `npm install`
|
| 36 |
+
- You prefer JavaScript over HTML
|
| 37 |
+
- Each code block must be completely independent. If modifications are needed, the entire code block must be rewritten
|
| 38 |
+
- When fetching data, you MUST use external libraries and packages, and avoid using placeholder URLs or URLs that require API keys
|
| 39 |
+
- Make sure the program is functional by creating a state when needed and having no required props
|
| 40 |
+
- Make sure to include all necessary code in one file
|
| 41 |
+
- There are no additional files in the local file system, unless you create them inside the same program
|
| 42 |
+
- Do not touch project dependencies files like package.json, package-lock.json, requirements.txt, etc
|
| 43 |
+
|
| 44 |
+
When developing with React or Vue components, follow these specific requirements:
|
| 45 |
+
- Use TypeScript or JavaScript as the language
|
| 46 |
+
- DO NOT use gray text color on a white background
|
| 47 |
+
- Make sure it can run by itself by using a default export at the end of the file
|
| 48 |
+
- DO NOT CALL `ReactDOM.render()` AT THE END OF THE FILE
|
| 49 |
+
- Use Tailwind classes for styling. DO NOT USE ARBITRARY VALUES (e.g. 'h-[600px]'). Make sure to use a consistent color palette
|
| 50 |
+
- If you use any imports from React like `useState` or `useEffect`, make sure to import them directly
|
| 51 |
+
- Use Tailwind margin and padding classes to style the components and ensure proper spacing
|
| 52 |
+
- Various npm packages are available to be imported, e.g. `import { LineChart, XAxis, ... } from "recharts"` & `<LineChart ...><XAxis dataKey="name"> ...`
|
| 53 |
+
- Images from the web are not allowed, but you can use placeholder images by specifying the width and height like so `<img src="/api/placeholder/400/320" alt="placeholder" />`
|
| 54 |
+
|
| 55 |
+
For Python development, you must follow these constraints:
|
| 56 |
+
- For any programs that require user inputs, you MUST USE `gradio` or `streamlit`
|
| 57 |
+
- Choose suitable PyPI packages to be imported, e.g., `import pandas`
|
| 58 |
+
- Avoid using libraries that require desktop GUI interfaces, with the exceptions of `pygame`, `gradio`, and `streamlit` which are explicitly supported
|
| 59 |
+
- For PyGame applications, you have to write the main function as an async function like:
|
| 60 |
+
```python
|
| 61 |
+
import asyncio
|
| 62 |
+
import pygame
|
| 63 |
+
|
| 64 |
+
async def main():
|
| 65 |
+
global game_state
|
| 66 |
+
while game_state:
|
| 67 |
+
game_state(pygame.event.get())
|
| 68 |
+
pygame.display.update()
|
| 69 |
+
await asyncio.sleep(0) # it must be called on every frame
|
| 70 |
+
|
| 71 |
+
if __name__ == "__main__":
|
| 72 |
+
asyncio.run(main())
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
For HTML development, ensure that:
|
| 76 |
+
- All HTML code must be self-contained in a single file
|
| 77 |
+
- Include any necessary CSS and JavaScript within the HTML file
|
| 78 |
+
- Ensure the code is directly executable in a browser environment
|
| 79 |
+
- Images from the web are not allowed, but you can use placeholder images by specifying the width and height like so `<img src="/api/placeholder/400/320" alt="placeholder" />`
|
| 80 |
+
|
| 81 |
+
For Mermaid development:
|
| 82 |
+
- Write Mermaid diagrams directly using ```mermaid code blocks, e.g.:
|
| 83 |
+
```mermaid
|
| 84 |
+
graph TD;
|
| 85 |
+
A-->B;
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
For Command Line Code Runner (C/C++/Go/Java/Rust), ensure that:
|
| 89 |
+
- ALWAYS generate complete, self-contained code in a single file. Avoid non-standard libraries.
|
| 90 |
+
- Your code should be able to be compiled and run directly.
|
| 91 |
+
- Your code must complete the task without any user inputs. It should not be long running.
|
| 92 |
+
- You should provide example test cases in the code and output the result to stdout or stderr.
|
| 93 |
+
|
| 94 |
+
The code must be in the markdown format:
|
| 95 |
+
```<language>
|
| 96 |
+
<code>
|
| 97 |
+
```
|
| 98 |
+
"""
|
| 99 |
+
|
| 100 |
+
DEFAULT_PYTHON_RUNNER_INSTRUCTION = """
|
| 101 |
+
You are an expert Software Engineer. Your task is to generate self-contained, executable Python code that can run directly in a code interpreter environment.
|
| 102 |
+
|
| 103 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 104 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 105 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 106 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 107 |
+
- If you use any external libraries, make sure to specify them for installation with `pip install`
|
| 108 |
+
- Make sure to include all necessary code in one file
|
| 109 |
+
- Make sure it does not require any user inputs
|
| 110 |
+
- Choose suitable PyPI packages to be imported, e.g., `import pandas`
|
| 111 |
+
|
| 112 |
+
The code must be in the markdown format:
|
| 113 |
+
```python
|
| 114 |
+
<code>
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
You can output in stdout, stderr, or render images, plots, and tables.
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
DEFAULT_JAVASCRIPT_RUNNER_INSTRUCTION = """
|
| 121 |
+
You are an expert Software Engineer. Your task is to generate self-contained JavaScript code that can run directly in a code interpreter environment.
|
| 122 |
+
|
| 123 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 124 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 125 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 126 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 127 |
+
- If you use any external libraries, make sure to specify them for installation with `npm install`
|
| 128 |
+
- Make sure to include all necessary code in one file
|
| 129 |
+
- Ensure the code is self-contained and does not rely on browser-specific APIs
|
| 130 |
+
|
| 131 |
+
The code must be in the markdown format:
|
| 132 |
+
```javascript
|
| 133 |
+
<code>
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
You can output in stdout, stderr, or render images, plots, and tables.
|
| 137 |
+
"""
|
| 138 |
+
|
| 139 |
+
DEFAULT_HTML_SANDBOX_INSTRUCTION = """
|
| 140 |
+
You are an expert Software Engineer and UI/UX designer. Your task is to generate self-contained HTML code that can run directly in a browser environment.
|
| 141 |
+
|
| 142 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 143 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 144 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 145 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 146 |
+
- Include any necessary CSS and JavaScript within the HTML file
|
| 147 |
+
- If you use any external libraries, make sure to specify them
|
| 148 |
+
- Make sure the program is functional by creating a state when needed
|
| 149 |
+
- Images from the web are not allowed, but you can use placeholder images by specifying the width and height like so `<img src="/api/placeholder/400/320" alt="placeholder" />`
|
| 150 |
+
|
| 151 |
+
The code must be in the markdown format:
|
| 152 |
+
```html
|
| 153 |
+
<code>
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
For HTML development, ensure that:
|
| 157 |
+
- All HTML code must be self-contained in a single file
|
| 158 |
+
- Include any necessary CSS and JavaScript within the HTML file
|
| 159 |
+
- Ensure the code is directly executable in a browser environment
|
| 160 |
+
- Images from the web are not allowed, but you can use placeholder images by specifying the width and height like so `<img src="/api/placeholder/400/320" alt="placeholder" />`
|
| 161 |
+
"""
|
| 162 |
+
|
| 163 |
+
DEFAULT_REACT_SANDBOX_INSTRUCTION = """
|
| 164 |
+
You are an expert Software Engineer and UI/UX designer. Your task is to generate a self-contained React component using TypeScript that can run directly in a browser environment.
|
| 165 |
+
|
| 166 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 167 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 168 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 169 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 170 |
+
- If you use any external libraries, make sure to specify them for installation with `npm install`
|
| 171 |
+
- Make sure the program is functional by creating a state when needed and having no required props
|
| 172 |
+
- Make sure it can run by itself by using a default export at the end of the file
|
| 173 |
+
- DO NOT CALL `ReactDOM.render()` AT THE END OF THE FILE
|
| 174 |
+
- Use Tailwind classes for styling. DO NOT USE ARBITRARY VALUES (e.g. 'h-[600px]'). Make sure to use a consistent color palette
|
| 175 |
+
- If you use any imports from React like `useState` or `useEffect`, make sure to import them directly
|
| 176 |
+
- Images from the web are not allowed, but you can use placeholder images by specifying the width and height like so `<img src="/api/placeholder/400/320" alt="placeholder" />`
|
| 177 |
+
|
| 178 |
+
The code must be in the markdown format:
|
| 179 |
+
```typescript
|
| 180 |
+
<code>
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
When developing with React components, follow these specific requirements:
|
| 184 |
+
- Use TypeScript or JavaScript as the language
|
| 185 |
+
- DO NOT use gray text color on a white background
|
| 186 |
+
- Make sure it can run by itself by using a default export at the end of the file
|
| 187 |
+
- DO NOT CALL `ReactDOM.render()` AT THE END OF THE FILE
|
| 188 |
+
- Use Tailwind classes for styling. DO NOT USE ARBITRARY VALUES (e.g. 'h-[600px]'). Make sure to use a consistent color palette
|
| 189 |
+
- If you use any imports from React like `useState` or `useEffect`, make sure to import them directly
|
| 190 |
+
- Use Tailwind margin and padding classes to style the components and ensure proper spacing
|
| 191 |
+
- Various npm packages are available to be imported, e.g. `import { LineChart, XAxis, ... } from "recharts"` & `<LineChart ...><XAxis dataKey="name"> ...`
|
| 192 |
+
- Images from the web are not allowed, but you can use placeholder images by specifying the width and height like so `<img src="/api/placeholder/400/320" alt="placeholder" />`
|
| 193 |
+
"""
|
| 194 |
+
|
| 195 |
+
DEFAULT_VUE_SANDBOX_INSTRUCTION = """
|
| 196 |
+
You are an expert Software Engineer and UI/UX designer. Your task is to generate a self-contained Vue.js component using TypeScript that can run directly in a browser environment.
|
| 197 |
+
|
| 198 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 199 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 200 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 201 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 202 |
+
- If you use any external libraries, make sure to specify them for installation with `npm install`
|
| 203 |
+
- Make sure the program is functional by creating a state when needed and having no required props
|
| 204 |
+
- The component should be a simple custom page in a styled `<div>` element
|
| 205 |
+
- Do not include <NuxtWelcome /> or reference any external components
|
| 206 |
+
- Use Tailwind classes for styling. DO NOT USE ARBITRARY VALUES (e.g. 'h-[600px]'). Make sure to use a consistent color palette
|
| 207 |
+
- Images from the web are not allowed, but you can use placeholder images by specifying the width and height like so `<img src="/api/placeholder/400/320" alt="placeholder" />`
|
| 208 |
+
|
| 209 |
+
The code must be in the markdown format:
|
| 210 |
+
```vue
|
| 211 |
+
<code>
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
When developing with Vue components, follow these specific requirements:
|
| 215 |
+
- Use Vue 3's Composition API with <script setup> syntax for better TypeScript integration
|
| 216 |
+
- Use TypeScript for type safety and better developer experience
|
| 217 |
+
- Properly type all props, emits, and refs using Vue 3's type system
|
| 218 |
+
- Use defineProps, defineEmits, and other Vue 3 macros correctly
|
| 219 |
+
- Implement reactive state management using ref() or reactive() from Vue
|
| 220 |
+
- Follow Vue 3's best practices for component organization and lifecycle management
|
| 221 |
+
- Use computed properties for derived state
|
| 222 |
+
- Handle component events using proper Vue 3 event handling syntax
|
| 223 |
+
- Use Tailwind classes for styling with a consistent design system
|
| 224 |
+
- Ensure components are responsive using Tailwind's responsive classes
|
| 225 |
+
- Use Vue's built-in transition and animation systems when needed
|
| 226 |
+
- Follow proper Vue 3 security practices (e.g., v-html only when necessary)
|
| 227 |
+
- Implement proper error handling and loading states
|
| 228 |
+
- Add comments explaining complex logic or component structure
|
| 229 |
+
- Use async/await for asynchronous operations
|
| 230 |
+
- Ensure the component is accessible following ARIA best practices
|
| 231 |
+
"""
|
| 232 |
+
|
| 233 |
+
DEFAULT_PYGAME_SANDBOX_INSTRUCTION = """
|
| 234 |
+
You are an expert Software Engineer and UI/UX designer. Your task is to generate self-contained PyGame code that can run directly in a browser environment.
|
| 235 |
+
|
| 236 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 237 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 238 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 239 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 240 |
+
- If you use any external libraries, make sure to specify them for installation with `pip install`
|
| 241 |
+
- Make sure it does not require any user inputs
|
| 242 |
+
- Write the main function as an async function like:
|
| 243 |
+
|
| 244 |
+
```python
|
| 245 |
+
import asyncio
|
| 246 |
+
import pygame
|
| 247 |
+
|
| 248 |
+
async def main():
|
| 249 |
+
global game_state
|
| 250 |
+
while game_state:
|
| 251 |
+
game_state(pygame.event.get())
|
| 252 |
+
pygame.display.update()
|
| 253 |
+
await asyncio.sleep(0) # it must be called on every frame
|
| 254 |
+
|
| 255 |
+
if __name__ == "__main__":
|
| 256 |
+
asyncio.run(main())
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
The code must be in the markdown format:
|
| 260 |
+
```python
|
| 261 |
+
<code>
|
| 262 |
+
```
|
| 263 |
+
"""
|
| 264 |
+
|
| 265 |
+
DEFAULT_GRADIO_SANDBOX_INSTRUCTION = """
|
| 266 |
+
You are an expert Software Engineer and UI/UX designer. Your task is to generate self-contained Gradio application code that can run directly in a browser environment.
|
| 267 |
+
|
| 268 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 269 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 270 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 271 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 272 |
+
- If you use any external libraries, make sure to specify them for installation with `pip install`
|
| 273 |
+
- Make sure it does not require any user inputs
|
| 274 |
+
- Choose suitable PyPI packages to be imported, e.g., `import pandas`
|
| 275 |
+
|
| 276 |
+
The code must be in the markdown format:
|
| 277 |
+
```python
|
| 278 |
+
<code>
|
| 279 |
+
```
|
| 280 |
+
"""
|
| 281 |
+
|
| 282 |
+
DEFAULT_STREAMLIT_SANDBOX_INSTRUCTION = """
|
| 283 |
+
You are an expert Software Engineer and UI/UX designer. Your task is to generate self-contained Streamlit application code that can run directly in a browser environment.
|
| 284 |
+
|
| 285 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 286 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 287 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 288 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 289 |
+
- If you use any external libraries, make sure to specify them for installation with `pip install`
|
| 290 |
+
- Make sure it does not require any user inputs
|
| 291 |
+
- Choose suitable PyPI packages to be imported, e.g., `import pandas`
|
| 292 |
+
- The app should automatically reload when changes are made
|
| 293 |
+
|
| 294 |
+
The code must be in the markdown format:
|
| 295 |
+
```python
|
| 296 |
+
<code>
|
| 297 |
+
```
|
| 298 |
+
"""
|
| 299 |
+
|
| 300 |
+
DEFAULT_MERMAID_SANDBOX_INSTRUCTION = """
|
| 301 |
+
You are an expert Software Engineer. Your task is to generate self-contained Mermaid diagram code that can be rendered directly.
|
| 302 |
+
|
| 303 |
+
Before you begin writing any code, you must follow these fundamental rules:
|
| 304 |
+
- You are NOT allowed to start directly with a code block. Before writing code, ALWAYS think carefully step-by-step
|
| 305 |
+
- Your response must contain a clear explanation of the solution you are providing
|
| 306 |
+
- ALWAYS generate complete, self-contained code in a single file
|
| 307 |
+
|
| 308 |
+
The code must be in the markdown format:
|
| 309 |
+
```mermaid
|
| 310 |
+
<code>
|
| 311 |
+
```
|
| 312 |
+
|
| 313 |
+
Example:
|
| 314 |
+
```mermaid
|
| 315 |
+
graph TD;
|
| 316 |
+
A-->B;
|
| 317 |
+
```
|
| 318 |
+
"""
|
| 319 |
+
|
| 320 |
+
DEFAULT_C_CODE_RUN_SANDBOX_INSTRUCTION = """
|
| 321 |
+
You are an expert Software Engineer. Your task is to generate self-contained C code that can run directly in a code runner environment.
|
| 322 |
+
|
| 323 |
+
Ensure that:
|
| 324 |
+
- ALWAYS generate complete, self-contained code in a single file. Avoid non-standard libraries.
|
| 325 |
+
- Your code should be able to be compiled and run directly.
|
| 326 |
+
- Your code must complete the task without any user inputs. It should not be long running.
|
| 327 |
+
- You should provide example test cases in the code and output the result to stdout or stderr.
|
| 328 |
+
|
| 329 |
+
The code must be in the markdown format:
|
| 330 |
+
```c
|
| 331 |
+
<code>
|
| 332 |
+
```
|
| 333 |
+
"""
|
| 334 |
+
|
| 335 |
+
DEFAULT_CPP_CODE_RUN_SANDBOX_INSTRUCTION = """
|
| 336 |
+
You are an expert Software Engineer. Your task is to generate self-contained C++ code that can run directly in a code runner environment.
|
| 337 |
+
|
| 338 |
+
Ensure that:
|
| 339 |
+
- ALWAYS generate complete, self-contained code in a single file. Avoid non-standard libraries.
|
| 340 |
+
- Your code should be able to be compiled and run directly.
|
| 341 |
+
- Your code must complete the task without any user inputs. It should not be long running.
|
| 342 |
+
- You should provide example test cases in the code and output the result to stdout or stderr.
|
| 343 |
+
|
| 344 |
+
The code must be in the markdown format:
|
| 345 |
+
```cpp
|
| 346 |
+
<code>
|
| 347 |
+
```
|
| 348 |
+
"""
|
| 349 |
+
|
| 350 |
+
DEFAULT_JAVA_CODE_RUN_SANDBOX_INSTRUCTION = """
|
| 351 |
+
You are an expert Software Engineer. Your task is to generate self-contained Java code that can run directly in a code runner environment.
|
| 352 |
+
|
| 353 |
+
Ensure that:
|
| 354 |
+
- ALWAYS generate complete, self-contained code in a single file. Avoid non-standard libraries.
|
| 355 |
+
- Your code should be able to be compiled and run directly.
|
| 356 |
+
- Your code must complete the task without any user inputs. It should not be long running.
|
| 357 |
+
- You should provide example test cases in the code and output the result to stdout or stderr.
|
| 358 |
+
|
| 359 |
+
The code must be in the markdown format:
|
| 360 |
+
```java
|
| 361 |
+
<code>
|
| 362 |
+
```
|
| 363 |
+
"""
|
| 364 |
+
|
| 365 |
+
DEFAULT_GOLANG_CODE_RUN_SANDBOX_INSTRUCTION = """
|
| 366 |
+
You are an expert Software Engineer. Your task is to generate self-contained Go code that can run directly in a code runner environment.
|
| 367 |
+
|
| 368 |
+
Ensure that:
|
| 369 |
+
- ALWAYS generate complete, self-contained code in a single file. Avoid non-standard libraries.
|
| 370 |
+
- Your code should be able to be compiled and run directly.
|
| 371 |
+
- Your code must complete the task without any user inputs. It should not be long running.
|
| 372 |
+
- You should provide example test cases in the code and output the result to stdout or stderr.
|
| 373 |
+
|
| 374 |
+
The code must be in the markdown format:
|
| 375 |
+
```go
|
| 376 |
+
<code>
|
| 377 |
+
```
|
| 378 |
+
"""
|
| 379 |
+
|
| 380 |
+
DEFAULT_RUST_CODE_RUN_SANDBOX_INSTRUCTION = """
|
| 381 |
+
You are an expert Software Engineer. Your task is to generate self-contained Rust code that can run directly in a code runner environment.
|
| 382 |
+
|
| 383 |
+
Ensure that:
|
| 384 |
+
- ALWAYS generate complete, self-contained code in a single file. Avoid non-standard libraries.
|
| 385 |
+
- Your code should be able to be compiled and run directly.
|
| 386 |
+
- Your code must complete the task without any user inputs. It should not be long running.
|
| 387 |
+
- You should provide example test cases in the code and output the result to stdout or stderr.
|
| 388 |
+
|
| 389 |
+
The code must be in the markdown format:
|
| 390 |
+
```rust
|
| 391 |
+
<code>
|
| 392 |
+
```
|
| 393 |
+
"""
|
sandbox/sandbox_manager.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Facades for interacting with the e2b sandbox.
|
| 3 |
+
'''
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from typing import Literal
|
| 7 |
+
from e2b import Sandbox
|
| 8 |
+
from e2b.sandbox.commands.command_handle import CommandExitException
|
| 9 |
+
from e2b.exceptions import TimeoutException
|
| 10 |
+
import time
|
| 11 |
+
import threading
|
| 12 |
+
from httpcore import ReadTimeout
|
| 13 |
+
import queue
|
| 14 |
+
|
| 15 |
+
from .constants import E2B_API_KEY, SANDBOX_TEMPLATE_ID, SANDBOX_NGINX_PORT, SANDBOX_RETRY_COUNT, SANDBOX_TIMEOUT_SECONDS, INSTALLED_PYPI_PACKAGES
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def create_sandbox(template: str = SANDBOX_TEMPLATE_ID) -> Sandbox:
|
| 19 |
+
'''
|
| 20 |
+
Create a new sandbox.
|
| 21 |
+
Will retry if the sandbox creation fails.
|
| 22 |
+
'''
|
| 23 |
+
print("Creating new sandbox...")
|
| 24 |
+
for attempt in range(1, SANDBOX_RETRY_COUNT + 1):
|
| 25 |
+
try:
|
| 26 |
+
return Sandbox(
|
| 27 |
+
api_key=E2B_API_KEY,
|
| 28 |
+
domain="e2b-foxtrot.dev",
|
| 29 |
+
template=template,
|
| 30 |
+
timeout=SANDBOX_TIMEOUT_SECONDS,
|
| 31 |
+
)
|
| 32 |
+
except Exception as e:
|
| 33 |
+
if attempt < SANDBOX_RETRY_COUNT:
|
| 34 |
+
time.sleep(1 * attempt)
|
| 35 |
+
else:
|
| 36 |
+
raise e
|
| 37 |
+
raise RuntimeError("Failed to create sandbox after maximum attempts")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def reuse_or_create_sandbox(sandbox_id: str | None, template: str = SANDBOX_TEMPLATE_ID) -> Sandbox:
|
| 41 |
+
'''
|
| 42 |
+
Reuse an existing sandbox if it is running, otherwise create a new sandbox.
|
| 43 |
+
'''
|
| 44 |
+
sandbox = None
|
| 45 |
+
|
| 46 |
+
if sandbox_id is not None:
|
| 47 |
+
try:
|
| 48 |
+
sandbox = Sandbox.connect(
|
| 49 |
+
sandbox_id=sandbox_id,
|
| 50 |
+
api_key=E2B_API_KEY,
|
| 51 |
+
)
|
| 52 |
+
if not sandbox.is_running(request_timeout=5):
|
| 53 |
+
sandbox = None
|
| 54 |
+
except Exception as e:
|
| 55 |
+
pass
|
| 56 |
+
|
| 57 |
+
if sandbox is not None:
|
| 58 |
+
sandbox.set_timeout(timeout=SANDBOX_TIMEOUT_SECONDS)
|
| 59 |
+
else:
|
| 60 |
+
sandbox = create_sandbox(template=template)
|
| 61 |
+
|
| 62 |
+
return sandbox
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def run_command_in_sandbox(
|
| 66 |
+
sandbox: Sandbox,
|
| 67 |
+
command: str,
|
| 68 |
+
working_directory: str | None = None,
|
| 69 |
+
timeout: int = 60,
|
| 70 |
+
print_output: bool = True,
|
| 71 |
+
) -> tuple[bool, list[str], list[str]]:
|
| 72 |
+
'''
|
| 73 |
+
Run a command in the sandbox.
|
| 74 |
+
Return whether the command was successful and the stdout and stderr output.
|
| 75 |
+
'''
|
| 76 |
+
is_run_success = False
|
| 77 |
+
stdouts: list[str] = []
|
| 78 |
+
stderrs: list[str] = []
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
command_result = sandbox.commands.run(
|
| 82 |
+
cmd=command,
|
| 83 |
+
cwd=working_directory,
|
| 84 |
+
timeout=timeout,
|
| 85 |
+
request_timeout=timeout + 5,
|
| 86 |
+
on_stdout=lambda message: stdouts.append(message),
|
| 87 |
+
on_stderr=lambda message: stderrs.append(message),
|
| 88 |
+
)
|
| 89 |
+
if command_result and command_result.exit_code == 0:
|
| 90 |
+
is_run_success = True
|
| 91 |
+
except Exception as e:
|
| 92 |
+
stderrs.append(str(e))
|
| 93 |
+
is_run_success = False
|
| 94 |
+
|
| 95 |
+
if print_output:
|
| 96 |
+
print(f"Command: {command}")
|
| 97 |
+
for stdout in stdouts:
|
| 98 |
+
print(stdout)
|
| 99 |
+
for stderr in stderrs:
|
| 100 |
+
print(stderr)
|
| 101 |
+
|
| 102 |
+
return is_run_success, stdouts, stderrs
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def install_pip_dependencies(sandbox: Sandbox, dependencies: list[str]) -> list[str]:
|
| 106 |
+
'''
|
| 107 |
+
Install pip dependencies in the sandbox.
|
| 108 |
+
|
| 109 |
+
Return errors if any.
|
| 110 |
+
'''
|
| 111 |
+
install_errors = []
|
| 112 |
+
if not dependencies:
|
| 113 |
+
return install_errors
|
| 114 |
+
|
| 115 |
+
for dependency in dependencies:
|
| 116 |
+
if dependency not in INSTALLED_PYPI_PACKAGES:
|
| 117 |
+
try:
|
| 118 |
+
sandbox.commands.run(
|
| 119 |
+
f"uv pip install --system {dependency}",
|
| 120 |
+
timeout=60 * 3,
|
| 121 |
+
on_stdout=lambda message: print(message),
|
| 122 |
+
on_stderr=lambda message: print(message),
|
| 123 |
+
)
|
| 124 |
+
except Exception as e:
|
| 125 |
+
install_errors.append(f"Error during installing pip package {dependency}: {str(e)}")
|
| 126 |
+
continue
|
| 127 |
+
|
| 128 |
+
return install_errors
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def parse_npm_package_name(package) -> tuple[str, str | None]:
|
| 132 |
+
'''abc@123 -> abc, 123'''
|
| 133 |
+
return package.split("@")[0], package.split("@")[1] if "@" in package else None
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def is_npm_package_installed(package: str, installed_packages: dict[str, str | None]) -> bool:
|
| 137 |
+
package_name, package_version = parse_npm_package_name(package)
|
| 138 |
+
return package_name in installed_packages and (package_version is None or installed_packages[package_name] == package_version)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def get_installed_npm_packages(sandbox: Sandbox, project_root: str) -> dict[str, str | None]:
|
| 142 |
+
installed_packages_raw = []
|
| 143 |
+
sandbox.commands.run(
|
| 144 |
+
"npm list --depth=0 --json",
|
| 145 |
+
cwd=project_root,
|
| 146 |
+
timeout=30,
|
| 147 |
+
on_stdout=lambda message: installed_packages_raw.append(message),
|
| 148 |
+
)
|
| 149 |
+
lines = [json.loads(line)
|
| 150 |
+
for line in installed_packages_raw if line.strip()]
|
| 151 |
+
if not lines:
|
| 152 |
+
return {}
|
| 153 |
+
package_data = lines[-1]
|
| 154 |
+
dependencies = package_data.get("dependencies", {})
|
| 155 |
+
return {
|
| 156 |
+
dep_name: details.get("version")
|
| 157 |
+
for dep_name, details in dependencies.items()
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def install_npm_dependencies(sandbox: Sandbox, dependencies: list[str], project_root: str = '~') -> list[str]:
|
| 162 |
+
'''
|
| 163 |
+
Install npm dependencies in the sandbox.
|
| 164 |
+
|
| 165 |
+
Return errors if any.
|
| 166 |
+
'''
|
| 167 |
+
install_errors = []
|
| 168 |
+
if not dependencies:
|
| 169 |
+
return install_errors
|
| 170 |
+
|
| 171 |
+
installed_packages: dict[str, str | None] = get_installed_npm_packages(
|
| 172 |
+
sandbox, project_root)
|
| 173 |
+
|
| 174 |
+
dependencies_to_install = [dependency for dependency in dependencies if not is_npm_package_installed(
|
| 175 |
+
dependency, installed_packages)]
|
| 176 |
+
|
| 177 |
+
for dependency in dependencies_to_install:
|
| 178 |
+
try:
|
| 179 |
+
sandbox.commands.run(
|
| 180 |
+
f"npm install {dependency} --prefer-offline --no-audit --no-fund --legacy-peer-deps",
|
| 181 |
+
cwd=project_root,
|
| 182 |
+
timeout=60 * 3,
|
| 183 |
+
on_stdout=lambda message: print(message),
|
| 184 |
+
on_stderr=lambda message: print(message),
|
| 185 |
+
)
|
| 186 |
+
except Exception as e:
|
| 187 |
+
install_errors.append(f"Error during installing npm package {dependency}:" + str(e))
|
| 188 |
+
continue
|
| 189 |
+
|
| 190 |
+
return install_errors
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def run_background_command_with_timeout(
|
| 194 |
+
sandbox: Sandbox,
|
| 195 |
+
command: str,
|
| 196 |
+
cwd: str = "~",
|
| 197 |
+
timeout: int = 5,
|
| 198 |
+
) -> str:
|
| 199 |
+
"""
|
| 200 |
+
Run a command in the background and wait for a short time to check for startup errors.
|
| 201 |
+
|
| 202 |
+
Args:
|
| 203 |
+
sandbox: The sandbox instance
|
| 204 |
+
command: The command to run
|
| 205 |
+
cwd: The working directory for the command
|
| 206 |
+
timeout: How long to wait for startup errors (in seconds)
|
| 207 |
+
|
| 208 |
+
Returns:
|
| 209 |
+
str: Any error output collected during startup
|
| 210 |
+
"""
|
| 211 |
+
stderr = ""
|
| 212 |
+
|
| 213 |
+
cmd = sandbox.commands.run(
|
| 214 |
+
command,
|
| 215 |
+
timeout=60 * 3, # Overall timeout for the command
|
| 216 |
+
cwd=cwd,
|
| 217 |
+
background=True,
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
def wait_for_command(result_queue):
|
| 221 |
+
nonlocal stderr
|
| 222 |
+
try:
|
| 223 |
+
result = cmd.wait()
|
| 224 |
+
if result.stderr:
|
| 225 |
+
stderr += result.stderr
|
| 226 |
+
result_queue.put(stderr)
|
| 227 |
+
except ReadTimeout:
|
| 228 |
+
result_queue.put(stderr)
|
| 229 |
+
except CommandExitException as e:
|
| 230 |
+
stderr += "".join(e.stderr)
|
| 231 |
+
result_queue.put(stderr)
|
| 232 |
+
except TimeoutException:
|
| 233 |
+
return
|
| 234 |
+
|
| 235 |
+
result_queue = queue.Queue()
|
| 236 |
+
wait_thread = threading.Thread(
|
| 237 |
+
target=wait_for_command, args=(result_queue,))
|
| 238 |
+
wait_thread.daemon = True # Make thread daemon so it won't prevent program exit
|
| 239 |
+
wait_thread.start()
|
| 240 |
+
|
| 241 |
+
try:
|
| 242 |
+
return result_queue.get(timeout=timeout)
|
| 243 |
+
except queue.Empty:
|
| 244 |
+
return stderr
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def get_sandbox_app_url(
|
| 248 |
+
sandbox: Sandbox,
|
| 249 |
+
app_type: Literal["react", "vue", "html", "pygame"]
|
| 250 |
+
) -> str:
|
| 251 |
+
'''
|
| 252 |
+
Get the URL for the app in the sandbox with container wrapper.
|
| 253 |
+
'''
|
| 254 |
+
return f"https://{sandbox.get_host(port=SANDBOX_NGINX_PORT)}/container/?app={app_type}"
|
sandbox/sandbox_state.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Chatbot state.
|
| 3 |
+
'''
|
| 4 |
+
|
| 5 |
+
from sandbox.code_analyzer import SandboxEnvironment
|
| 6 |
+
from typing import TypedDict
|
| 7 |
+
|
| 8 |
+
class ChatbotSandboxState(TypedDict):
|
| 9 |
+
'''
|
| 10 |
+
Chatbot sandbox state in gr.state.
|
| 11 |
+
'''
|
| 12 |
+
enable_sandbox: bool
|
| 13 |
+
'''
|
| 14 |
+
Whether the code sandbox is enabled.
|
| 15 |
+
'''
|
| 16 |
+
sandbox_instruction: str | None
|
| 17 |
+
'''
|
| 18 |
+
The sandbox instruction to display.
|
| 19 |
+
'''
|
| 20 |
+
|
| 21 |
+
enabled_round: int
|
| 22 |
+
'''
|
| 23 |
+
The chat round after which the sandbox is enabled.
|
| 24 |
+
'''
|
| 25 |
+
sandbox_run_round: int
|
| 26 |
+
'''
|
| 27 |
+
How many rounds the sandbox has been run inside the session.
|
| 28 |
+
'''
|
| 29 |
+
edit_round: int
|
| 30 |
+
'''
|
| 31 |
+
How many rounds the code has been edited.
|
| 32 |
+
Starting from 0, incrementing each time the code is edited.
|
| 33 |
+
Refreshed when running a generated code.
|
| 34 |
+
'''
|
| 35 |
+
|
| 36 |
+
sandbox_environment: SandboxEnvironment | None
|
| 37 |
+
'''
|
| 38 |
+
The sandbox environment to run the code.
|
| 39 |
+
'''
|
| 40 |
+
auto_selected_sandbox_environment: SandboxEnvironment | None
|
| 41 |
+
'''
|
| 42 |
+
The sandbox environment selected automatically.
|
| 43 |
+
'''
|
| 44 |
+
code_to_execute: str | None
|
| 45 |
+
'''
|
| 46 |
+
The code to execute in the sandbox.
|
| 47 |
+
'''
|
| 48 |
+
code_language: str | None
|
| 49 |
+
'''
|
| 50 |
+
The code language to execute in the sandbox.
|
| 51 |
+
'''
|
| 52 |
+
code_dependencies: tuple[list[str], list[str]]
|
| 53 |
+
'''
|
| 54 |
+
The code dependencies for the sandbox (python, npm).
|
| 55 |
+
'''
|
| 56 |
+
|
| 57 |
+
sandbox_output: str | None
|
| 58 |
+
'''
|
| 59 |
+
The sandbox output.
|
| 60 |
+
'''
|
| 61 |
+
sandbox_error: str | None
|
| 62 |
+
'''
|
| 63 |
+
The sandbox error.
|
| 64 |
+
'''
|
| 65 |
+
|
| 66 |
+
sandbox_id: str | None
|
| 67 |
+
'''
|
| 68 |
+
The remote e2b sandbox id. None if not run yet.
|
| 69 |
+
'''
|
| 70 |
+
chat_session_id: str | None
|
| 71 |
+
'''
|
| 72 |
+
The chat session id, unique per chat.
|
| 73 |
+
The two battle models share the same chat session id.
|
| 74 |
+
'''
|
| 75 |
+
conv_id: str | None
|
| 76 |
+
'''
|
| 77 |
+
The conv id, unique per chat per model.
|
| 78 |
+
'''
|
| 79 |
+
|
| 80 |
+
btn_list_length: int
|
| 81 |
+
'''
|
| 82 |
+
Count of Gradio user interface buttons.
|
| 83 |
+
'''
|
sandbox/sandbox_telemetry.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Module for logging the sandbox interactions and state.
|
| 3 |
+
'''
|
| 4 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
from typing import Any, List, Literal, Optional, TypedDict
|
| 8 |
+
import datetime
|
| 9 |
+
|
| 10 |
+
from chat_state import LOG_DIR
|
| 11 |
+
from sandbox.sandbox_state import ChatbotSandboxState
|
| 12 |
+
|
| 13 |
+
from azure.storage.blob import BlobServiceClient
|
| 14 |
+
|
| 15 |
+
from sandbox.constants import AZURE_BLOB_STORAGE_CONNECTION_STRING, AZURE_BLOB_STORAGE_CONTAINER_NAME
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class SandboxLog(TypedDict):
|
| 19 |
+
'''
|
| 20 |
+
The schema of the sandbox log stored.
|
| 21 |
+
'''
|
| 22 |
+
sandbox_state: ChatbotSandboxState
|
| 23 |
+
user_interaction_records: Optional[List[Any]]
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def upload_data_to_azure_storage(
|
| 27 |
+
data: bytes,
|
| 28 |
+
blob_name: str,
|
| 29 |
+
write_mode: Literal['overwrite', 'append'],
|
| 30 |
+
connection_string: str | None = AZURE_BLOB_STORAGE_CONNECTION_STRING,
|
| 31 |
+
container_name: str = AZURE_BLOB_STORAGE_CONTAINER_NAME,
|
| 32 |
+
) -> None:
|
| 33 |
+
'''
|
| 34 |
+
Upload data to Azure Blob Storage.
|
| 35 |
+
'''
|
| 36 |
+
if not connection_string:
|
| 37 |
+
raise ValueError("AZURE_STORAGE_CONNECTION_STRING is not set")
|
| 38 |
+
|
| 39 |
+
blob_service_client = BlobServiceClient.from_connection_string(connection_string)
|
| 40 |
+
container_client = blob_service_client.get_container_client(container_name)
|
| 41 |
+
|
| 42 |
+
if write_mode == "overwrite":
|
| 43 |
+
container_client.upload_blob(
|
| 44 |
+
name=blob_name,
|
| 45 |
+
data=data,
|
| 46 |
+
overwrite=True
|
| 47 |
+
)
|
| 48 |
+
elif write_mode == "append":
|
| 49 |
+
blob_client = container_client.get_blob_client(blob=blob_name)
|
| 50 |
+
if not blob_client.exists():
|
| 51 |
+
blob_client.upload_blob(data, blob_type="AppendBlob")
|
| 52 |
+
else:
|
| 53 |
+
blob_client.append_block(data)
|
| 54 |
+
else:
|
| 55 |
+
raise ValueError("Unsupported write_mode. Use 'w' for overwrite or 'a' for append.")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def get_sandbox_log_blob_name(filename: str) -> str:
|
| 59 |
+
date_str = datetime.datetime.now().strftime('%Y_%m_%d')
|
| 60 |
+
blob_name = f"{date_str}/sandbox_logs/{filename}"
|
| 61 |
+
return blob_name
|
| 62 |
+
|
| 63 |
+
def get_conv_log_filepath(
|
| 64 |
+
date: datetime.date,
|
| 65 |
+
chat_mode: Literal['battle_anony', 'battle_named', 'direct'],
|
| 66 |
+
chat_session_id: str,
|
| 67 |
+
) -> str:
|
| 68 |
+
'''
|
| 69 |
+
Get the filepath for the conversation log.
|
| 70 |
+
|
| 71 |
+
Expected directory structure:
|
| 72 |
+
softwarearenlog/
|
| 73 |
+
└── YEAR_MONTH_DAY/
|
| 74 |
+
├── conv_logs/
|
| 75 |
+
│ ├── battle_anony/
|
| 76 |
+
│ │ └── CHATSESSIONID.json
|
| 77 |
+
│ ├── battle_named/
|
| 78 |
+
│ │ └── CHATSESSIONID.json
|
| 79 |
+
│ └── direct/
|
| 80 |
+
│ └── CHATSESSIONID.json
|
| 81 |
+
'''
|
| 82 |
+
date_str = date.strftime('%Y_%m_%d')
|
| 83 |
+
filepath = os.path.join(
|
| 84 |
+
date_str,
|
| 85 |
+
'conv_logs',
|
| 86 |
+
chat_mode,
|
| 87 |
+
f"{chat_session_id}.json"
|
| 88 |
+
)
|
| 89 |
+
return filepath
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def get_sandbox_log_filepath(
|
| 93 |
+
date: datetime.date,
|
| 94 |
+
chat_mode: Literal['battle_anony', 'battle_named', 'direct'],
|
| 95 |
+
chat_session_id: str,
|
| 96 |
+
) -> str:
|
| 97 |
+
'''
|
| 98 |
+
Get the filepath for the conversation log.
|
| 99 |
+
|
| 100 |
+
Expected directory structure:
|
| 101 |
+
softwarearenlog/
|
| 102 |
+
└── YEAR_MONTH_DAY/
|
| 103 |
+
├── conv_logs/
|
| 104 |
+
└── sandbox_logs/
|
| 105 |
+
├── battle/
|
| 106 |
+
│ └── sandbox-records-SESSIONID-A-B-EDITID.json
|
| 107 |
+
├── side-by-side/
|
| 108 |
+
│ └── sandbox-records-SESSIONID-A-B-EDITID.json
|
| 109 |
+
└── direct/
|
| 110 |
+
└── sandbox-records-SESSIONID-A-B-EDITID.json
|
| 111 |
+
'''
|
| 112 |
+
date_str = date.strftime('%Y_%m_%d')
|
| 113 |
+
filepath = os.path.join(
|
| 114 |
+
date_str,
|
| 115 |
+
'sandbox_logs',
|
| 116 |
+
chat_mode,
|
| 117 |
+
f"{chat_session_id}.json"
|
| 118 |
+
)
|
| 119 |
+
return filepath
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def get_conv_log_blob_name(filename: str) -> str:
|
| 123 |
+
date_str = datetime.datetime.now().strftime('%Y_%m_%d')
|
| 124 |
+
blob_name = f"{date_str}/conv_logs/{filename}"
|
| 125 |
+
return blob_name
|
| 126 |
+
|
| 127 |
+
_executor = ThreadPoolExecutor(max_workers=20)
|
| 128 |
+
def save_conv_log_to_azure_storage(
|
| 129 |
+
filename: str,
|
| 130 |
+
log_data: dict[str, Any],
|
| 131 |
+
write_mode: Literal['overwrite', 'append'] = 'append',
|
| 132 |
+
use_async: bool = True
|
| 133 |
+
) -> None:
|
| 134 |
+
try:
|
| 135 |
+
if AZURE_BLOB_STORAGE_CONNECTION_STRING:
|
| 136 |
+
blob_name = get_conv_log_blob_name(filename)
|
| 137 |
+
log_json: str = json.dumps(
|
| 138 |
+
obj=log_data,
|
| 139 |
+
default=str,
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
def _run_upload():
|
| 143 |
+
upload_data_to_azure_storage(
|
| 144 |
+
str.encode(log_json + "\n"),
|
| 145 |
+
blob_name,
|
| 146 |
+
write_mode
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
if use_async:
|
| 150 |
+
_executor.submit(_run_upload)
|
| 151 |
+
else:
|
| 152 |
+
_run_upload()
|
| 153 |
+
except Exception as e:
|
| 154 |
+
print(f"Error uploading conv log to Azure Blob Storage: {e}")
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def get_sandbox_log_filename(sandbox_state: ChatbotSandboxState) -> str:
|
| 158 |
+
return (
|
| 159 |
+
'-'.join(
|
| 160 |
+
[
|
| 161 |
+
"sandbox-logs",
|
| 162 |
+
f"{sandbox_state['conv_id']}", # chat conv id
|
| 163 |
+
f"{sandbox_state['enabled_round']}", # current chat round
|
| 164 |
+
f"{sandbox_state['sandbox_run_round']}", # current sandbox round
|
| 165 |
+
]
|
| 166 |
+
) + ".json"
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def upsert_sandbox_log(filename: str, data: str) -> None:
|
| 171 |
+
filepath = os.path.join(
|
| 172 |
+
LOG_DIR,
|
| 173 |
+
datetime.datetime.now().strftime('%Y_%m_%d'), # current date as 2025_02_02
|
| 174 |
+
'sandbox_logs',
|
| 175 |
+
filename
|
| 176 |
+
)
|
| 177 |
+
# create directory if not exists
|
| 178 |
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
| 179 |
+
with open(filepath, "w") as fout:
|
| 180 |
+
fout.write(data)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def create_sandbox_log(sandbox_state: ChatbotSandboxState, user_interaction_records: list[Any] | None) -> SandboxLog:
|
| 184 |
+
return {
|
| 185 |
+
"sandbox_state": sandbox_state,
|
| 186 |
+
"user_interaction_records": user_interaction_records,
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def log_sandbox_telemetry_gradio_fn(
|
| 191 |
+
sandbox_state: ChatbotSandboxState,
|
| 192 |
+
sandbox_ui_value: tuple[str, bool, list[Any]] | None
|
| 193 |
+
) -> None:
|
| 194 |
+
if sandbox_state is None:
|
| 195 |
+
return
|
| 196 |
+
sandbox_id = sandbox_state['sandbox_id']
|
| 197 |
+
user_interaction_records = sandbox_ui_value[2] if sandbox_ui_value else None
|
| 198 |
+
if sandbox_id is None:
|
| 199 |
+
return
|
| 200 |
+
|
| 201 |
+
log_json = create_sandbox_log(sandbox_state, user_interaction_records)
|
| 202 |
+
log_data = json.dumps(
|
| 203 |
+
log_json,
|
| 204 |
+
indent=2,
|
| 205 |
+
default=str,
|
| 206 |
+
ensure_ascii=False
|
| 207 |
+
)
|
| 208 |
+
filename = get_sandbox_log_filename(sandbox_state)
|
| 209 |
+
upsert_sandbox_log(filename=filename, data=log_data)
|
| 210 |
+
|
| 211 |
+
# # Upload to Azure Blob Storage
|
| 212 |
+
# if AZURE_BLOB_STORAGE_CONNECTION_STRING:
|
| 213 |
+
# try:
|
| 214 |
+
# blob_name = get_sandbox_log_blob_name(filename)
|
| 215 |
+
# upload_data_to_azure_storage(
|
| 216 |
+
# data=str.encode(log_data),
|
| 217 |
+
# blob_name=blob_name,
|
| 218 |
+
# write_mode='overwrite'
|
| 219 |
+
# )
|
| 220 |
+
# except Exception as e:
|
| 221 |
+
# print(f"Error uploading sandbox log to Azure Blob Storage: {e}")
|