WEBVTT
Kind: captions
Language: zh

00:00:01.800 --> 00:00:03.466
大家好我是小木头

00:00:03.600 --> 00:00:04.366
上一期视频

00:00:04.366 --> 00:00:07.000
我们用 OpenAI 官方托管的沙箱环境

00:00:07.066 --> 00:00:09.000
把 Agents API 跑通了

00:00:09.000 --> 00:00:10.133
创建了 session

00:00:10.400 --> 00:00:11.333
跑了命令

00:00:11.533 --> 00:00:12.700
下载了 artifact

00:00:12.766 --> 00:00:15.000
在任务执行中也加载了 Skill

00:00:15.733 --> 00:00:17.466
官方托管的沙箱环境

00:00:17.466 --> 00:00:18.900
只是执行环境的一种

00:00:19.066 --> 00:00:20.533
Agents API 还支持另一种

00:00:20.533 --> 00:00:22.200
环境类型叫 self_hosted

00:00:23.666 --> 00:00:25.066
执行器可以跑在咱们

00:00:25.066 --> 00:00:26.466
自己的基础设施里

00:00:26.533 --> 00:00:27.966
OpenAI 只负责调度

00:00:28.266 --> 00:00:30.166
这一期我分享一个刚刚

00:00:30.166 --> 00:00:32.400
开源的小项目叫 apple-sandbox

00:00:32.933 --> 00:00:35.200
它用 Apple 官方的 container 工具

00:00:35.200 --> 00:00:37.366
在 Mac 上给每个 session 起一个

00:00:37.366 --> 00:00:39.266
独立的轻量级的虚拟机

00:00:39.466 --> 00:00:42.066
把 Agents API 的执行器跑在这里面

00:00:42.366 --> 00:00:43.466
不需要云账号

00:00:43.466 --> 00:00:44.733
不需要公网地址

00:00:44.733 --> 00:00:45.900
也不需要 Docker

00:00:46.533 --> 00:00:49.133
项目已经做过端到端的验证

00:00:49.266 --> 00:00:52.166
在测试中能够看到真实的 session 的创建

00:00:52.300 --> 00:00:53.566
真实的任务执行

00:00:53.766 --> 00:00:55.333
结果文件也能够直接

00:00:55.333 --> 00:00:57.066
落到咱们本机的磁盘上

00:00:58.400 --> 00:01:00.500
今天的内容咱们分三块吧

00:01:00.500 --> 00:01:01.966
先介绍这个项目是什么

00:01:02.000 --> 00:01:02.866
架构是什么

00:01:02.900 --> 00:01:04.733
然后重点说说它和 Agents

00:01:04.800 --> 00:01:06.133
API 之间的连接方式

00:01:06.366 --> 00:01:08.700
尤其是没有公网的地址的情况下

00:01:08.700 --> 00:01:10.766
它是怎么和 OpenAI 协同工作的

00:01:10.800 --> 00:01:12.966
最后用发布版本从安装到

00:01:12.966 --> 00:01:14.933
跑任务完整的演示一遍

00:01:17.666 --> 00:01:19.733
apple-sandbox 是一个用 Go 语言

00:01:19.733 --> 00:01:21.066
编写的命令行工具

00:01:22.066 --> 00:01:23.300
没有常驻进程

00:01:23.400 --> 00:01:25.066
目前呢也没有守护程序

00:01:25.133 --> 00:01:26.133
它只做一件事情

00:01:26.133 --> 00:01:29.866
把 OpenAI 的 codex exec-server 这个执行器

00:01:29.866 --> 00:01:31.666
跑进一个隔离的虚拟机里

00:01:32.000 --> 00:01:35.066
底层用的是 Apple 去年开源的 container 项目

00:01:35.100 --> 00:01:37.500
它和 Docker 最大的区别是隔离级别

00:01:37.866 --> 00:01:40.800
Docker 上所有容器共享一个 Linux 内核

00:01:41.000 --> 00:01:42.766
Apple container 是每个容器单独

00:01:42.766 --> 00:01:44.900
起一个轻量级的 Linux 虚拟机

00:01:45.100 --> 00:01:48.400
基于 macOS 自带的 Virtualization.framework

00:01:48.866 --> 00:01:50.866
所以在 apple-sandbox 里面

00:01:50.866 --> 00:01:53.266
一个 session 就是一个 OCI 容器

00:01:53.400 --> 00:01:55.266
也就是一台独立的虚拟机

00:01:55.500 --> 00:01:58.100
agent 在里面装软件跑脚本

