smolagents 文档

安全代码执行

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

安全代码执行

如果您是构建代理的新手,请务必先阅读代理简介smolagents 入门指南

代码 Agent

多篇 研究 论文 表明,让 LLM 以代码形式编写其动作(工具调用)要比当前工具调用的标准格式好得多,后者在业界各不相同,都是“将动作写成工具名称和使用参数的 JSON”。

为什么代码更好?因为我们特意设计了代码语言,使其能出色地表达计算机执行的动作。如果 JSON 代码片段是更好的方式,那么这个包就会用 JSON 代码片段编写,魔鬼都会嘲笑我们。

代码只是在计算机上表达动作的更好方式。它具有更好的:

  • **可组合性:** 您可以将 JSON 操作相互嵌套,或者定义一组 JSON 操作供以后重用,就像您可以定义一个 Python 函数一样吗?
  • **对象管理:** 如何在 JSON 中存储 generate_image 等操作的输出?
  • **通用性:** 代码旨在简单地表达计算机可以做的任何事情。
  • LLM 训练语料库中的表示:为什么不利用这个天赐良机,即大量高质量的动作已经包含在 LLM 训练语料库中?

下图对此进行了说明,该图取自 可执行代码动作引发更好的 LLM Agent

这就是为什么我们强调推出代码 Agent,在这种情况下是 Python Agent,这意味着要花更多精力构建安全的 Python 解释器。

本地代码执行??

默认情况下,CodeAgent 在您的环境中运行 LLM 生成的代码。

这本质上是有风险的,LLM 生成的代码可能对您的环境有害。

恶意代码执行可能以多种方式发生:

  • 纯粹的 LLM 错误:LLM 远非完美,可能会在试图提供帮助时无意中生成有害命令。虽然这种风险很低,但已经观察到 LLM 试图执行潜在危险代码的实例。
  • 供应链攻击:运行不受信任或被篡改的 LLM 可能会使系统暴露于有害代码生成。虽然在使用知名模型并在安全推理基础设施上运行时这种风险极低,但它仍然是理论上的可能性。
  • 提示注入:浏览网页的 Agent 可能会访问包含有害指令的恶意网站,从而向 Agent 的内存中注入攻击。
  • 利用可公开访问的 Agent:向公众开放的 Agent 可能会被恶意行为者滥用以执行有害代码。攻击者可能会精心设计对抗性输入以利用 Agent 的执行能力,从而导致意想不到的后果。一旦恶意代码被执行,无论是意外还是有意,它都可能损坏文件系统、利用本地或云端资源、滥用 API 服务,甚至危及网络安全。

有人可能会说,在 自主性谱系 中,代码 Agent 在您的系统上赋予了 LLM 比其他自主性较低的设置高得多的自主性:这与更高的风险相伴而生。

因此,您需要非常注意安全。

为了提高安全性,我们提出了一系列措施,这些措施提供了更高级别的安全性,但设置成本也更高。

我们建议您牢记,没有解决方案是 100% 安全的。

我们的本地 Python 执行器

为了增加第一层安全性,`smolagents` 中的代码执行不是由原生 Python 解释器执行的。我们从头开始重建了一个更安全的 `LocalPythonExecutor`。

确切地说,这个解释器通过从您的代码中加载抽象语法树 (AST) 并逐个操作地执行它,确保始终遵循某些规则。

  • 默认情况下,除非用户已将其明确添加到授权列表中,否则不允许导入。
  • 此外,默认情况下禁用对子模块的访问,每个子模块也必须在导入列表中明确授权,或者您可以传递例如 `numpy.*` 来允许 `numpy` 及其所有子包,如 `numpy.random` 或 `numpy.a.b`。
    • 请注意,一些看似无害的包,如 `random`,可能会访问到潜在有害的子模块,如 `random._os`。
  • 处理的基本操作总数有上限,以防止无限循环和资源膨胀。
  • 任何在我们自定义解释器中未明确定义的操作都会引发错误。

您可以按如下方式尝试这些安全措施:

from smolagents.local_python_executor import LocalPythonExecutor

