用 OpenAI Agents SDK 在 Modal 沙箱中运行智能体

讲清 OpenAI Agents SDK 的沙箱执行模式,演示如何结合 Modal 创建隔离工作区,让智能体读取文件、执行命令、修改代码并返回结果。

阅读时长: 7 分钟
共 3499字
作者: eimoon.com

用 OpenAI Agents SDK 在 Modal 沙箱中运行智能体

OpenAI Agents SDK 的新一代沙箱工作流,把智能体的编排层与实际执行环境拆开了。

应用本身负责智能体逻辑、模型调用和决策;真正的文件读写、命令执行、代码运行,则放在隔离的沙箱工作区里完成。这样可以避免把所有内容塞进同一个提示词循环中,也让智能体具备更真实的工程操作能力。

这种结构适合以下场景:

  • 检查项目目录和源码
  • 创建、修改、删除文件
  • 执行 shell 命令
  • 运行测试并生成产物
  • 将工作结果返回给主应用

下面用一个完整示例演示:如何结合 OpenAI Agents SDK 与 Modal Sandboxes,构建一个能在隔离环境中工作的 Python 智能体应用。

Agents SDK 的沙箱能力有什么变化

当前这套机制的重点,在于把“推理”和“执行”解耦。

主要能力包括:

  • 原生支持在隔离环境中运行智能体
  • 通过 Manifest 定义智能体可访问的文件、目录和输出
  • 使用 SandboxAgent 让智能体绑定真实工作区
  • 使用 SandboxRunConfig 控制沙箱运行方式
  • 提供类似 Codex 的工具能力,用于文件编辑、shell 命令执行和项目检查
  • 支持 MCP,可连接外部工具与服务
  • 支持 Skills 与 AGENTS.md,用于补充项目级指令
  • 支持多个沙箱提供商,包括 Modal、E2B、Cloudflare、Daytona、Blaxel、Runloop 和 Vercel

这类能力的价值不在于“让模型回答更多问题”,而在于让回答建立在真实文件系统和执行结果之上,而不是只依赖提示词上下文。

示例目标

示例项目实现一个简单的支持工单分流服务。应用会:

  1. 创建一个沙箱工作区
  2. 向工作区写入几个项目文件
  3. 启动一个 gpt-5.4-mini 智能体
  4. 让智能体先检查沙箱中的文件,再给出回答

环境准备

先安装依赖:

pip install "openai-agents[modal]" modal

需要准备两个账户:

  • OpenAI 账户,需要可用 API 额度,并且账户已验证,可访问最新支持模型
  • Modal 账户,免费额度足够完成示例测试

接着配置 OpenAI API Key。

macOS 或 Linux:

export OPENAI_API_KEY="your_openai_api_key"

Windows PowerShell:

$env:OPENAI_API_KEY="your_openai_api_key"

然后完成本地 Modal 登录:

modal setup

执行后会打开浏览器,完成登录并授权 token。认证成功后,Modal 会把凭据写入本地环境。

定义沙箱工作区

创建 main.py,先写入需要的导入:

import asyncio

from agents import ModelSettings, Runner
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.sandbox.entries import File
from agents.extensions.sandbox import ModalSandboxClient, ModalSandboxClientOptions

然后定义一个沙箱工作区。这里放入一个小型支持工单分流项目,包含:

  • README.md
  • src/app.py
  • docs/release-checks.md
manifest = Manifest(
    entries={
        "README.md": File(
            content=(
                b"# Support Ticket Triage\n\n"
                b"Small service that labels customer tickets by urgency and team.\n"
            )
        ),
        "src/app.py": File(
            content=(
                b"def route_ticket(subject: str, customer_tier: str) -> dict:\n"
                b"    urgent = customer_tier == \"enterprise\" or \"outage\" in subject.lower()\n"
                b"    return {\n"
                b"        \"priority\": \"high\" if urgent else \"normal\",\n"
                b"        \"team\": \"support-ops\" if urgent else \"customer-care\",\n"
                b"    }\n"
            )
        ),
        "docs/release-checks.md": File(
            content=(
                b"# Release Checks\n\n"
                b"- Confirm routing rules match the current support escalation policy.\n"
            )
        ),
    }
)

Manifest 用来声明沙箱里存在哪些文件。这样智能体面对的是一个真实项目结构,而不是提示词中一段抽象描述。

在这个例子中,智能体可以检查:

  • 工单路由逻辑
  • 项目说明
  • 发布检查项

如果需要,也可以直接在沙箱中修改这些文件。

创建沙箱智能体

工作区定义好之后,创建绑定沙箱的智能体:

agent = SandboxAgent(
    name="Modal Sandbox Assistant",
    model="gpt-5.4-mini",
    instructions=(
        "You are a coding assistant reviewing a small production service. "
        "Inspect the sandbox workspace before answering. "
        "Keep the answer short and practical."
    ),
    default_manifest=manifest,
    model_settings=ModelSettings(tool_choice="required"),
)