00:01:58.100 --> 00:01:59.900
都不会离开这台虚拟机

00:02:00.600 --> 00:02:02.900
从下往上来看看整个结构

00:02:02.900 --> 00:02:04.266
目前呢是 4 层

00:02:04.300 --> 00:02:06.466
最底下是 Apple Silicon 的 Mac

00:02:06.666 --> 00:02:08.100
装好 container 工具

00:02:08.700 --> 00:02:10.600
执行 container system start 把

00:02:10.600 --> 00:02:11.800
它的服务启动起来

00:02:12.066 --> 00:02:14.333
往上一层是 Apple container 本身

00:02:14.500 --> 00:02:17.466
负责从 OCI 镜像启动虚拟机

00:02:17.600 --> 00:02:19.766
再往上是执行器的镜像

00:02:20.166 --> 00:02:21.233
基于 node 22 的精简版

00:02:22.900 --> 00:02:26.166
安装了 CA 证书和 codex 这个 npm 包

00:02:26.300 --> 00:02:29.966
入口脚本里面执行了 codex exec-server

00:02:30.500 --> 00:02:33.666
最上面才是 apple-sandbox 这个 CLI

00:02:33.733 --> 00:02:35.900
它负责创建工作目录

00:02:36.100 --> 00:02:37.200
注入环境变量

00:02:37.200 --> 00:02:38.400
调用 container run

00:02:38.966 --> 00:02:40.000
然后呢就退出

00:02:40.000 --> 00:02:41.666
把虚拟机留在后台跑

00:02:42.800 --> 00:02:45.666
有一个设计方面的细节需要单独聊聊

00:02:45.933 --> 00:02:48.666
每个 session 在本机的 sandboxes

00:02:48.666 --> 00:02:50.166
目录下有一个自己的子目录

00:02:50.300 --> 00:02:53.200
直接挂载成虚拟机里的 /workspace

00:02:53.600 --> 00:02:54.800
所以文件进出不需要

00:02:54.800 --> 00:02:56.400
任何上传下载接口

00:02:56.533 --> 00:02:58.266
往那个目录里放一个文件

00:02:58.333 --> 00:02:59.866
虚拟机里马上就能看到

00:03:00.266 --> 00:03:01.800
agent 写出来的结果

00:03:01.800 --> 00:03:03.666
本机磁盘上也能马上读到

00:03:04.133 --> 00:03:06.266
虚拟机是随时可以销毁的

00:03:06.366 --> 00:03:08.366
磁盘上这个目录才是真正的状态

00:03:08.900 --> 00:03:10.000
命令一共 8 个

00:03:10.200 --> 00:03:11.533
doctor 检查环境

00:03:11.666 --> 00:03:13.333
build 构建执行器镜像

00:03:13.533 --> 00:03:14.900
run 启动一个 session

00:03:15.133 --> 00:03:16.066
logs 看日志

00:03:16.533 --> 00:03:17.666
ls 列出 session

00:03:18.133 --> 00:03:20.933
stop 和 rm 分别是停止以及删除

00:03:21.800 --> 00:03:24.666
可以用 prune 命令按时间批量

00:03:24.666 --> 00:03:26.133
地清理已经停掉的 session

00:03:30.366 --> 00:03:33.200
再来看看它在整个链路里的位置

00:03:35.166 --> 00:03:35.733
第一步

00:03:35.733 --> 00:03:39.133
你的应用用普通的 API key 来调用 Agents API

00:03:39.700 --> 00:03:42.166
创建一个 self_hosted 类型的 session

00:03:42.766 --> 00:03:44.333
返回里除了 session ID

00:03:44.333 --> 00:03:45.533
还有两个关键字段

00:03:45.900 --> 00:03:49.133
environment.id 和一个远端的地址 remote_url

00:03:49.200 --> 00:03:51.133
这两个值是给执行器用的

00:03:52.100 --> 00:03:54.900
第二步在你的 OpenAI 平台上单独

00:03:54.900 --> 00:03:56.800
建一个受限的 API key

00:03:57.200 --> 00:04:00.333
拿普通的 API key 去注册执行器

00:04:00.733 --> 00:04:02.666
API 会直接返回 403

00:04:03.200 --> 00:04:04.533
通常会看到这么一个错误

00:04:04.533 --> 00:04:08.000
说缺少 api.agents.environments.connect 这个 scope

00:04:08.866 --> 00:04:11.200
第三步是把这个受限的 API key