# Set up custom executor, authorize package "numpy"
custom_executor = LocalPythonExecutor(["numpy"])

# Utilisty for pretty printing errors
def run_capture_exception(command: str):
    try:
        custom_executor(harmful_command)
    except Exception as e:
        print("ERROR:\n", e)

# Undefined command just do not work
harmful_command="!echo Bad command"
run_capture_exception(harmful_command)
# >>> ERROR: invalid syntax (<unknown>, line 1)


# Imports like os will not be performed unless explicitly added to `additional_authorized_imports`
harmful_command="import os; exit_code = os.system('echo Bad command')"
run_capture_exception(harmful_command)
# >>> ERROR: Code execution failed at line 'import os' due to: InterpreterError: Import of os is not allowed. Authorized imports are: ['statistics', 'numpy', 'itertools', 'time', 'queue', 'collections', 'math', 'random', 're', 'datetime', 'stat', 'unicodedata']

# Even in authorized imports, potentially harmful packages will not be imported
harmful_command="import random; random._os.system('echo Bad command')"
run_capture_exception(harmful_command)
# >>> ERROR: Code execution failed at line 'random._os.system('echo Bad command')' due to: InterpreterError: Forbidden access to module: os

# Infinite loop are interrupted after N operations
harmful_command="""
while True:
    pass
"""
run_capture_exception(harmful_command)
# >>> ERROR: Code execution failed at line 'while True: pass' due to: InterpreterError: Maximum number of 1000000 iterations in While loop exceeded

这些安全措施使我们的解释器更安全。我们已在多种用例中使用它,从未观察到对环境造成任何损害。

重要的是要理解,没有任何本地 Python 沙箱是完全安全的。虽然我们的解释器比标准 Python 解释器提供了显著的安全性改进,但对于一个坚定的攻击者或经过微调的恶意 LLM 来说,仍然有可能找到漏洞并可能损害您的环境。

例如,如果您允许像 `Pillow` 这样的包处理图像,LLM 可能会生成代码来创建数千个大图像文件以填满您的硬盘。其他高级的逃逸技术可能会利用授权包中更深层次的漏洞。

在本地环境中运行 LLM 生成的代码总是带有一定的固有风险。以真正强大的安全隔离运行 LLM 生成的代码的唯一方法是使用远程执行选项,如 E2B 或 Docker,详见下文。

使用来自可信推理提供商的知名 LLM 时,恶意攻击的风险很低,但并非为零。对于高安全性的应用程序或使用不太可信的模型时,您应考虑使用远程执行沙箱。

安全代码执行的沙箱方法

在使用执行代码的 AI Agent 时,安全性至关重要。在 smolagents 中,有两种主要的沙箱代码执行方法,每种方法都具有不同的安全属性和功能:

Sandbox approaches comparison

  1. 在沙箱中运行单个代码片段:这种方法(图左侧)仅在沙箱中执行 Agent 生成的 Python 代码片段,而将 Agent 系统的其余部分保留在您的本地环境中。使用 `executor_type="e2b"` 或 `executor_type="docker"` 进行设置更简单,但它不支持多 Agent,并且仍需要在您的环境和沙箱之间传递状态数据。

  2. 在沙箱中运行整个 Agent 系统:这种方法(图右侧)在沙箱环境中运行整个 Agent 系统,包括 Agent、模型和工具。这提供了更好的隔离,但需要更多手动设置,并且可能需要将敏感凭据(如 API 密钥)传递到沙箱环境。

本指南介绍了如何为您的 Agent 应用程序设置和使用这两种沙箱方法。

E2B 设置

安装

  1. e2b.dev 创建一个 E2B 账户
  2. 安装所需软件包
pip install 'smolagents[e2b]'

在 E2B 中运行您的 Agent:快速入门

我们提供了一种使用 E2B 沙箱的简单方法:只需在 Agent 初始化时添加 `executor_type="e2b"`,如下所示:

from smolagents import InferenceClientModel, CodeAgent

with CodeAgent(model=InferenceClientModel(), tools=[], executor_type="e2b") as agent:
    agent.run("Can you give me the 100th Fibonacci number?")

