Skip to content
LangChain 文档版本 跟随上游 源快照 1cb10a8a 更新于 2026-10-08

沙箱

使用沙箱后端在隔离环境中执行代码

智能体会生成代码、与文件系统交互并运行 shell 命令。由于你无法预测智能体可能做什么,因此它的环境必须被隔离,使其无法访问凭据、文件或网络,这一点很重要。沙箱通过在智能体的执行环境与你的主机系统之间创建边界来提供这种隔离。

在 Deep Agents 中,沙箱是后端,它们定义了智能体运行的环境。与其他仅暴露文件操作的后端(State、Filesystem、Store)不同,沙箱后端还会给智能体一个 execute 工具来运行 shell 命令。当你配置沙箱后端时,智能体会获得:

  • 所有标准文件系统工具(ls、read_file、write_file、edit_file、delete、glob、grep)

  • 用于在沙箱中运行任意 shell 命令的 execute 工具

  • 保护你的主机系统的安全边界

graph LR
    subgraph Agent
        LLM --> Tools
        Tools --> LLM
    end

    Agent <-- backend protocol --> Sandbox

    subgraph Sandbox
        Filesystem
        Bash
        Dependencies
    end

    classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
    classDef output fill:#EBD0F0,stroke:#885270,stroke-width:2px,color:#441E33

    class LLM,Tools process
    class Filesystem,Bash,Dependencies output

为什么使用沙箱?

沙箱用于保障安全。 它们让智能体能够执行任意代码、访问文件和使用网络,而不会危及你的凭据、本地文件或主机系统。 当智能体自主运行时,这种隔离至关重要。

沙箱在以下场景中特别有用:

  • 编码智能体:自主运行的智能体可以使用 shell、git、克隆仓库(许多服务商提供原生 git API,例如 Daytona 的 git 操作),并运行 Docker-in-Docker 以用于构建和测试流水线
  • 数据分析智能体:在安全、隔离的环境中加载文件、安装数据分析库(pandas、numpy 等)、运行统计计算,并创建诸如 PowerPoint 演示文稿之类的输出

TIP

正在使用 Deep Agents Code? Deep Agents Code 通过 --sandbox 标志内置了沙箱支持。有关 Deep Agents Code 专属的设置、标志(--sandbox-id、--sandbox-setup)和示例,请参阅使用远程沙箱。

NOTE

如果你在寻找 LangSmith 沙箱: LangSmith 提供一方托管的沙箱,你可以直接从 LangSmith UI 或 SDK 使用,无需第三方账户。有关托管沙箱资源、快照、服务 URL 和身份认证代理,请参阅 LangSmith Sandboxes。

基本用法

这些示例假设你已经使用服务商的 SDK 创建了沙箱/devbox,并已设置好凭据。有关注册、身份认证以及特定于服务商的生命周期细节,请参阅可用服务商。

bash
pip install "langsmith[sandbox]"
bash
uv add "langsmith[sandbox]"
python
from deepagents import create_deep_agent
from deepagents.backends import LangSmithSandbox
from langchain_anthropic import ChatAnthropic
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

agent = create_deep_agent(
    model=ChatAnthropic(model="claude-sonnet-4-6"),
    system_prompt="You are a Python coding assistant with sandbox access.",
    backend=backend,
)
try:
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "Create a small Python package and run pytest",
                }
            ]
        }
    )
finally:
    client.delete_sandbox(ls_sandbox.name)

查看示例追踪

打开此示例的公开 LangSmith 运行。

TIP

LangSmith 追踪会显示沙箱内运行了哪些 shell 命令,以及智能体如何使用文件系统工具。请按照可观测性快速入门进行设置。有关托管沙箱托管,请参阅 LangSmith Sandboxes。

我们还建议你设置 LangSmith Engine,它会监控你的追踪、检测问题并提出修复建议。

可用服务商

有关特定于服务商的设置、身份认证和生命周期细节,请参阅沙箱集成。

生命周期与作用域

大多数应用选择每个线程一个沙箱(线程作用域),或为同一个助手上的每个线程共用一个沙箱(助手作用域)。