这里几个参数的作用很直接:

参数 作用
name 智能体名称
model="gpt-5.4-mini" 使用响应更快的模型
instructions 明确要求先检查沙箱工作区再回答
default_manifest=manifest 将智能体绑定到前面定义的工作区
tool_choice="required" 强制优先使用工具,而不是只靠模型记忆作答

tool_choice="required" 很关键。没有这个设置时,模型可能直接根据提示词猜测答案;加上之后,更容易触发对真实文件的读取和检查。

创建 Modal 沙箱客户端

下一步是创建 Modal 客户端和配置项:

client = ModalSandboxClient()
options = ModalSandboxClientOptions(
    app_name="openai-agents-modal-demo",
    workspace_persistence="tar",
)

这里的含义如下:

  • ModalSandboxClient():指定使用 Modal 作为沙箱提供商
  • app_name="openai-agents-modal-demo":给 Modal 应用一个清晰名称,便于在控制台查看
  • workspace_persistence="tar":定义工作区文件的打包与持久化方式

启动沙箱会话

先根据 manifest 创建沙箱,再启动它:

sandbox = await client.create(
    manifest=manifest,
    options=options,
)

await sandbox.start()

client.create() 会基于 Manifest 初始化一个新的 Modal 沙箱,文件结构与前面定义的工作区一致。

await sandbox.start() 则真正启动这个隔离环境。启动完成后,智能体就可以在里面读文件、执行命令和处理项目内容。

在沙箱中运行智能体

沙箱启动后,通过 SandboxRunConfig 把当前会话传给 Agents SDK:

result = await Runner.run(
    agent,
    (
        "Explain what this service does and name one production check "
        "before release. Keep it under 3 sentences."
    ),
    run_config=RunConfig(
        sandbox=SandboxRunConfig(session=sandbox),
        workflow_name="Modal sandbox example",
    ),
)

print(result.final_output)

这里的执行路径很清晰:

  • 模型负责理解任务和组织回答
  • 沙箱负责提供真实工作区
  • 智能体在沙箱里检查文件后给出结论

这个提示词要求它说明服务用途,并给出一个上线前的生产检查项。由于工作区中已经包含了路由逻辑和发布检查文档,回答会建立在这些文件内容之上。

清理沙箱

任务结束后,应该及时删除沙箱,避免留下空闲会话继续占用资源:

await client.aclose(sandbox)

更稳妥的做法是把清理逻辑放进 finally 块。即使智能体执行失败,资源也能被回收。

完整示例代码

下面是完整脚本:

import asyncio

from agents import ModelSettings, Runner
from agents.extensions.sandbox import ModalSandboxClient, ModalSandboxClientOptions
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.sandbox.entries import File


async def main():
    manifest = Manifest(
        entries={
            "README.md": File(
                content=(
                    b"# Support Ticket Triage\n\n"
                    b"Small service that labels customer tickets by urgency and team.\n"
                )
            ),
            "src/app.py": File(
                content=(
                    b"def route_ticket(subject: str, customer_tier: str) -> dict:\n"
                    b"    urgent = customer_tier == \"enterprise\" or \"outage\" in subject.lower()\n"
                    b"    return {\n"
                    b"        \"priority\": \"high\" if urgent else \"normal\",\n"
                    b"        \"team\": \"support-ops\" if urgent else \"customer-care\",\n"
                    b"    }\n"
                )
            ),
            "docs/release-checks.md": File(
                content=(
                    b"# Release Checks\n\n"
                    b"- Confirm routing rules match the current support escalation policy.\n"
                )
            ),
        }
    )

    agent = SandboxAgent(
        name="Modal Sandbox Assistant",
        model="gpt-5.4-mini",
        instructions=(
            "You are a coding assistant reviewing a small production service. "
            "Inspect the sandbox workspace before answering. "
            "Keep the answer short and practical."
        ),
        default_manifest=manifest,
        model_settings=ModelSettings(tool_choice="required"),
    )

    client = ModalSandboxClient()
    options = ModalSandboxClientOptions(
        app_name="openai-agents-modal-demo",
        workspace_persistence="tar",
    )

    sandbox = await client.create(
        manifest=manifest,
        options=options,
    )

    await sandbox.start()

    try:
        result = await Runner.run(
            agent,
            (
                "Explain what this service does and name one production check "
                "before release. Keep it under 3 sentences."
            ),
            run_config=RunConfig(
                sandbox=SandboxRunConfig(session=sandbox),
                workflow_name="Modal sandbox example",
            ),
        )

        print(result.final_output)

    finally:
        await sandbox.aclose()

if __name__ == "__main__":
    asyncio.run(main())

这个脚本的结构可以概括为四步:

  1. 定义工作区
  2. 创建并启动沙箱
  3. 在沙箱中运行智能体
  4. 无论成功失败都清理会话

本地运行测试

main.py 所在目录执行:

python main.py