使用 Agent 作为上下文管理器(使用 `with` 语句)可确保在 Agent 完成任务后立即清理 E2B 沙箱。或者,您可以手动调用 Agent 的 `cleanup()` 方法。

此解决方案在每次 `agent.run()` 开始时将 Agent 状态发送到服务器。然后从本地环境调用模型,但生成的代码将被发送到沙箱执行,并且只返回输出。

下图对此进行了说明。

sandboxed code execution

然而,由于任何对受管理 Agent 的调用都需要模型调用,并且我们不将机密信息传输到远程沙箱,因此模型调用将缺少凭据。因此,该解决方案尚不适用于更复杂的多 Agent 设置。

在 E2B 中运行您的 Agent:多 Agent

要在 E2B 沙箱中使用多 Agent,您需要完全在 E2B 内部运行您的 Agent。

具体方法如下:

from e2b_code_interpreter import Sandbox
import os

# Create the sandbox
sandbox = Sandbox()

# Install required packages
sandbox.commands.run("pip install smolagents")

def run_code_raise_errors(sandbox, code: str, verbose: bool = False) -> str:
    execution = sandbox.run_code(
        code,
        envs={'HF_TOKEN': os.getenv('HF_TOKEN')}
    )
    if execution.error:
        execution_logs = "\n".join([str(log) for log in execution.logs.stdout])
        logs = execution_logs
        logs += execution.error.traceback
        raise ValueError(logs)
    return "\n".join([str(log) for log in execution.logs.stdout])

# Define your agent application
agent_code = """
import os
from smolagents import CodeAgent, InferenceClientModel

# Initialize the agents
agent = CodeAgent(
    model=InferenceClientModel(token=os.getenv("HF_TOKEN"), provider="together"),
    tools=[],
    name="coder_agent",
    description="This agent takes care of your difficult algorithmic problems using code."
)

manager_agent = CodeAgent(
    model=InferenceClientModel(token=os.getenv("HF_TOKEN"), provider="together"),
    tools=[],
    managed_agents=[agent],
)

# Run the agent
response = manager_agent.run("What's the 20th Fibonacci number?")
print(response)
"""

# Run the agent code in the sandbox
execution_logs = run_code_raise_errors(sandbox, agent_code)
print(execution_logs)

Docker 设置

安装

  1. 在您的系统上安装 Docker
  2. 安装所需软件包
pip install 'smolagents[docker]'

在 Docker 中运行您的 Agent:快速入门

与上面的 E2B 沙箱类似,要快速开始使用 Docker,只需在 Agent 初始化时添加 `executor_type="docker"`,如下所示:

from smolagents import InferenceClientModel, CodeAgent

with CodeAgent(model=InferenceClientModel(), tools=[], executor_type="docker") as agent:
    agent.run("Can you give me the 100th Fibonacci number?")

使用 Agent 作为上下文管理器(使用 `with` 语句)可确保在 Agent 完成任务后立即清理 Docker 容器。或者,您可以手动调用 Agent 的 `cleanup()` 方法。

高级 Docker 用法

如果要在 Docker 中运行多 Agent 系统,您需要在一个沙箱中设置一个自定义解释器。

以下是如何设置 Dockerfile 的方法:

FROM python:3.10-bullseye

# Install build dependencies
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        build-essential \
        python3-dev && \
    pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir smolagents && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Run with limited privileges
USER nobody

# Default command
CMD ["python", "-c", "print('Container ready')"]

创建一个沙箱管理器来运行代码

import docker
import os
from typing import Optional