沙箱在被关闭之前会消耗资源并产生费用。请确保在沙箱不再使用后将其关闭。

有关完整的生命周期表、异步图工厂注意事项、TTL 行为、LangGraph Deployment 接线以及客户端示例,请参阅「投入生产」中的沙箱生命周期。

线程作用域(默认)

每次对话都有自己的沙箱。首次运行会创建它;同一线程上的后续轮次会复用它。当线程结束或沙箱 TTL 过期时,该环境会被回收。如下例所示,使用沙箱名称或元数据存储映射,以便每次运行都解析到同一个沙箱。

TIP

当用户可能在一段空闲时间后返回时,请在沙箱上配置 TTL,以便服务商自动删除或归档空闲环境。

python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]  
    sandbox_name = f"thread-{thread_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(
            name=sandbox_name,
            idle_ttl_seconds=3600,  # TTL: clean up when idle
        )
    return create_deep_agent(
        model="google_genai:gemini-3.6-flash",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]  
    sandbox_name = f"thread-{thread_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(
            name=sandbox_name,
            idle_ttl_seconds=3600,  # TTL: clean up when idle
        )
    return create_deep_agent(
        model="openai:gpt-5.5",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]  
    sandbox_name = f"thread-{thread_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(
            name=sandbox_name,
            idle_ttl_seconds=3600,  # TTL: clean up when idle
        )
    return create_deep_agent(
        model="anthropic:claude-sonnet-5",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]  
    sandbox_name = f"thread-{thread_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(
            name=sandbox_name,
            idle_ttl_seconds=3600,  # TTL: clean up when idle
        )
    return create_deep_agent(
        model="openrouter:z-ai/glm-5.2",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]  
    sandbox_name = f"thread-{thread_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(
            name=sandbox_name,
            idle_ttl_seconds=3600,  # TTL: clean up when idle
        )
    return create_deep_agent(
        model="fireworks:accounts/fireworks/models/glm-5p2",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]  
    sandbox_name = f"thread-{thread_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(
            name=sandbox_name,
            idle_ttl_seconds=3600,  # TTL: clean up when idle
        )
    return create_deep_agent(
        model="baseten:zai-org/GLM-5.2",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]  
    sandbox_name = f"thread-{thread_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(
            name=sandbox_name,
            idle_ttl_seconds=3600,  # TTL: clean up when idle
        )
    return create_deep_agent(
        model="ollama:north-mini-code-1.0",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )

助手作用域

同一个助手上的每个线程都会复用同一个沙箱。文件、已安装的包和已克隆的仓库会跨对话持续存在。

WARNING

助手作用域的沙箱会随着时间推移累积沙箱内状态。请向你的沙箱服务商配置 TTL,使用快照定期重置,或实现清理逻辑,以免磁盘和内存无限增长。

