base_agent/tools/code_executor.py

149 lines
5.3 KiB
Python
Raw Normal View History

2026-03-30 08:48:36 +00:00
"""
tools/code_executor.py
代码执行工具 在沙箱中执行 Python 代码片段
配置通过 settings.tools['code_executor'] 读取
"""
2026-02-28 08:21:35 +00:00
import io
2026-03-30 08:48:36 +00:00
import sys
import textwrap
2026-02-28 08:21:35 +00:00
import time
2026-03-30 08:48:36 +00:00
import traceback
from contextlib import redirect_stderr, redirect_stdout
2026-03-09 05:37:29 +00:00
from config.settings import settings
2026-04-15 08:20:22 +00:00
from tools.base_tool import BaseTool
2026-03-30 08:48:36 +00:00
from utils.logger import get_logger
logger = get_logger("TOOL.CodeExecutor")
def _cfg(key: str, fallback=None):
return settings.tools['code_executor'].get(key, fallback)
2026-02-28 08:21:35 +00:00
2026-04-15 08:20:22 +00:00
class Tool(BaseTool):
2026-02-28 08:21:35 +00:00
name = "code_executor"
2026-03-30 08:48:36 +00:00
description = (
"在安全沙箱中执行 Python 代码片段,返回标准输出和执行结果。"
"适用于数据处理、计算、格式转换等任务。"
"注意:沙箱模式下禁止文件系统写入、网络访问和系统调用。"
)
2026-02-28 08:21:35 +00:00
parameters = {
2026-03-30 08:48:36 +00:00
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "要执行的 Python 代码字符串",
},
"timeout": {
"type": "integer",
"description": "执行超时秒数(默认来自 config.yaml code_executor.timeout",
},
},
"required": ["code"],
2026-02-28 08:21:35 +00:00
}
2026-03-30 08:48:36 +00:00
# 沙箱模式下禁止的模块和函数
_FORBIDDEN_SANDBOX = [
"import os", "import sys", "import subprocess",
"import socket", "import requests", "import httpx",
"import shutil", "open(", "__import__",
"exec(", "eval(", "compile(",
]
2026-02-28 08:21:35 +00:00
2026-03-30 08:48:36 +00:00
def execute(self, code: str = "", timeout: int | None = None, **_) -> str:
if not code or not code.strip():
return "❌ 参数错误: code 不能为空"
2026-03-09 05:37:29 +00:00
2026-03-30 08:48:36 +00:00
sandbox = _cfg('sandbox', True)
t = timeout or _cfg('timeout', 5)
code = textwrap.dedent(code)
2026-03-09 05:37:29 +00:00
2026-03-30 08:48:36 +00:00
logger.info(
f"🐍 执行代码 sandbox={sandbox} timeout={t}s "
f"[config timeout={_cfg('timeout')}s sandbox={_cfg('sandbox')}]\n"
f" 代码预览: {code[:100]}"
2026-03-09 05:37:29 +00:00
)
2026-02-28 08:21:35 +00:00
2026-03-30 08:48:36 +00:00
# 沙箱安全检查
if sandbox:
err = self._sandbox_check(code)
if err:
return err
# 使用线程超时执行
return self._run_with_timeout(code, t)
def _run_with_timeout(self, code: str, timeout: int) -> str:
"""在独立线程中执行代码,超时则终止"""
import threading
result_box: list[str] = []
error_box: list[str] = []
def _run():
stdout_buf = io.StringIO()
stderr_buf = io.StringIO()
local_ns: dict = {}
start = time.time()
try:
with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
exec(code, {"__builtins__": __builtins__}, local_ns) # noqa: S102
elapsed = time.time() - start
stdout = stdout_buf.getvalue()
stderr = stderr_buf.getvalue()
# 尝试获取最后一个表达式的值
last_val = ""
lines = [l.strip() for l in code.strip().splitlines() if l.strip()]
if lines:
last_line = lines[-1]
if not last_line.startswith(("#", "print", "import", "from",
"def ", "class ", "if ", "for ",
"while ", "try:", "with ")):
try:
val = eval(last_line, {"__builtins__": __builtins__}, local_ns) # noqa: S307
if val is not None:
last_val = f"\n返回值: {repr(val)}"
except Exception:
pass
output = stdout + (f"\n[stderr]\n{stderr}" if stderr else "") + last_val
result_box.append(
f"✅ 执行成功 耗时={elapsed:.3f}s\n"
f"{'' * 40}\n"
f"{output.strip() or '(无输出)'}"
)
except Exception:
elapsed = time.time() - start
tb = traceback.format_exc()
error_box.append(
f"❌ 执行错误 耗时={elapsed:.3f}s\n"
f"{'' * 40}\n{tb}"
)
thread = threading.Thread(target=_run, daemon=True)
thread.start()
thread.join(timeout=timeout)
if thread.is_alive():
return (
f"⏰ 执行超时(>{timeout}s\n"
f" 请增大 config.yaml → tools.code_executor.timeout\n"
f" 或优化代码逻辑"
2026-02-28 08:21:35 +00:00
)
2026-03-30 08:48:36 +00:00
if error_box:
return error_box[0]
return result_box[0] if result_box else "❌ 执行失败(未知错误)"
def _sandbox_check(self, code: str) -> str | None:
"""沙箱模式下的静态安全检查"""
for forbidden in self._FORBIDDEN_SANDBOX:
if forbidden in code:
return (
f"❌ 沙箱限制: 代码包含禁止操作 '{forbidden}'\n"
f" 如需完整权限请在 config.yaml → "
f"tools.code_executor.sandbox 设置为 false"
)
return None