feat: code-interpreter native (#20592)

* code-interpreter native

* Update tools.py

* Update builtin.py

* Update builtin.py

* Update tools.py

* Update builtin.py

* Update builtin.py

* Update builtin.py

* Update builtin.py

* Update builtin.py

* Update builtin.py

* Update builtin.py

* Update builtin.py

* Update builtin.py

* Update builtin.py
This commit is contained in:
Classic298
2026-01-11 21:18:41 +01:00
committed by GitHub
parent 0fb4cceec1
commit af584b46f4
2 changed files with 171 additions and 0 deletions

View File

@@ -341,6 +341,166 @@ async def edit_image(
return json.dumps({"error": str(e)})
# =============================================================================
# CODE INTERPRETER TOOLS
# =============================================================================
async def execute_code(
code: str,
__request__: Request = None,
__user__: dict = None,
__event_emitter__: callable = None,
__event_call__: callable = None,
__chat_id__: str = None,
__message_id__: str = None,
__metadata__: dict = None,
) -> str:
"""
Execute Python code in a sandboxed environment and return the output.
Use this to perform calculations, data analysis, generate visualizations,
or run any Python code that would help answer the user's question.
:param code: The Python code to execute
:return: JSON with stdout, stderr, and result from execution
"""
from uuid import uuid4
if __request__ is None:
return json.dumps({"error": "Request context not available"})
try:
# Import blocked modules from config (same as middleware)
from open_webui.config import CODE_INTERPRETER_BLOCKED_MODULES
# Add import blocking code if there are blocked modules
if CODE_INTERPRETER_BLOCKED_MODULES:
import textwrap
blocking_code = textwrap.dedent(
f"""
import builtins
BLOCKED_MODULES = {CODE_INTERPRETER_BLOCKED_MODULES}
_real_import = builtins.__import__
def restricted_import(name, globals=None, locals=None, fromlist=(), level=0):
if name.split('.')[0] in BLOCKED_MODULES:
importer_name = globals.get('__name__') if globals else None
if importer_name == '__main__':
raise ImportError(
f"Direct import of module {{name}} is restricted."
)
return _real_import(name, globals, locals, fromlist, level)
builtins.__import__ = restricted_import
"""
)
code = blocking_code + "\n" + code
engine = getattr(__request__.app.state.config, "CODE_INTERPRETER_ENGINE", "pyodide")
if engine == "pyodide":
# Execute via frontend pyodide using bidirectional event call
if __event_call__ is None:
return json.dumps({"error": "Event call not available. WebSocket connection required for pyodide execution."})
output = await __event_call__(
{
"type": "execute:python",
"data": {
"id": str(uuid4()),
"code": code,
"session_id": __metadata__.get("session_id") if __metadata__ else None,
},
}
)
# Parse the output - pyodide returns dict with stdout, stderr, result
if isinstance(output, dict):
stdout = output.get("stdout", "")
stderr = output.get("stderr", "")
result = output.get("result", "")
else:
stdout = ""
stderr = ""
result = str(output) if output else ""
elif engine == "jupyter":
from open_webui.utils.code_interpreter import execute_code_jupyter
output = await execute_code_jupyter(
__request__.app.state.config.CODE_INTERPRETER_JUPYTER_URL,
code,
(
__request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN
if __request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH == "token"
else None
),
(
__request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD
if __request__.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH == "password"
else None
),
__request__.app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT,
)
stdout = output.get("stdout", "")
stderr = output.get("stderr", "")
result = output.get("result", "")
else:
return json.dumps({"error": f"Unknown code interpreter engine: {engine}"})
# Handle image outputs (base64 encoded) - replace with uploaded URLs
# Get actual user object for image upload (upload_image requires user.id attribute)
if __user__ and __user__.get("id"):
from open_webui.models.users import Users
from open_webui.utils.files import get_image_url_from_base64
user = Users.get_user_by_id(__user__["id"])
# Extract and upload images from stdout
if stdout and isinstance(stdout, str):
stdout_lines = stdout.split("\n")
for idx, line in enumerate(stdout_lines):
if "data:image/png;base64" in line:
image_url = get_image_url_from_base64(
__request__,
line,
__metadata__ or {},
user,
)
if image_url:
stdout_lines[idx] = f"![Output Image]({image_url})"
stdout = "\n".join(stdout_lines)
# Extract and upload images from result
if result and isinstance(result, str):
result_lines = result.split("\n")
for idx, line in enumerate(result_lines):
if "data:image/png;base64" in line:
image_url = get_image_url_from_base64(
__request__,
line,
__metadata__ or {},
user,
)
if image_url:
result_lines[idx] = f"![Output Image]({image_url})"
result = "\n".join(result_lines)
response = {
"status": "success",
"stdout": stdout,
"stderr": stderr,
"result": result,
}
return json.dumps(response, ensure_ascii=False)
except Exception as e:
log.exception(f"execute_code error: {e}")
return json.dumps({"error": str(e)})
# =============================================================================
# MEMORY TOOLS
# =============================================================================

View File

@@ -51,6 +51,7 @@ from open_webui.tools.builtin import (
fetch_url,
generate_image,
edit_image,
execute_code,
search_memories,
add_memory,
replace_memory_content,
@@ -449,6 +450,14 @@ def get_builtin_tools(
) and get_model_capability("image_generation"):
builtin_functions.append(edit_image)
# Add code interpreter tool if enabled globally AND model has code_interpreter capability
# Supports both pyodide (via frontend event call) and jupyter engines
if (
getattr(request.app.state.config, "ENABLE_CODE_INTERPRETER", True)
and get_model_capability("code_interpreter")
):
builtin_functions.append(execute_code)
# Notes tools - search, view, create, and update user's notes (if notes enabled globally)
if getattr(request.app.state.config, "ENABLE_NOTES", False):
builtin_functions.extend(
@@ -473,6 +482,8 @@ def get_builtin_tools(
"__request__": request,
"__user__": extra_params.get("__user__", {}),
"__event_emitter__": extra_params.get("__event_emitter__"),
"__event_call__": extra_params.get("__event_call__"),
"__metadata__": extra_params.get("__metadata__"),
"__chat_id__": extra_params.get("__chat_id__"),
"__message_id__": extra_params.get("__message_id__"),
"__model_knowledge__": model_knowledge,