python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    assistant_id = config["configurable"]["assistant_id"]  
    sandbox_name = f"assistant-{assistant_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(name=sandbox_name)
    return create_deep_agent(
        model="google_genai:gemini-3.6-flash",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    assistant_id = config["configurable"]["assistant_id"]  
    sandbox_name = f"assistant-{assistant_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(name=sandbox_name)
    return create_deep_agent(
        model="openai:gpt-5.5",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    assistant_id = config["configurable"]["assistant_id"]  
    sandbox_name = f"assistant-{assistant_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(name=sandbox_name)
    return create_deep_agent(
        model="anthropic:claude-sonnet-5",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    assistant_id = config["configurable"]["assistant_id"]  
    sandbox_name = f"assistant-{assistant_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(name=sandbox_name)
    return create_deep_agent(
        model="openrouter:z-ai/glm-5.2",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    assistant_id = config["configurable"]["assistant_id"]  
    sandbox_name = f"assistant-{assistant_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(name=sandbox_name)
    return create_deep_agent(
        model="fireworks:accounts/fireworks/models/glm-5p2",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    assistant_id = config["configurable"]["assistant_id"]  
    sandbox_name = f"assistant-{assistant_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(name=sandbox_name)
    return create_deep_agent(
        model="baseten:zai-org/GLM-5.2",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langchain_core.runnables import RunnableConfig
from langsmith.sandbox import SandboxClient

client = SandboxClient()

async def agent(config: RunnableConfig):
    assistant_id = config["configurable"]["assistant_id"]  
    sandbox_name = f"assistant-{assistant_id}"
    existing = [
        sb
        for sb in client.list_sandboxes()
        if getattr(sb, "name", None) == sandbox_name
    ]
    if existing:
        ls_sandbox = existing[0]
    else:
        ls_sandbox = client.create_sandbox(name=sandbox_name)
    return create_deep_agent(
        model="ollama:north-mini-code-1.0",
        backend=LangSmithSandbox(sandbox=ls_sandbox),
    )

有关在图工厂之外手动创建、执行和拆卸的内容,请参阅基本用法;有关特定于服务商的 API,请参阅沙箱集成。

集成模式

根据智能体的运行位置,集成智能体与沙箱有两种架构模式。

智能体位于沙箱中模式

智能体在沙箱内运行,你通过网络与它通信。你构建一个预装了智能体框架的 Docker 或 VM 镜像,在沙箱内运行它,并从外部连接以发送消息。

优势:

  • ✅ 与本地开发高度一致。
  • ✅ 智能体与环境之间紧密耦合。

权衡:

  • 🔴 API 密钥必须存放在沙箱内(安全风险)。
  • 🔴 更新需要重新构建镜像。
  • 🔴 需要用于通信的基础设施(WebSocket 或 HTTP 层)。

要在沙箱中运行智能体,请构建一个镜像并在其上安装 deepagents。

dockerfile
FROM python:3.11
RUN pip install deepagents-code

然后在沙箱内运行智能体。 要使用沙箱内的智能体,你必须添加额外的基础设施来处理你的应用与沙箱内智能体之间的通信。

沙箱作为工具模式

智能体在你的机器或服务器上运行。当它需要执行代码时,会调用沙箱工具(例如 execute、read_file 或 write_file),这些工具会调用服务商的 API 以在远程沙箱中运行操作。

优势:

  • ✅ 无需重新构建镜像即可即时更新智能体代码。
  • ✅ 智能体状态与执行之间更清晰的分离。
    • API 密钥保留在沙箱之外。
    • 沙箱故障不会丢失智能体状态。
    • 可以选择在多个沙箱中并行运行任务。
  • ✅ 仅为执行时间付费。

权衡:

  • 🔴 每次执行调用都有网络延迟。
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

agent = create_deep_agent(
    model="google_genai:gemini-3.6-flash",
    backend=backend,
    system_prompt="You are a coding assistant with sandbox access. You can create and run code in the sandbox.",
)

try:
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "Create a hello world Python script and run it",
                }
            ]
        }
    )
    print(result["messages"][-1].content)
finally:
    client.delete_sandbox(ls_sandbox.name)
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

agent = create_deep_agent(
    model="openai:gpt-5.5",
    backend=backend,
    system_prompt="You are a coding assistant with sandbox access. You can create and run code in the sandbox.",
)

try:
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "Create a hello world Python script and run it",
                }
            ]
        }
    )
    print(result["messages"][-1].content)
finally:
    client.delete_sandbox(ls_sandbox.name)
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

agent = create_deep_agent(
    model="anthropic:claude-sonnet-5",
    backend=backend,
    system_prompt="You are a coding assistant with sandbox access. You can create and run code in the sandbox.",
)

try:
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "Create a hello world Python script and run it",
                }
            ]
        }
    )
    print(result["messages"][-1].content)
finally:
    client.delete_sandbox(ls_sandbox.name)
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

agent = create_deep_agent(
    model="openrouter:z-ai/glm-5.2",
    backend=backend,
    system_prompt="You are a coding assistant with sandbox access. You can create and run code in the sandbox.",
)

try:
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "Create a hello world Python script and run it",
                }
            ]
        }
    )
    print(result["messages"][-1].content)
finally:
    client.delete_sandbox(ls_sandbox.name)
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

agent = create_deep_agent(
    model="fireworks:accounts/fireworks/models/glm-5p2",
    backend=backend,
    system_prompt="You are a coding assistant with sandbox access. You can create and run code in the sandbox.",
)