class DockerSandbox:
    def __init__(self):
        self.client = docker.from_env()
        self.container = None

    def create_container(self):
        try:
            image, build_logs = self.client.images.build(
                path=".",
                tag="agent-sandbox",
                rm=True,
                forcerm=True,
                buildargs={},
                # decode=True
            )
        except docker.errors.BuildError as e:
            print("Build error logs:")
            for log in e.build_log:
                if 'stream' in log:
                    print(log['stream'].strip())
            raise

        # Create container with security constraints and proper logging
        self.container = self.client.containers.run(
            "agent-sandbox",
            command="tail -f /dev/null",  # Keep container running
            detach=True,
            tty=True,
            mem_limit="512m",
            cpu_quota=50000,
            pids_limit=100,
            security_opt=["no-new-privileges"],
            cap_drop=["ALL"],
            environment={
                "HF_TOKEN": os.getenv("HF_TOKEN")
            },
        )

    def run_code(self, code: str) -> Optional[str]:
        if not self.container:
            self.create_container()

        # Execute code in container
        exec_result = self.container.exec_run(
            cmd=["python", "-c", code],
            user="nobody"
        )

        # Collect all output
        return exec_result.output.decode() if exec_result.output else None


    def cleanup(self):
        if self.container:
            try:
                self.container.stop()
            except docker.errors.NotFound:
                # Container already removed, this is expected
                pass
            except Exception as e:
                print(f"Error during cleanup: {e}")
            finally:
                self.container = None  # Clear the reference

# Example usage:
sandbox = DockerSandbox()

try:
    # Define your agent code
    agent_code = """
import os
from smolagents import CodeAgent, InferenceClientModel

# Initialize the agent
agent = CodeAgent(
    model=InferenceClientModel(token=os.getenv("HF_TOKEN"), provider="together"),
    tools=[]
)

# Run the agent
response = agent.run("What's the 20th Fibonacci number?")
print(response)
"""

    # Run the code in the sandbox
    output = sandbox.run_code(agent_code)
    print(output)

finally:
    sandbox.cleanup()

WebAssembly 设置

WebAssembly (Wasm) 是一种二进制指令格式,允许代码在安全的沙箱环境中运行。它被设计成快速、高效和安全,是执行潜在不受信任代码的绝佳选择。

`WasmExecutor` 使用 PyodideDeno

安装

  1. 在您的系统上安装 Deno

在 WebAssembly 中运行您的 Agent:快速入门

只需将 `executor_type="wasm"` 传递给 Agent 初始化,如下所示:

from smolagents import InferenceClientModel, CodeAgent

agent = CodeAgent(model=InferenceClientModel(), tools=[], executor_type="wasm")

agent.run("Can you give me the 100th Fibonacci number?")

沙箱的最佳实践

这些关键实践适用于 E2B 和 Docker 沙箱:

  • 资源管理

    • 设置内存和 CPU 限制
    • 实施执行超时
    • 监控资源使用情况
  • 安全

    • 以最低权限运行
    • 禁用不必要的网络访问
    • 使用环境变量存储机密
  • 环境

    • 保持依赖项最小化
    • 使用固定的包版本
    • 如果使用基础镜像,请定期更新
  • 清理

    • 始终确保正确清理资源,尤其是对于 Docker 容器,以避免出现占用资源的悬空容器。

✨ 通过遵循这些实践并实施适当的清理程序,您可以确保您的 Agent 在沙箱环境中安全高效地运行。

比较安全方法

如前图所示,两种沙箱方法具有不同的安全影响:

方法 1:仅在沙箱中运行代码片段

  • 优点:
    • 通过简单参数(`executor_type="e2b"` 或 `executor_type="docker"`)更容易设置
    • 无需将 API 密钥传输到沙箱
    • 更好地保护您的本地环境
  • 缺点:
    • 不支持多 Agent(受管理 Agent)
    • 仍需要在您的环境和沙箱之间传输状态
    • 仅限于特定的代码执行

方法 2:在沙箱中运行整个 Agent 系统

  • 优点:
    • 支持多 Agent
    • 完全隔离整个 Agent 系统
    • 对于复杂的 Agent 架构更灵活
  • 缺点:
    • 需要更多手动设置
    • 可能需要将敏感的 API 密钥传输到沙箱
    • 由于更复杂的操作,可能延迟更高

选择最能平衡您的安全需求与应用程序要求的方法。对于大多数具有较简单 Agent 架构的应用程序,方法 1 提供了安全性和易用性的良好平衡。对于需要完全隔离的更复杂的多 Agent 系统,方法 2 虽然设置更复杂,但提供了更好的安全保障。

< > 在 GitHub 上更新