00:04:11.200 --> 00:04:13.766
放进环境变量 OPENAI_EXECUTOR_API_KEY

00:04:14.533 --> 00:04:16.166
执行 apple-sandbox run

00:04:16.566 --> 00:04:20.000
传入 environment.id 和 remote_url

00:04:20.466 --> 00:04:22.166
CLI 会创建工作目录

00:04:22.333 --> 00:04:24.733
把这三个值作为环境变量注入

00:04:25.400 --> 00:04:27.766
调用 container run 启动一台虚拟机

00:04:28.200 --> 00:04:30.533
虚拟机里的入口脚本把受限

00:04:30.533 --> 00:04:33.500
的 API key 转成 codex exec-server

00:04:33.500 --> 00:04:36.200
自己要求的变量名 CODEX_API_KEY

00:04:36.200 --> 00:04:38.133
然后呢启动 exec-server

00:04:39.933 --> 00:04:42.700
第四步 exec-server 用这个 API key 主动向

00:04:42.700 --> 00:04:45.266
remote_url 发起一个 WebSocket 连接

00:04:45.366 --> 00:04:46.166
完成注册

00:04:46.800 --> 00:04:49.500
OpenAI 那边把 session 标记成活跃

00:04:49.866 --> 00:04:52.966
之后每一轮任务都是 OpenAI 那边发命令

00:04:53.100 --> 00:04:55.500
虚拟机在 /workspace 里执行

00:04:55.533 --> 00:04:56.766
结果呢再回传

00:04:57.566 --> 00:04:59.600
你的应用通过 Agents API 提交任务

00:04:59.700 --> 00:05:00.933
订阅事件流

00:05:01.066 --> 00:05:04.766
看到的和 OpenAI 托管的 sandbox 完全一样

00:05:05.000 --> 00:05:06.066
整个过程里

00:05:06.133 --> 00:05:08.900
apple-sandbox 从头到尾没有和 OpenAI 通信

00:05:08.966 --> 00:05:10.933
它只负责把三个参数变成

00:05:10.933 --> 00:05:12.933
一台跑着执行器的虚拟机

00:05:12.933 --> 00:05:14.300
然后呢就退出了

00:05:17.400 --> 00:05:20.333
这段是这期里面我想再聊聊的一部分

00:05:20.466 --> 00:05:22.166
首先看托管的 sandbox

00:05:22.300 --> 00:05:23.200
什么都不用管

00:05:23.200 --> 00:05:24.566
OpenAI 自己起环境

00:05:25.400 --> 00:05:27.700
如果大家看过 Cloudflare 官方

00:05:27.700 --> 00:05:29.866
给 Agents API 写的参考实现

00:05:30.300 --> 00:05:32.533
它是云端部署的典型形态

00:05:32.566 --> 00:05:33.866
你部署一个 worker

00:05:34.000 --> 00:05:35.333
公网地址有了

00:05:35.600 --> 00:05:37.766
OpenAI 通过带签名的 webhook

00:05:37.800 --> 00:05:39.600
把 session 的事件推送

00:05:42.166 --> 00:05:43.533
有任务要执行

00:05:43.800 --> 00:05:44.733
状态空闲

00:05:45.000 --> 00:05:46.100
执行失败等等

00:05:46.100 --> 00:05:48.533
这些呢都是事件会推送过来的

00:05:48.866 --> 00:05:51.900
而 worker 收到事件再去启动或唤醒容器

00:05:52.000 --> 00:05:55.666
容器里的执行器向 OpenAI 建立 WebSocket 连接

00:05:56.133 --> 00:05:57.766
也就是说在那套方案里面

00:05:57.766 --> 00:06:00.366
webhook 和 WebSocket 各管一件事

00:06:00.933 --> 00:06:03.366
WebSocket 是执行器和 OpenAI 之间

00:06:03.366 --> 00:06:05.533
真正的交换命令和结果

00:06:06.866 --> 00:06:08.800
永远都是从你这边主动出站

00:06:08.800 --> 00:06:10.733
而 webhook 只解决一个问题

00:06:10.866 --> 00:06:12.133
OpenAI 什么时候需要算力

00:06:12.133 --> 00:06:14.066
怎么通知你把容器启动起来

00:06:14.966 --> 00:06:17.800
而 apple-sandbox 跑在一台个人的 Mac 电脑上

00:06:17.800 --> 00:06:18.966
没有公网地址

00:06:19.000 --> 00:06:20.566
也就没法给 OpenAI 提供

00:06:20.566 --> 00:06:22.266
一个接收 webhook 的地址

