主题
为 Managed Deep Agents 添加沙箱
为一个托管 deep agent 配置一个隔离的文件系统和 shell。
沙箱为托管 deep agent 提供一个隔离的文件系统和 shell,用于处理文件、运行代码和执行命令。
NOTE
Managed Deep Agents 处于公开 beta 阶段,仅在 US 区域的 LangSmith Cloud 上可用。
把沙箱声明放在 sandbox/ 下。只有当你想预配一个快照时,才添加 sandbox/setup.sh:
text
my-agent/
agent.py
sandbox/
__init__.py
setup.sh # optional有关完整项目布局,请参阅项目结构。
配置沙箱
当 agent 需要写文件、运行代码或执行 shell 命令时,使用沙箱。
mda init 会搭建一个沙箱声明。Managed Deep Agents 仅在 sandbox/ 目录存在时启用沙箱。
mda init 不会创建 setup.sh。如果快照应当安装包、克隆一个树或以其他方式更改镜像,请你自己添加那个文件。
Managed Deep Agents 为此后端使用 LangSmith 沙箱。复用始终是每个持久线程一个沙箱。
用 define_sandbox 声明沙箱:
python
from managed_deepagents import define_sandbox
sandbox = define_sandbox(
idle_ttl_seconds=600,
default_timeout=600,
)| 选项 | 默认值 | 描述 |
|---|---|---|
idle_ttl_seconds | 600 | 沙箱及其内容被删除之前的不活动秒数。删除不可恢复。 |
default_timeout | 600 | 每条命令允许的秒数。 |
配置沙箱代理
沙箱代理会向匹配的出站请求注入标头,并控制沙箱能到达哪些目的地。代理在沙箱之外运行,因此沙箱代码可以调用经过身份验证的 API 而无需处理凭据。
例如,要从沙箱调用 OpenAI API,把 OPENAI_API_KEY 存入你的 LangSmith 工作区机密,并配置此代理规则:
python
from managed_deepagents import define_sandbox
sandbox = define_sandbox(
proxy_config={
"rules": [
{
"name": "openai-api",
"match_hosts": ["api.openai.com"],
"headers": [
{
"name": "Authorization",
"type": "workspace_secret",
"value": "Bearer {OPENAI_API_KEY}",
},
],
},
],
},
)有关配置选项和网络限制,参阅沙箱身份验证代理。
在代理标头中使用连接
在沙箱中使用连接来为 CLI 命令和 API 请求进行身份验证。
例如,要以当前用户身份从沙箱调用 GitHub API,先创建 github 连接,然后配置代理:
python
from managed_deepagents import bearer, connections, define_sandbox
github = connections.get("github", {"type": "user"})
sandbox = define_sandbox(
proxy_config={
"rules": [
{
"name": "github-api",
"match_hosts": ["api.github.com"],
"headers": [{"name": "Authorization", "value": bearer(github)}],
},
],
"access_control": {"allow_list": ["api.github.com"]},
},
)将一个连接引用用作标头值,或用 bearer(ref) 或 basic(username, ref) 格式化它。对于连接值,省略标头的 type。Managed Deep Agents 会把它设为 opaque。
预配快照
如果 sandbox/setup.sh 存在,mda deploy 和 mda dev 会运行该脚本一次,并把结果环境保存为一个快照。那次运行产生的修改,例如克隆的仓库和安装的包,会持久保存在快照中。新线程克隆那个快照,而非运行 setup.sh。在 setup.sh 更改之前快照会被复用,一旦更改它就重新构建。
该脚本以 bash -e 运行。非零退出会使快照以及那次部署或 mda dev 会话失败。LangSmith 不会把线上部署更新到失败的快照。任何先前成功的快照会继续服务。
bash
#!/usr/bin/env bash
set -euo pipefail
apt-get update && apt-get install -y jq
mkdir -p /workspace项目 .env 中被 deploy 转发的值,在 setup.sh 运行时作为环境变量可用,例如用于克隆私有仓库的令牌。克隆快照的线程沙箱不会继承那些变量。在 setup.sh 运行时不要把机密写入文件系统;磁盘上的任何东西都是每个线程镜像的一部分。
编辑 setup.sh 并重新部署,不会清除线上线程上的 /workspace。那些沙箱保留它们已有的文件。新线程会克隆新快照。
选择烘焙基座
若没有烘焙基座,LangSmith 的默认沙箱模板是起点。要从别的东西开始,请恰好设置以下之一:
| 选项 | 用途 |
|---|---|
snapshot_name | LangSmith 快照名称。允许带标签。 |
snapshot_id | LangSmith 快照 id。 |
docker_image | 已发布的 Docker 镜像。 |
python
from managed_deepagents import define_sandbox
sandbox = define_sandbox(
idle_ttl_seconds=600,
docker_image="python:3.12-slim",
)对于私有镜像,传入镜像和一个 registry。Managed Deep Agents 会在烘焙时创建或更新一个部署自有的 Host 注册表。只有变量名会被编译;凭据值不会进入构建或快照。
在 password_env 中命名密码:
python
from managed_deepagents import define_sandbox
sandbox = define_sandbox(
docker_image="ghcr.io/acme/agent-base:1",
registry={
"url": "ghcr.io",
"username": "octocat",
"password_env": "GHCR_TOKEN",
},
)把 GHCR_TOKEN 放在项目 .env 或进程环境中。烘焙之后,Managed Deep Agents 不会把那个值转发给正在运行的 Agent Server。
agent 如何使用沙箱
agent 使用内置的文件系统工具,例如 ls、read_file、write_file、edit_file、delete、glob 和 grep,并用 execute 运行 shell 命令。使用指令来指定 agent 应在何处工作以及不得修改什么。
从代码读写沙箱文件
自编工具和中间件通过 runtime.backend 访问沙箱文件系统。当你自己的代码需要一个文件时使用它,而不是提示 agent 替你获取。
NOTE
runtime.backend 需要 managed-deepagents>=0.8.0。
为 runtime 参数添加注解以接收类型化的接口:
python
from langchain.tools import tool
from managed_deepagents import ManagedDeepAgentRuntime
@tool(parse_docstring=True)
def write_report(summary: str, runtime: ManagedDeepAgentRuntime) -> str:
"""Write a summary to the sandbox and return its path.
Args:
summary: Report body to store.
"""
if runtime.backend is None:
raise RuntimeError("write_report requires a sandbox")
result = runtime.backend.write("/workspace/report.txt", summary)
if result.error:
raise RuntimeError(result.error)
return "/workspace/report.txt"每个操作都绑定到处理当前运行的那个线程的沙箱,因此两个线程读取 /workspace/report.txt 时各自看到自己的副本。后端惰性解析,且一个从不触碰它的工具永远不会预配一个沙箱。
可用操作
| 方法 | 目的 |
|---|---|
ls(path) | 列出一个目录。 |
read(file_path, offset, limit) | 读取文本,默认 2000 行。 |
write(file_path, content) | 写入文本,替换任何现有文件。 |
edit(file_path, old_string, new_string, replace_all) | 就地替换一个子串。 |
delete(file_path) | 移除一个文件。 |
grep(pattern, path, glob, max_count) | 搜索文件内容。 |
glob(pattern, path) | 匹配路径。 |
execute(command, timeout) | 运行一条 shell 命令。 |
upload_files(files) | 从 (path, bytes) 对写入原始字节。 |
download_files(paths) | 读取给定路径的原始字节。 |
每个方法都有一个以 a 为前缀的异步对应物,例如 aread、awrite 和 adownload_files。
参数和返回类型来自 Deep Agents 后端契约。参阅后端。
传输二进制文件
upload_files 和 download_files 传输原始字节,因此它们适合图像、归档,以及任何会被文本操作破坏的其他文件。Download 会为每个请求的路径返回字节:
python
import hashlib
from langchain.tools import tool
from managed_deepagents import ManagedDeepAgentRuntime
@tool(parse_docstring=True)
def checksum_file(file_path: str, runtime: ManagedDeepAgentRuntime) -> str:
"""Return the SHA-256 checksum of a sandbox file.
Args:
file_path: Absolute path inside the sandbox.
"""
if runtime.backend is None:
raise RuntimeError("checksum_file requires a sandbox")
result = runtime.backend.download_files([file_path])[0]
if result.error or result.content is None:
raise RuntimeError(result.error or "file_not_found")
return hashlib.sha256(result.content).hexdigest()Upload 接受路径和内容对,每个文件一对:
python
uploaded = runtime.backend.upload_files([("/workspace/logo.png", payload)])
if uploaded[0].error:
raise RuntimeError(uploaded[0].error)每个结果都带有 path 和 error,而下载还带有 content。失败时,error 是 file_not_found、permission_denied、is_directory 或 invalid_path 之一,且下载的 content 为空。检查 error,而不是假定传输成功。
限制
runtime.backend 仅涵盖该沙箱。它没有到 Context Hub 的路由,因此技能、指令和记忆无法通过它访问。
没有沙箱时,runtime.backend 为 None。在每次调用之前对它做判空保护,因为一个项目可以在工具发布之后移除 sandbox/。
delete、upload_files 和 download_files 取决于所安装的后端,并在其未实现时会抛出。根 glob 会返回一个错误而非预配一个沙箱。
禁用沙箱
删除 sandbox/ 目录以选择退出,例如对于一个只需要其提示、记忆和工具的 agent。
对于现有部署,用 mda delete 删除部署也会删除与之关联的托管沙箱、{deployment}--setup-* 配方快照,以及在存在时的部署自有注册表。
部署
Managed Deep Agents 负责沙箱命名、配方烘焙、复用、恢复和清理。每个持久线程获得它自己的沙箱,从当前配方快照克隆而来。有关平台级的生命周期详情,参阅沙箱。
何时使用沙箱
| 目标 | 使用 |
|---|---|
| 在隔离环境中写文件、运行代码或执行 shell 命令 | 沙箱 |
| 跨线程存储持久知识 | 记忆 |
| 无需文件系统的始终在线行为 | 指令 |
更多信息请参阅项目结构。