如果配置正确,脚本会:

  1. 创建 Modal 沙箱
  2. manifest 加载到沙箱
  3. 让 OpenAI 智能体基于该工作区运行
  4. 打印结果
  5. 清理沙箱

预期输出类似下面这样:

This service triages customer support tickets by assigning urgency and routing them to the right team. One production check before release is to confirm the routing rules still match the current support escalation policy.

运行通常需要几秒钟。执行期间,可以在 Modal 控制台中打开 openai-agents-modal-demo 这个应用并查看日志,用来确认沙箱是否被成功创建、启动、调用和清理。

做成可交互的 Web 应用

前面的脚本是一次性执行,适合验证链路是否打通;如果需要连续对话、追问、改文件和跑测试,更适合加一个简单前端。

这里可以使用 Gradio。

先安装依赖:

pip install gradio

然后创建 app.py,把同样的 OpenAI Agents + Modal Sandbox 逻辑包装成聊天界面。这个版本会在可能的情况下复用沙箱会话,避免每条消息都重新创建一个全新环境。

启动应用:

python app.py

终端会输出本地地址:

* Running on local URL:  http://127.0.0.1:7860
* To create a public link, set share=True in launch().

在浏览器打开该地址后,可以直接通过聊天界面让智能体执行以下任务:

  • 检查项目
  • 解释服务用途
  • 创建新文件
  • 修改已有文件
  • 在 Modal 沙箱中运行测试

这种模式更接近真实开发流程,因为同一个会话里可以持续积累上下文和文件变更。

一个更完整的执行案例

在交互界面中,先让智能体解释项目服务职责,通常几秒钟内就能返回基于沙箱文件的回答。

继续要求它创建一个新的路由辅助文件时,执行时间会明显变长,因为它不仅要生成代码,还要:

  • 修改沙箱工作区
  • 新增测试文件
  • 执行 pytest
  • 根据测试结果确认修改是否有效

实际执行中,智能体创建了:

  • src/routing_rules.py
  • tests/test_routing_rules.py

然后运行 pytest,6 个测试全部通过。这说明新增辅助模块生效,同时已有工单路由逻辑没有被破坏。

这类场景体现了沙箱模式的真实价值:模型不只是“建议怎么改”,而是能在隔离环境里实际完成修改并验证结果。

适用范围与局限

这种工作流适合以下任务:

  • 代码审查
  • 代码修复
  • 测试生成
  • 项目检查
  • 文档更新
  • 产物生成

但也有几个边界需要提前认识。

1. 交互式应用的复杂度高于单次脚本

单次脚本很容易搭建,交互式应用则要处理更多问题:

  • 会话复用
  • 状态管理
  • 沙箱生命周期
  • 超时与重试
  • 错误反馈

2. 沙箱超时需要根据任务调整

执行简单检查通常很快;涉及多文件修改、测试运行、依赖安装时,默认超时可能不够。

实际操作中,文件创建和测试步骤可能出现超时,沙箱超时时间需要从 300 秒提高到 600 秒,才能完成完整流程。

3. 日志可见性仍然不够强

等待智能体执行时,最大的调试障碍通常不是模型错误,而是缺少足够细的过程日志。

即使查看 Modal 日志,也未必总能清楚看出当前阶段到底是在:

  • 检查文件
  • 编辑代码
  • 运行测试
  • 等待模型
  • 等待沙箱响应

如果执行链路更长,缺少细粒度日志会直接影响排障效率。

常见问题

编排层和沙箱执行环境有什么区别

编排层运行在 Python 应用中,负责智能体逻辑、模型调用和决策。沙箱是隔离执行环境,负责文件读写、shell 命令和代码执行。

两者分离后,不可信或不可预测的代码只在沙箱内运行,不会直接影响主应用。

必须使用 Modal 吗

不是。Modal 只是受支持的提供商之一。

OpenAI Agents SDK 还支持:

  • Modal
  • E2B
  • Cloudflare
  • Daytona
  • Blaxel
  • Runloop
  • Vercel

更换提供商时,通常只需要替换对应的 client 类,例如把 ModalSandboxClient 换成 E2BSandboxClient,其余智能体逻辑可以基本保持不变。

Manifest 的作用是什么

Manifest 用于定义沙箱工作区里的文件和目录结构。

没有它时,智能体只能依赖提示词上下文;有了它,智能体面对的是一个可检查、可编辑、可推理的真实项目,因此回答通常更可靠。

这和 ChatGPT 的 Code Interpreter 是同一回事吗

不是。

Code Interpreter 是面向终端用户的内置功能。Agents SDK 的沙箱能力是面向开发者的框架,允许自行接入执行环境,例如 Modal 或 E2B,并由应用自行管理工作区、文件和会话生命周期。

关于

关注我获取更多资讯

月球基地博客公众号二维码,扫码关注获取更多 AI 与编程资讯
📢 公众号
月球基地博客作者个人微信二维码,扫码交流 AI 与编程话题
💬 个人号
使用 Hugo 构建
主题 StackJimmy 设计