00:06:22.266 --> 00:06:23.266
那怎么办呢

00:06:23.866 --> 00:06:26.166
答案其实也在 OpenAI 自己文档里

00:06:26.700 --> 00:06:27.966
self_hosted 环境

00:06:27.966 --> 00:06:30.266
也就咱们这种自己运行的沙箱环境

00:06:30.466 --> 00:06:31.933
有两种同样都可以

00:06:31.933 --> 00:06:33.600
认为是正式的启动方式

00:06:33.866 --> 00:06:35.666
一种叫 webhook-managed

00:06:36.000 --> 00:06:38.766
就是刚才咱们谈到的像 Cloudflare 那种

00:06:38.900 --> 00:06:40.366
OpenAI 决定什么时候要算力

00:06:40.666 --> 00:06:41.766
推送事件给你

00:06:42.533 --> 00:06:44.300
另一种呢叫 application-managed

00:06:44.500 --> 00:06:46.966
你的应用创建 session 的那一刻

00:06:47.066 --> 00:06:48.733
就已经拿到了环境的 ID

00:06:48.733 --> 00:06:51.066
也就是 environment.id 和 remote_url

00:06:51.733 --> 00:06:53.900
直接同步把算力呢启动起来

00:06:53.900 --> 00:06:55.533
不需要等待 OpenAI 的通知

00:06:56.533 --> 00:07:00.000
Cloudflare、Vercel、Modal 的集成文档都把这两种

00:07:00.566 --> 00:07:01.933
并列成为选项

00:07:01.966 --> 00:07:03.300
而不是主备关系

00:07:04.300 --> 00:07:06.700
apple-sandbox 实现的正是 application-managed

00:07:07.066 --> 00:07:07.866
创建 session

00:07:07.900 --> 00:07:08.933
拿到两个参数

00:07:08.933 --> 00:07:11.000
立即执行 apple-sandbox run

00:07:11.066 --> 00:07:12.500
三步呢是在一起的

00:07:13.000 --> 00:07:15.733
webhook 那条路解决的是规模化

00:07:15.733 --> 00:07:17.700
场景下按需启动算力的问题

00:07:18.066 --> 00:07:19.100
一个开发者呢

00:07:19.100 --> 00:07:20.400
在自己的 Mac 上跑

00:07:20.400 --> 00:07:23.100
其实呢并没有很具体的这方面的问题

00:07:23.900 --> 00:07:25.266
真正的数据通道

00:07:25.600 --> 00:07:26.600
两种方式是一样的

00:07:26.600 --> 00:07:28.333
都是执行器主动出站

00:07:28.866 --> 00:07:30.533
发起一个 WebSocket 连接

00:07:30.600 --> 00:07:31.866
连接断了自己重连

00:07:32.133 --> 00:07:34.866
OpenAI 从来不需要反向地连你的机器

00:07:35.066 --> 00:07:36.533
所以少了公网地址

00:07:36.566 --> 00:07:38.766
少的只是提前收通知这件事

00:07:39.166 --> 00:07:41.733
那少了通知还剩一件事情要自己处理

00:07:41.966 --> 00:07:43.133
session 什么时候结束

00:07:43.300 --> 00:07:44.700
虚拟机什么时候该销毁

00:07:45.400 --> 00:07:47.400
apple-sandbox 没有去轮询 OpenAI

00:07:47.400 --> 00:07:48.366
也并不需要

00:07:48.900 --> 00:07:50.300
exec-server 有个特性

00:07:50.500 --> 00:07:52.266
连接临时断开它会重连

00:07:52.266 --> 00:07:54.366
但 OpenAI 彻底关闭 session 之后

00:07:54.366 --> 00:07:55.333
它就退出了

00:07:55.500 --> 00:07:56.300
容器随之呢

00:07:56.300 --> 00:07:57.700
就进入了 stopped 状态

00:07:57.900 --> 00:07:59.733
所以 prune 命令只看一件事

00:07:59.766 --> 00:08:01.000
哪些容器已经停了

00:08:01.000 --> 00:08:01.800
停了多久

00:08:02.066 --> 00:08:03.800
prune --older-than 1h

00:08:04.000 --> 00:08:05.866
把停掉超过一小时的清理掉

00:08:06.300 --> 00:08:08.400
手动跑或者挂一个 cron 都行

00:08:08.500 --> 00:08:10.566
它永远呢不会碰正在运行的容器

00:08:11.533 --> 00:08:14.133
当然了目前 apple-sandbox 还处于非常早期