try:
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "Create a hello world Python script and run it",
                }
            ]
        }
    )
    print(result["messages"][-1].content)
finally:
    client.delete_sandbox(ls_sandbox.name)
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

agent = create_deep_agent(
    model="baseten:zai-org/GLM-5.2",
    backend=backend,
    system_prompt="You are a coding assistant with sandbox access. You can create and run code in the sandbox.",
)

try:
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "Create a hello world Python script and run it",
                }
            ]
        }
    )
    print(result["messages"][-1].content)
finally:
    client.delete_sandbox(ls_sandbox.name)
python
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

agent = create_deep_agent(
    model="ollama:north-mini-code-1.0",
    backend=backend,
    system_prompt="You are a coding assistant with sandbox access. You can create and run code in the sandbox.",
)

try:
    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": "Create a hello world Python script and run it",
                }
            ]
        }
    )
    print(result["messages"][-1].content)
finally:
    client.delete_sandbox(ls_sandbox.name)

View example trace

打开本示例的一个公开 LangSmith 运行记录。

本文档中的示例采用「沙箱作为工具」模式。 当你的服务商 SDK 负责通信层,并且你希望生产环境与本地开发保持一致时,选择「智能体置于沙箱中」模式。 当你需要快速迭代智能体逻辑、将 API 密钥保留在沙箱之外,或偏好更清晰的关注点分离时,选择「沙箱作为工具」模式。

沙箱如何工作

隔离边界

所有沙箱服务商都会保护你的宿主机系统免受智能体的文件系统与 shell 操作的影响。智能体无法读取你的本地文件、访问你机器上的环境变量,或干扰其他进程。然而,仅靠沙箱并不能防御以下情况:

  • 上下文注入:控制智能体部分输入的攻击者可以指示它在沙箱内运行任意命令。沙箱是隔离的,但智能体在沙箱内拥有完全的控制权。
  • 网络外泄:除非网络访问被阻止,否则被上下文注入的智能体可以通过 HTTP 或 DNS 将数据发送出沙箱。部分服务商支持阻止网络访问(例如 Modal 上的 blockNetwork: true)。

有关如何处理密钥并缓解这些风险,请参阅安全注意事项。

execute 方法

沙箱后端具有简单的架构:服务商唯一必须实现的方法是 execute(),它运行一条 shell 命令并返回其输出。

其他所有文件系统操作(read、write、edit、delete、ls、glob、grep)都由 BaseSandbox 基类构建在 execute() 之上,该基类构造脚本并通过 execute() 在沙箱内运行它们。

graph TB
    subgraph "Agent tools"
        Tools["ls, read_file, ..."]
        execute
    end

    BaseSandbox["BaseSandbox<br/>(uses execute)"] --> Tools
    execute_method["execute()"] --> BaseSandbox
    execute_method --> execute
    Provider["Provider SDK"] --> execute_method

    classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
    classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900

    class Tools,execute process
    class BaseSandbox,execute_method process
    class Provider trigger

这种设计意味着:

  • 新增服务商非常简单。 实现 execute()——基类会处理其他所有事情。
  • execute 工具是有条件可用的。 在每次模型调用时,harness 都会检查后端是否实现了 SandboxBackendProtocol。如果没有,该工具会被过滤掉,智能体永远不会看到它。

当智能体调用 execute 工具时,它会提供一个 command 字符串,并收到合并后的 stdout/stderr、退出码,以及输出过大时的截断提示。

你也可以在应用代码中直接调用后端的 execute() 方法。

python
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

result = backend.execute("python --version")
print(result.output)

例如:

4
[Command succeeded with exit code 0]
bash: foobar: command not found
[Command failed with exit code 127]

如果命令产生非常大的输出,结果会自动保存到文件,并指示智能体使用 read_file 逐步访问它。这可以防止上下文窗口溢出。

文件访问的两个层面

文件进出沙箱有两种截然不同的方式,理解何时使用哪一种很重要:

智能体文件系统工具:read_file、write_file、edit_file、delete、ls、glob、grep、execute 是 LLM 在执行期间调用的工具。这些工具在沙箱内通过 execute() 执行。智能体使用它们来读取代码、写入文件,以及作为任务的一部分运行命令。

