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:
@@ -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""
|
||||
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""
|
||||
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
|
||||
# =============================================================================
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user