00:08:14.133 --> 00:08:15.500
在后续的开发中还会

00:08:15.500 --> 00:08:16.966
逐渐完善这方面的功能

00:08:16.966 --> 00:08:18.166
使它越来越健壮

00:08:20.400 --> 00:08:22.400
把上面的机制串成状态图

00:08:22.400 --> 00:08:24.266
一个 session 在沙箱里经历

00:08:24.266 --> 00:08:25.700
的状态大致是这样的

00:08:26.200 --> 00:08:27.600
执行 apple-sandbox run

00:08:27.733 --> 00:08:28.766
容器被创建出来

00:08:28.766 --> 00:08:30.133
这是 Created 状态

00:08:30.500 --> 00:08:32.466
入口脚本启动 exec-server

00:08:32.466 --> 00:08:33.733
向 OpenAI 注册

00:08:33.800 --> 00:08:35.900
进入 Registering 状态

00:08:36.000 --> 00:08:37.933
注册呢有两个分支

00:08:38.400 --> 00:08:40.333
remote_url 或者 key 不对

00:08:40.333 --> 00:08:41.766
exec-server 立即就拒绝

00:08:41.766 --> 00:08:42.800
进入 Rejected

00:08:43.000 --> 00:08:44.300
容器随即就停掉

00:08:46.866 --> 00:08:47.466
错误信息呢

00:08:47.466 --> 00:08:49.400
可以在 apple-sandbox logs 里看到

00:08:49.533 --> 00:08:51.533
注册通过进入连接状态 Connected

00:08:51.800 --> 00:08:53.533
之后 OpenAI 每下发一条

00:08:53.533 --> 00:08:54.900
命令就进入 Executing

00:08:55.133 --> 00:08:56.300
执行完回到 Connected

00:08:56.700 --> 00:08:58.366
中间 WebSocket 掉线

00:08:58.366 --> 00:08:59.500
进入 Reconnecting

00:09:00.100 --> 00:09:01.500
exec-server 自己重连

00:09:01.533 --> 00:09:02.933
apple-sandbox 并不参与

00:09:03.266 --> 00:09:04.666
OpenAI 彻底关闭 session

00:09:05.066 --> 00:09:06.400
exec-server 退出

00:09:06.400 --> 00:09:08.166
容器呢就进入 stopped 状态

00:09:08.366 --> 00:09:10.866
最后通过 rm 或者 prune 把它删掉

00:09:10.933 --> 00:09:12.666
最终达到了 Removed 状态

00:09:13.400 --> 00:09:14.666
从 CLI 的角度看

00:09:14.666 --> 00:09:16.100
ls 只报两种状态

00:09:16.133 --> 00:09:17.066
running 和 stopped

00:09:17.400 --> 00:09:19.966
中间那些状态全都在虚拟机里面发生

00:09:21.333 --> 00:09:23.500
下面我们用发布版本完整跑一遍

00:09:23.566 --> 00:09:26.966
项目在 GitHub 上 sugarforever/apple-sandbox

00:09:27.200 --> 00:09:28.566
在发布版本这里

00:09:28.566 --> 00:09:31.600
releases 有一个 darwin_arm64 的压缩包

00:09:31.666 --> 00:09:32.533
只有这个平台

00:09:32.533 --> 00:09:33.733
因为 Apple container 呢

00:09:33.733 --> 00:09:35.600
只能在 Apple Silicon 的 Mac 上跑

00:09:36.300 --> 00:09:38.700
前置条件只有一个 brew install --cask container

00:09:38.733 --> 00:09:40.700
装好 Apple 的 container 工具

00:09:40.966 --> 00:09:43.366
然后按照 README 里的一条命令安装

00:09:43.533 --> 00:09:46.666
从 GitHub Releases 拉取最新的压缩包

00:09:46.700 --> 00:09:48.100
把二进制解压到用户

00:09:48.100 --> 00:09:50.066
目录下的 .local/bin

00:09:50.200 --> 00:09:51.300
并不需要 sudo

00:09:51.766 --> 00:09:53.866
执行器镜像的 Dockerfile 和入口

00:09:53.866 --> 00:09:55.600
脚本已经嵌在了二进制里

00:09:55.600 --> 00:09:57.500
所以不需要克隆代码仓库

00:09:57.500 --> 00:09:59.533
在任何一个空目录里都能操作

00:10:01.333 --> 00:10:03.500
装好后先跑 apple-sandbox doctor

00:10:03.700 --> 00:10:04.766
做 5 项检查