文件传输 API:你的应用代码所调用的 uploadFiles() 和 downloadFiles() 方法。它们使用服务商原生的文件传输 API(而非 shell 命令),专为在你的宿主机环境与沙箱之间移动文件而设计。可用于:

  • 在智能体运行前用源代码、配置或数据初始化沙箱
  • 在智能体完成后取回产物(生成的代码、构建输出、报告)
  • 预置依赖,供智能体使用
graph LR
    subgraph "Your application"
        App[Application code]
    end

    subgraph "Agent"
        LLM --> Tools["read_file, write_file, ..."]
        Tools --> LLM
    end

    subgraph "Sandbox"
        FS[Filesystem]
    end

    App -- "Provider API" --> FS
    Tools -- "execute()" --> FS

    classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900
    classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
    classDef output fill:#EBD0F0,stroke:#885270,stroke-width:2px,color:#441E33

    class App trigger
    class LLM,Tools process
    class FS output

处理文件

deepagents 沙箱后端支持文件传输 API,用于在你的应用与沙箱之间移动文件。

初始化沙箱

在智能体运行前使用 upload_files() 填充沙箱。路径必须是绝对路径,内容为 bytes:

python
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

backend.upload_files(
    [
        ("/src/index.py", b"print('Hello')\n"),
        ("/pyproject.toml", b"[project]\nname = 'my-app'\n"),
    ]
)

取回产物

在智能体完成后使用 download_files() 从沙箱取回文件:

python
from deepagents.backends.langsmith import LangSmithSandbox
from langsmith.sandbox import SandboxClient

client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)

results = backend.download_files(["/src/index.py", "/output.txt"])
for result in results:
    if result.content is not None:
        print(f"{result.path}: {result.content.decode()}")
    else:
        print(f"Failed to download {result.path}: {result.error}")

NOTE

在沙箱内部,智能体使用文件系统工具(read_file、write_file)。upload_files 和 download_files 方法用于让你的应用代码跨宿主机与沙箱之间的边界移动文件。

安全注意事项

沙箱将代码执行与你的宿主机系统隔离,但它们无法防御上下文注入。控制智能体部分输入的攻击者可以指示它从沙箱内部读取文件、运行命令或外泄数据。这使得沙箱内的凭据尤为危险。

WARNING

切勿将密钥放入沙箱。 注入到沙箱中的 API 密钥、token、数据库凭据及其他密钥(通过环境变量、挂载文件或 secrets 选项)都可能被上下文注入的智能体读取并外泄。这同样适用于短期或限定范围的凭据——如果智能体能访问它们,攻击者也能。

安全地处理密钥

如果你的智能体需要调用需要身份认证的 API 或访问受保护的资源,你有两种选择:

  1. 将密钥保留在沙箱之外的工具中。 定义在你的宿主机环境中运行(而非沙箱内)的工具,并在那里处理身份认证。智能体按名称调用这些工具,但永远看不到凭据。这是推荐的做法。

  2. 使用注入凭据的网络代理。 部分沙箱服务商支持代理,它可以拦截从沙箱发出的 HTTP 请求,并在转发前附加凭据(例如 Authorization 请求头)。智能体永远看不到密钥——它只是向某个 URL 发出普通请求。这种做法在各服务商之间尚未广泛可用。

WARNING

如果你必须将密钥注入沙箱(不推荐),请采取以下预防措施:

  • 对所有工具调用(而不仅仅是敏感调用)启用人在回路审批
  • 阻止或限制从沙箱发出的网络访问,以限制外泄途径
  • 使用尽可能窄的凭据范围和尽可能短的有效期
  • 监控沙箱网络流量,发现异常的外发请求

即使有这些防护措施,这仍然是一种不安全的变通方法。足够有创造力的上下文注入攻击可以绕过输出过滤和 HITL 审查。

通用最佳实践

  • 在你的应用基于沙箱输出采取行动之前先审查它们
  • 在不需要时阻止沙箱网络访问
  • 使用中间件过滤或脱敏工具输出中的敏感模式
  • 将沙箱内产生的一切视为不受信任的输入

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。