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)})
|
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
|
# MEMORY TOOLS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ from open_webui.tools.builtin import (
|
|||||||
fetch_url,
|
fetch_url,
|
||||||
generate_image,
|
generate_image,
|
||||||
edit_image,
|
edit_image,
|
||||||
|
execute_code,
|
||||||
search_memories,
|
search_memories,
|
||||||
add_memory,
|
add_memory,
|
||||||
replace_memory_content,
|
replace_memory_content,
|
||||||
@@ -449,6 +450,14 @@ def get_builtin_tools(
|
|||||||
) and get_model_capability("image_generation"):
|
) and get_model_capability("image_generation"):
|
||||||
builtin_functions.append(edit_image)
|
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)
|
# Notes tools - search, view, create, and update user's notes (if notes enabled globally)
|
||||||
if getattr(request.app.state.config, "ENABLE_NOTES", False):
|
if getattr(request.app.state.config, "ENABLE_NOTES", False):
|
||||||
builtin_functions.extend(
|
builtin_functions.extend(
|
||||||
@@ -473,6 +482,8 @@ def get_builtin_tools(
|
|||||||
"__request__": request,
|
"__request__": request,
|
||||||
"__user__": extra_params.get("__user__", {}),
|
"__user__": extra_params.get("__user__", {}),
|
||||||
"__event_emitter__": extra_params.get("__event_emitter__"),
|
"__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__"),
|
"__chat_id__": extra_params.get("__chat_id__"),
|
||||||
"__message_id__": extra_params.get("__message_id__"),
|
"__message_id__": extra_params.get("__message_id__"),
|
||||||
"__model_knowledge__": model_knowledge,
|
"__model_knowledge__": model_knowledge,
|
||||||
|
|||||||
Reference in New Issue
Block a user