00:10:04.900 --> 00:10:06.500
包含了像 Apple Silicon

00:10:06.800 --> 00:10:08.800
Hypervisor 支持 container 命令

00:10:08.866 --> 00:10:10.066
container 的服务状态

00:10:10.066 --> 00:10:10.900
磁盘空间

00:10:10.966 --> 00:10:12.566
全部 PASS 就可以了

00:10:12.900 --> 00:10:14.566
如果服务状态检查失败

00:10:14.566 --> 00:10:17.366
那么尝试运行 container system start 启动一下

00:10:17.366 --> 00:10:18.266
再跑一次

00:10:18.666 --> 00:10:22.000
然后 apple-sandbox build 来构建执行器的镜像

00:10:22.100 --> 00:10:23.966
这一步要拉取 node 镜像

00:10:24.133 --> 00:10:25.966
装 CA 证书装 codex

00:10:26.133 --> 00:10:27.700
第一次呢会花一点时间

00:10:29.466 --> 00:10:30.466
镜像准备好了

00:10:30.466 --> 00:10:31.700
现在呢创建一个 session

00:10:32.200 --> 00:10:35.266
向 /v1/agents/sessions 发一个 POST 请求

00:10:35.366 --> 00:10:37.966
请求的头带上 agents=v1 这个

00:10:37.966 --> 00:10:40.666
beta 标记以及 OpenAI 的 API key

00:10:40.966 --> 00:10:43.200
请求体里面带上 agent 和 session

00:10:43.666 --> 00:10:45.933
模型我选择了 gpt-6-astra

00:10:46.100 --> 00:10:48.700
系统指令是让它在 /workspace 里工作

00:10:48.700 --> 00:10:50.766
每一步跑了什么命令都展示出来

00:10:50.800 --> 00:10:53.466
生成的文件在结束前自己检查一遍

00:10:53.866 --> 00:10:55.933
environment 指定类型是 self_hosted

00:10:55.966 --> 00:10:57.700
工作目录是 /workspace

00:10:58.466 --> 00:11:00.266
API 响应应该是 201

00:11:00.400 --> 00:11:01.466
来看看三个字段

00:11:01.466 --> 00:11:02.600
一个呢是 ID

00:11:03.100 --> 00:11:05.266
ID 是 sess 开头的一个 session ID

00:11:06.666 --> 00:11:08.500
另外一个是 environment.id

00:11:08.666 --> 00:11:10.766
还包含一个 remote_url

00:11:10.966 --> 00:11:13.366
它是 api.openai.com 下面

00:11:13.366 --> 00:11:14.866
一个 connect 路径的地址

00:11:15.966 --> 00:11:18.000
这两个值原样地传给沙箱就行

00:11:18.000 --> 00:11:19.000
并不需要改动

00:11:19.066 --> 00:11:21.133
把响应存到 session.json

00:11:21.200 --> 00:11:23.666
后面呢用三个变量咱们把它取出来

00:11:24.466 --> 00:11:25.566
受限的 API key

00:11:25.566 --> 00:11:27.066
我已经在 OpenAI 平台上

00:11:27.066 --> 00:11:28.700
的 agents 部分创建好了

00:11:28.766 --> 00:11:30.066
把它放在本机环境

00:11:30.066 --> 00:11:32.466
变量 OPENAI_EXECUTOR_API_KEY 里面

00:11:33.500 --> 00:11:34.733
现在启动沙箱

00:11:34.900 --> 00:11:36.166
运行 apple-sandbox run

00:11:36.733 --> 00:11:38.533
给它 session ID、environment.id

00:11:38.533 --> 00:11:39.666
和 remote_url

00:11:39.733 --> 00:11:41.800
命令马上返回 session 的名字

00:11:41.800 --> 00:11:42.600
容器的 ID

00:11:42.600 --> 00:11:43.866
本机的工作目录

00:11:43.900 --> 00:11:45.733
还有查看日志的命令

00:11:46.700 --> 00:11:47.766
apple-sandbox ls

00:11:47.766 --> 00:11:49.900
状态呢应该能看到它是 running

00:11:50.600 --> 00:11:52.466
apple-sandbox logs -f

00:11:52.500 --> 00:11:53.566
可以跟一下日志

00:11:53.900 --> 00:11:55.900
能够看到 exec-server 启动

00:11:55.900 --> 00:11:57.366
向远程的 URL

00:11:57.366 --> 00:11:59.100
也就是 remote_url 发起了注册

