代理会生成代码、与文件系统交互以及运行 shell 命令。由于我们无法预测代理可能执行的操作,因此重要的是确保其环境是隔离的,这样它就无法访问凭据、文件或网络。沙盒通过在代理的执行环境与您的主机系统之间创建边界来提供这种隔离。
在 Deep Agents 中, **沙盒是 后端** ,用于定义代理操作的运行环境。与其他后端(State、Filesystem、Store)仅暴露文件操作不同,沙盒后端还为代理提供了一个 execute 工具,用于运行 shell 命令。配置沙盒后端后,代理将获得:
- - 所有标准文件系统工具(
ls,read_file,write_file,edit_file,glob,grep) - - 该
execute工具,用于在沙盒中运行任意 shell 命令 - - 保护您主机系统的安全边界
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 演示文稿等输出
基本用法
These examples assume you have already created a sandbox/devbox using the provider's SDK and have credentials set up. For signup, authentication, and provider-specific lifecycle details, see 可用提供商.
可用提供商
有关特定提供商的设置、认证和生命周期详情,请参见 沙盒集成.
没有看到您的提供商?您可以实现自己的沙箱后端。参见 贡献沙箱集成.
生命周期和作用域
大多数应用程序选择每个 线程 一个沙箱(线程作用域),或者在同一 助手 的每个线程共享一个沙箱(助手作用域)。
沙箱消耗资源并产生成本,直到被关闭。请确保在沙箱不再使用时将其关闭。
有关完整的生命周期表、异步 图工厂 说明、TTL 行为、LangGraph 部署连接以及客户端示例,请参阅 沙箱生命周期 ,位于生产环境指南中。
线程作用域(默认)
每个对话获得自己的沙箱。第一次运行创建它;同一线程上的后续对话复用它。当线程结束或沙箱 TTL 过期时,环境消失。按照以下示例存储沙箱名称或元数据的映射,以便每次运行解析到同一个沙箱。
Assistant-scoped
同一助手上的每个线程复用一个沙箱。文件、安装的包和克隆的仓库在对话之间持久保存。
有关在图工厂外部手动创建、执行和拆卸的内容,请参阅 基本用法 和 沙箱集成 ,了解提供商特定的 API。
集成模式
根据代理运行的位置,代理与沙箱集成有两种架构模式。
沙箱内代理模式
代理在沙箱内运行,您通过网络与它通信。您构建一个预装代理框架的 Docker 或 VM 镜像,在沙箱内运行它,并从外部连接发送消息。
Benefits:
- - ✅ 与本地开发高度一致。
- - ✅ 代理与环境紧密耦合。
Trade-offs:
- - 🔴 API 密钥必须存放在沙箱内(安全风险)。
- - 🔴 更新需要重建镜像。
- - 🔴 需要通信基础设施(WebSocket 或 HTTP 层)。
要在沙箱中运行代理,请构建镜像并在其上安装 deepagents。
FROM python:3.11
RUN pip install deepagents-code
然后在沙箱内运行代理。 要使用沙箱内的代理,您需要添加额外的基础设施来处理应用程序与沙箱内代理之间的通信。
沙箱作为工具模式
代理运行在您的机器或服务器上。当需要执行代码时,它调用沙盒工具(例如 execute, read_file, or write_file)来调用提供商的 API 在远程沙盒中执行操作。
Benefits:
- - ✅ 无需重建镜像即可即时更新代理代码。
- - ✅ 代理状态与执行之间更清晰的分离。
- - API 密钥保留在沙盒外部。
- - 沙盒故障不会丢失代理状态。
- - 可选择在多个沙盒中并行运行任务。
- - ✅ 仅需为执行时间付费。
Trade-offs:
- - 🔴 每次执行调用都会产生网络延迟。
from deepagents import create_deep_agent
from deepagents.backends.langsmith import LangSmithSandbox
from dotenv import load_dotenv
from langsmith.sandbox import SandboxClient
load_dotenv()
# Can also do this with AgentCore, Daytona, E2B, Modal, Runloop, or Vercel
client = SandboxClient()
ls_sandbox = client.create_sandbox()
backend = LangSmithSandbox(sandbox=ls_sandbox)
agent = create_deep_agent(
model="google_genai:gemini-3.5-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)
本文档中的示例使用沙盒作为工具模式。 当提供商的 SDK 处理通信层且您希望生产环境镜像本地开发时,请选择代理在沙盒中模式。 当需要快速迭代代理逻辑、将 API 密钥保留在沙盒外,或希望更清晰的责任分离时,请选择沙盒作为工具模式。
沙盒的工作原理
隔离边界
所有沙盒提供商会保护您的主机系统免受代理的文件系统和 shell 操作的影响。代理无法读取您的本地文件、访问您机器上的环境变量或干扰其他进程。但是,单独的沙盒无法 **无法** 防范以下威胁:
- 上下文注入:控制代理部分输入的攻击者可以指示其在沙盒内运行任意命令。沙盒是隔离的,但代理在其中拥有完全控制权。
- 网络渗透:除非网络访问被阻止,否则被注入上下文的代理可以通过 HTTP 或 DNS 将数据发送出沙盒。某些提供商支持阻止网络访问(例如,
blockNetwork: true在 Modal 上)。
请参见 安全注意事项 了解如何处理密钥和降低这些风险。
这些 execute 方法
沙箱后端采用简单的架构:提供程序只需实现一个方法 execute(),它运行 shell 命令并返回输出。所有其他文件系统操作(read, write, edit, ls, glob, grep)都建立在 execute() 之上,由 BaseSandbox 基类提供支持,该类构造脚本并通过 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 工具是有条件可用的。** 每次模型调用时,测试框架都会检查后端是否实现了 SandboxBackendProtocol。如果没有,工具会被过滤掉,代理永远不会看到它。
当代理调用 execute 工具时,它会提供一个 command string and gets back the combined stdout/stderr, exit code, and a truncation notice if the output was too large.
您也可以在应用代码中直接调用后端 execute() 方法。
LangSmith
from langsmith.sandbox import SandboxClient
from deepagents.backends.langsmith import LangSmithSandbox
client = SandboxClient()
ls_sandbox = client.create_sandbox(template_name="deepagents-deploy")
backend = LangSmithSandbox(sandbox=ls_sandbox)
result = backend.execute("python --version")
print(result.output)
AgentCore
pip install langchain-agentcore-codeinterpreter
uv add langchain-agentcore-codeinterpreter
from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter
from langchain_agentcore_codeinterpreter import AgentCoreSandbox
interpreter = CodeInterpreter(region="us-west-2")
interpreter.start()
backend = AgentCoreSandbox(interpreter=interpreter)
try:
result = backend.execute("python3 --version")
print(result.output)
finally:
interpreter.stop()
Daytona
pip install langchain-daytona
uv add langchain-daytona
from daytona import Daytona
from langchain_daytona import DaytonaSandbox
sandbox = Daytona().create()
backend = DaytonaSandbox(sandbox=sandbox)
result = backend.execute("python --version")
print(result.output)
E2B
pip install langchain-e2b
uv add langchain-e2b
from e2b import Sandbox
from langchain_e2b import E2BSandbox
e2b_sandbox = Sandbox.create()
sandbox = E2BSandbox(sandbox=e2b_sandbox)
try:
result = sandbox.execute("python --version")
print(result.output)
finally:
e2b_sandbox.kill()
Modal
from langchain_modal import ModalSandbox
app = modal.App.lookup("your-app")
modal_sandbox = modal.Sandbox.create(app=app)
backend = ModalSandbox(sandbox=modal_sandbox)
result = backend.execute("python --version")
print(result.output)
Runloop
pip install langchain-runloop
uv add langchain-runloop
from runloop_api_client import RunloopSDK
from langchain_runloop import RunloopSandbox
api_key = "..."
client = RunloopSDK(bearer_token=api_key)
devbox = client.devbox.create()
backend = RunloopSandbox(devbox=devbox)
try:
result = backend.execute("python --version")
print(result.output)
finally:
devbox.shutdown()
Vercel
pip install langchain-vercel-sandbox
uv add langchain-vercel-sandbox
from vercel.sandbox import Sandbox
from langchain_vercel_sandbox import VercelSandbox
sandbox = Sandbox.create()
backend = VercelSandbox(sandbox=sandbox)
try:
result = backend.execute("python --version")
print(result.output)
finally:
sandbox.stop()
例如:
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, 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:
LangSmith
from langsmith.sandbox import SandboxClient
from deepagents.backends.langsmith import LangSmithSandbox
client = SandboxClient()
ls_sandbox = client.create_sandbox(template_name="deepagents-deploy")
backend = LangSmithSandbox(sandbox=ls_sandbox)
backend.upload_files(
[
("/src/index.py", b"print('Hello')\n"),
("/pyproject.toml", b"[project]\nname = 'my-app'\n"),
]
)
AgentCore
pip install langchain-agentcore-codeinterpreter
uv add langchain-agentcore-codeinterpreter
from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter
from langchain_agentcore_codeinterpreter import AgentCoreSandbox
interpreter = CodeInterpreter(region="us-west-2")
interpreter.start()
backend = AgentCoreSandbox(interpreter=interpreter)
backend.upload_files(
[
("hello.py", b"print('Hello')\n"),
("data.csv", b"name,value\na,1\nb,2\n"),
]
)
Daytona
pip install langchain-daytona
uv add langchain-daytona
from daytona import Daytona
from langchain_daytona import DaytonaSandbox
sandbox = Daytona().create()
backend = DaytonaSandbox(sandbox=sandbox)
backend.upload_files(
[
("/src/index.py", b"print('Hello')\n"),
("/pyproject.toml", b"[project]\nname = 'my-app'\n"),
]
)
E2B
pip install langchain-e2b
uv add langchain-e2b
from e2b import Sandbox
from langchain_e2b import E2BSandbox
e2b_sandbox = Sandbox.create()
sandbox = E2BSandbox(sandbox=e2b_sandbox)
try:
sandbox.upload_files(
[
("/src/index.py", b"print('Hello')\n"),
("/pyproject.toml", b"[project]\nname = 'my-app'\n"),
]
)
finally:
e2b_sandbox.kill()
Modal
from langchain_modal import ModalSandbox
app = modal.App.lookup("your-app")
modal_sandbox = modal.Sandbox.create(app=app)
backend = ModalSandbox(sandbox=modal_sandbox)
backend.upload_files(
[
("/src/index.py", b"print('Hello')\n"),
("/pyproject.toml", b"[project]\nname = 'my-app'\n"),
]
)
Runloop
pip install langchain-runloop
uv add langchain-runloop
from runloop_api_client import RunloopSDK
from langchain_runloop import RunloopSandbox
api_key = "..."
client = RunloopSDK(bearer_token=api_key)
devbox = client.devbox.create()
backend = RunloopSandbox(devbox=devbox)
backend.upload_files(
[
("/src/index.py", b"print('Hello')\n"),
("/pyproject.toml", b"[project]\nname = 'my-app'\n"),
]
)
Vercel
pip install langchain-vercel-sandbox
uv add langchain-vercel-sandbox
from vercel.sandbox import Sandbox
from langchain_vercel_sandbox import VercelSandbox
sandbox = Sandbox.create()
backend = VercelSandbox(sandbox=sandbox)
backend.upload_files(
[
("/src/index.py", b"print('Hello')\n"),
("/pyproject.toml", b"[project]\nname = 'my-app'\n"),
]
)
检索生成物
使用 download_files() 在代理完成后从沙箱中检索文件:
LangSmith
from langsmith.sandbox import SandboxClient
from deepagents.backends.langsmith import LangSmithSandbox
client = SandboxClient()
ls_sandbox = client.create_sandbox(template_name="deepagents-deploy")
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}")
AgentCore
pip install langchain-agentcore-codeinterpreter
uv add langchain-agentcore-codeinterpreter
from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter
from langchain_agentcore_codeinterpreter import AgentCoreSandbox
interpreter = CodeInterpreter(region="us-west-2")
interpreter.start()
backend = AgentCoreSandbox(interpreter=interpreter)
results = backend.download_files(["hello.py"])
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}")
interpreter.stop()
Daytona
pip install langchain-daytona
uv add langchain-daytona
from daytona import Daytona
from langchain_daytona import DaytonaSandbox
sandbox = Daytona().create()
backend = DaytonaSandbox(sandbox=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}")
E2B
pip install langchain-e2b
uv add langchain-e2b
from e2b import Sandbox
from langchain_e2b import E2BSandbox
e2b_sandbox = Sandbox.create()
sandbox = E2BSandbox(sandbox=e2b_sandbox)
try:
results = sandbox.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}")
finally:
e2b_sandbox.kill()
Modal
from langchain_modal import ModalSandbox
app = modal.App.lookup("your-app")
modal_sandbox = modal.Sandbox.create(app=app)
backend = ModalSandbox(sandbox=modal_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}")
Runloop
pip install langchain-runloop
uv add langchain-runloop
from runloop_api_client import RunloopSDK
from langchain_runloop import RunloopSandbox
api_key = "..."
client = RunloopSDK(bearer_token=api_key)
devbox = client.devbox.create()
backend = RunloopSandbox(devbox=devbox)
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}")
Vercel
pip install langchain-vercel-sandbox
uv add langchain-vercel-sandbox
from vercel.sandbox import Sandbox
from langchain_vercel_sandbox import VercelSandbox
sandbox = Sandbox.create()
backend = VercelSandbox(sandbox=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}")
安全注意事项
沙箱将代码执行与您的主机系统隔离,但它们不能防止 **上下文注入**。攻击者如果控制了代理输入的一部分,可以指示其读取文件、运行命令或从沙箱中窃取数据。这使得沙箱内的凭证特别危险。
安全处理密钥
如果您的代理需要调用已认证的API或访问受保护的资源,您有两个选项:
- **将密钥保存在沙箱外部的工具中。** 在您的主机环境中运行工具(不是在沙箱内部)并在那里处理身份验证。代理按名称调用这些工具,但从不看到凭证。这是推荐的方法。
- **使用注入凭证的网络代理。** 某些沙箱提供商支持代理,这些代理拦截来自沙箱的传出HTTP请求并附加凭证(例如,
Authorization标头),然后转发。代理永远不会看到密钥——它只是向URL发出普通请求。这种方法尚未在提供商中广泛普及。
通用最佳实践
- - 在应用程序中对沙箱输出采取行动之前先进行审查
- - 不需要时阻止沙箱的网络访问
- - 使用 中间件 过滤或删除工具输出中的敏感模式
- - 将沙箱内生成的所有内容视为不受信任的输入