00:11:59.900 --> 00:12:02.500
顺便验证一下它真的是一台虚拟机

00:12:02.733 --> 00:12:04.300
container exec 进去

00:12:04.300 --> 00:12:05.766
执行 uname -a

00:12:05.766 --> 00:12:07.466
看到的是 Linux 内核

00:12:07.466 --> 00:12:08.900
跑在了一台 Mac 上

00:12:09.400 --> 00:12:10.600
给它一个任务吧

00:12:11.100 --> 00:12:13.300
先在本机 sandboxes 目录下

00:12:13.533 --> 00:12:16.333
这个 session 的子目录里放一个 CSV 文件

00:12:16.766 --> 00:12:17.766
内容很简单

00:12:17.766 --> 00:12:18.766
4 行数据

00:12:18.800 --> 00:12:20.200
item 和 amount 两列

00:12:20.200 --> 00:12:21.666
不需要任何上传接口

00:12:21.666 --> 00:12:22.566
放进目录里

00:12:22.566 --> 00:12:24.533
实际上就放进了虚拟机

00:12:24.533 --> 00:12:26.133
里的 /workspace 目录

00:12:26.666 --> 00:12:27.766
顺便说一句吧

00:12:28.533 --> 00:12:31.333
OpenAI 托管的 sandbox 创建 session 的时候呢

00:12:31.333 --> 00:12:33.166
可以用 environment.files 这个

00:12:33.166 --> 00:12:35.966
字段内联地把文件带进去

00:12:36.066 --> 00:12:38.333
同样的字段在 self_hosted 上

00:12:38.333 --> 00:12:40.400
OpenAI 会返回 400

00:12:40.733 --> 00:12:43.066
说 environment.files 是个未知参数

00:12:43.500 --> 00:12:45.800
因此 self_hosted 类型的文件

00:12:45.800 --> 00:12:47.533
进出只能靠你自己去挂载

00:12:48.266 --> 00:12:49.200
API 并不支持

00:12:49.866 --> 00:12:51.133
然后就提交任务吧

00:12:51.533 --> 00:12:53.733
POST 到 session 的 events 接口

00:12:53.766 --> 00:12:54.700
事件类型呢

00:12:54.700 --> 00:12:56.733
是 agent.session.input.message

00:12:57.133 --> 00:13:01.300
内容是用 Python 读 /workspace/amounts.csv

00:13:01.466 --> 00:13:03.366
验证 amount 列是数字

00:13:03.500 --> 00:13:04.733
算行数和总数

00:13:04.733 --> 00:13:06.766
把结果按固定格式写到

00:13:07.166 --> 00:13:09.166
/workspace/outputs/summary.json

00:13:10.100 --> 00:13:11.966
写完读回来验证报告结果

00:13:12.866 --> 00:13:14.866
应该能够看到返回是 202

00:13:14.966 --> 00:13:17.400
session 状态从 idle 变成了 in_progress

00:13:17.700 --> 00:13:18.366
这时候呢

00:13:18.366 --> 00:13:19.666
可以看两个地方

00:13:19.766 --> 00:13:21.766
一个是 apple-sandbox 的日志

00:13:22.333 --> 00:13:24.000
通过 apple-sandbox logs 命令

00:13:24.133 --> 00:13:25.766
能看到执行器收到命令

00:13:25.766 --> 00:13:26.566
执行命令

00:13:26.566 --> 00:13:28.966
另一边是 Agents API 的事件流

00:13:29.500 --> 00:13:30.533
等它跑完

00:13:30.533 --> 00:13:33.800
直接用 cat 命令查看本机的

00:13:33.800 --> 00:13:36.133
sandboxes 目录下的 summary.json 文件

00:13:36.466 --> 00:13:37.266
应该有 4 行

00:13:37.266 --> 00:13:38.466
总和呢 75

00:13:38.566 --> 00:13:40.466
verified 是标记为了 true

00:13:40.666 --> 00:13:42.600
这是 Python 在虚拟机里执行

00:13:42.700 --> 00:13:45.266
通过挂载写到本机磁盘上的文件

00:13:47.200 --> 00:13:48.300
没有 artifact 下载

00:13:48.300 --> 00:13:49.966
也没有轮询任何的接口

00:13:50.133 --> 00:13:51.533
self_hosted 的产物

00:13:51.600 --> 00:13:55.066
这些 artifact 不会发布到 OpenAI 的 Artifacts 接口

00:13:55.066 --> 00:13:56.900
文件呢就会在你自己的目录里

00:13:58.766 --> 00:14:00.266
跑完了三步清理

00:14:00.333 --> 00:14:02.100
apple-sandbox stop 停掉容器

00:14:02.266 --> 00:14:03.966
apple-sandbox rm 删掉容器

00:14:03.966 --> 00:14:07.100
再用 DELETE 请求把 OpenAI 那边的 session 删掉

00:14:07.300 --> 00:14:09.500
如果不想一个一个地记 session ID

00:14:10.200 --> 00:14:11.766
apple-sandbox prune

00:14:11.766 --> 00:14:12.566
--older

00:14:12.766 --> 00:14:14.100
-than 1h

00:14:14.700 --> 00:14:16.333
先加一个 --dry-run 的选项

00:14:16.333 --> 00:14:17.533
看看会删哪些

00:14:17.766 --> 00:14:19.466
再真正地去删掉它们

00:14:21.133 --> 00:14:23.666
这里呢有几点值得注意的 README 呢

00:14:23.666 --> 00:14:24.800
也已经写了

00:14:24.800 --> 00:14:27.466
第一个出站网络没有做白名单

00:14:27.566 --> 00:14:29.166
Apple container 的网络只有两种

00:14:29.166 --> 00:14:31.100
完全内网或者完全开放

00:14:31.300 --> 00:14:32.966
没有按域名放行的机制

00:14:33.100 --> 00:14:35.666
因此现在执行器是全开放出站的

00:14:35.666 --> 00:14:38.933
已经确认它需要访问 api.openai.com

00:14:39.000 --> 00:14:40.966
注册之后还需不需要别的地址呢

00:14:40.966 --> 00:14:41.900
还没有验证

00:14:41.966 --> 00:14:44.266
所以不建议拿它跑不受信任的任务

00:14:44.300 --> 00:14:46.800
等加了代理或者白名单那一层

00:14:46.800 --> 00:14:47.733
咱们再说吧

00:14:48.166 --> 00:14:49.466
第二点是只有 CLI

00:14:49.466 --> 00:14:50.766
目前呢还没有 SDK

00:14:51.000 --> 00:14:52.666
TypeScript 或者 Python 的封装

00:14:52.666 --> 00:14:54.700
其实呢就是包一层子进程调用

00:14:54.700 --> 00:14:56.700
后面呢可能我们也会继续地添加

00:14:56.766 --> 00:14:57.800
感兴趣的朋友呢也

00:14:57.800 --> 00:14:59.166
可以来自己封装一下

00:14:59.500 --> 00:15:02.266
第三 prune 判断 session 结束靠的是推断

00:15:02.400 --> 00:15:04.400
容器停了就代表 session 结束了

00:15:04.400 --> 00:15:06.533
这条推断依赖于 exec-server

00:15:06.800 --> 00:15:09.600
在 OpenAI 彻底关闭连接之后会自己退出

00:15:09.733 --> 00:15:10.566
目前文档呢

00:15:10.566 --> 00:15:11.666
是这么描述的

00:15:12.133 --> 00:15:13.900
端到端测试当中也没有反例

00:15:14.500 --> 00:15:16.866
但连接中到停止这一步的转换

00:15:16.900 --> 00:15:18.800
还没有被单独地去观测过

00:15:20.000 --> 00:15:22.133
这一期我们把 Agents API 的 self_hosted

00:15:22.133 --> 00:15:24.366
环境接到一台 Mac 电脑上

00:15:24.866 --> 00:15:27.133
Apple container 提供虚拟机级别的隔离

00:15:27.366 --> 00:15:29.800
执行器主动出站连接 OpenAI

00:15:29.800 --> 00:15:31.900
本机目录直接就当做工作区

00:15:32.066 --> 00:15:33.200
没有公网地址

00:15:33.300 --> 00:15:36.733
靠的是 application-managed 正式的启动方式

00:15:36.900 --> 00:15:38.733
缺的只是提前收到通知

00:15:39.700 --> 00:15:41.766
用 prune 命令我们做了补全

00:15:42.000 --> 00:15:43.333
项目呢开源在 GitHub

00:15:43.333 --> 00:15:44.300
欢迎大家试用

00:15:44.300 --> 00:15:46.500
也欢迎提 issue 欢迎 PR

00:15:46.566 --> 00:15:48.200
如果这期视频对你有帮助

00:15:48.266 --> 00:15:48.900
欢迎关注

00:15:48.900 --> 00:15:50.200
也欢迎一键三连吧

00:15:50.200 --> 00:15:51.700
那我们就下期再见

