用途:当有人需要搭建 OpenEnv 服务器、把已有环境移植到 OpenEnv,或为某个环境添加 OpenEnv 变体时使用本技能。目标是构建环境的 OpenEnv 版本,目标版本为 OpenEnv >= 0.4.1(openenv 包,原 openenv-core[core])。
概念:OpenEnv 是一个通过 MCP(Model Context Protocol)形态暴露工具的 HTTP 服务器。运行时是 FastAPI,工具是被 FastMCP 装饰的函数。客户端通过 list_tools() 发现工具(底层是 /step 上的 list-tools 动作),并通过 call_tool(name, **args) 调用。当用户已有共享领域模块(<domain>.py)并想要 OpenEnv 变体时,不要把领域逻辑复制进框架目录,而应包装它。
原型(按任务选择):
- 纯 Python 游戏:确定性、单个 @mcp.tool、纯文本观测,奖励从轨迹外部计算。
- 有状态沙箱:E2B / 浏览器 / 数据库,多个工具修改会话状态,每个会话一个 MCPEnvironment。
- 视觉 / 计算机使用:截图以 MCP 图像内容块(fastmcp.utilities.types.Image)返回,动作面有 19 个工具,模仿 Anthropic 的 computer_20251124,可选在 /web 挂载自定义 Gradio UI。
推荐文件布局(具体路径由用户决定,规范形态如下):<env_dir>/openenv/ 下包含 pyproject.toml(openenv + e2b-* + fastmcp + uvicorn + gradio)、init.py、models.py(Pydantic State / 类型化 action / observation 模型)、Dockerfile(基于 ghcr.io/huggingface/openenv-base 的多阶段构建)、openenv.yaml(spec_version 1、name、runtime、app、port)、server/ 目录(init.py、app.py 中 create_app(EnvCls, CallToolAction, CallToolObservation, env_name=...)、<env>_environment.py 中带 @mcp.tool 方法的 MCPEnvironment 子类)、rollout.py(MCPToolClient 驱动服务器,自动发现工具)、README.md(一页;若部署到 Spaces 则带 HF frontmatter)。
实现顺序(连续一趟完成):
- Pydantic 状态模型 models.py:继承 openenv.core.env_server.types.State,加入每回合会修改的字段(step_count、last_output、sandbox/session id 等)。视觉环境加入 last_screenshot_b64;不要在 state 里存巨大 blob,除非确需,否则用 observation 的 metadata。
- MCPEnvironment:server/<name>_environment.py 中定义 class MyEnv(MCPEnvironment),仅在真正会话隔离时设 SUPPORTS_CONCURRENT_SESSIONS = True;init 中初始化环境侧状态,创建 FastMCP('my_env'),用 @mcp.tool 定义函数,再 super().init(mcp)。关键契约:双导入模式(server/ 内用 try: from ..models import X; except ImportError: from models import X,仓库内相对导入可用,Docker 中扁平导入可用,对 e2b_sandbox.py 等同级模块同理);工具方法是 init 内被 @mcp.tool 装饰的函数,闭包访问 self 读写状态,不要试图把 @mcp.tool 放在实例方法上,因为 FastMCP 内省的是自由函数;图像使用 fastmcp.utilities.types.Image,返回 Image(data=png_bytes, format='png'),模型会收到 MCP 图像内容块,若以文本返回 base64 字符串模型会看不见;生命周期钩子 reset(seed=None, episode_id=None, **kwargs) 返回 Observation,step(action, timeout_s=None, **kwargs) 由 MCPEnvironment 继承用于工具分发,仅在需要前后钩子(如步数计数递增、终止信号处理)时覆盖。
- FastAPI 应用 server/app.py:从 openenv.core.env_server.http_server 导入 create_app,从 openenv.core.env_server.mcp_types 导入 CallToolAction、CallToolObservation,按双导入模式导入环境类与可选 Gradio UI;定义自定义 gradio builder(如需要);设置 os.environ['ENABLE_WEB_INTERFACE']='true';调用 create_app(MyEnv, CallToolAction, CallToolObservation, env_name='my_env', max_concurrent_envs=int(os.getenv('MAX_CONCURRENT_ENVS','4')), gradio_builder=_custom_gradio_builder)。注意传类而非实例化环境。
- 自定义 Gradio UI(可选,计算机使用类环境受益):server/gradio_ui.py 定义 my_ui_builder(env_factory) 返回 gr.Blocks,挂载在 /web(在 HF Space frontmatter 中设 base_path: /web)。计算机使用环境的典型模式包含一个 iframe 面板显示 E2B 流 URL 与文本控件并列,但任意 gr.Blocks 布局均可。
- rollout.py:使用 openenv.core.mcp_client.MCPToolClient,发现工具而非硬编码:with MCPToolClient(base_url=ENV_URL).sync() as env: env.reset(); tools = env.list_tools();转换为 OpenAI tool schemas,驱动 LLM,调用 env.call_tool(name, **args)。注意:对返回图像的工具,env.call_tool 只保留 result.data(图像返回时为 None);应使用 env.step(CallToolAction(tool_name='screenshot', arguments={})) 获取完整结果 dict,再读取 obs.result['content'][0]['data'] 获取 b64 图像。多模态模型(Qwen3-VL、GPT-4o)每轮把最新截图作为图像块放入用户消息。
- Dockerfile:多阶段构建,FROM ghcr.io/huggingface/openenv-base:latest(官方基础镜像已含 FastAPI、MCP、Gradio);uv sync 两次(先 no-install-project,再带项目)以便缓存友好;通过 /health 做健康检查;暴露端口 8000。HF Spaces 的规范 app_port 是 8000(不是 7860),README frontmatter 中设 base_path: /web 使 Gradio 挂在该前缀下。
- HF Space README frontmatter 示例:title、emoji、colorFrom、colorTo、sdk: docker、pinned: false、app_port: 8000、base_path: /web、tags: [openenv, your-domain]、short_description。
验证门槛(宣布完成前四项都要通过):仓库内导入(PYTHONPATH=envs uv run python -c 'from envs.<name>.openenv.server.<name>_environment import <Cls>');本地服务器(uv run uvicorn server.app:app --port 8000,curl /health 返回 {'status':'healthy'},/list_environments 返回环境名);工具发现(MCPToolClient.list_tools() 返回预期列表);rollout(MAX_TURNS=3 uv run python rollout.py 无错误运行)。
常见坑:POST /list_tools 出现 KeyError: 'tools'(OpenEnv 不直接暴露 /list_tools,MCPToolClient 底层用 /step 加 list-tools 动作,始终通过客户端发现);截图返回 None(env.call_tool('screenshot') 只返回结构化 data 字段,应使用 env.step(CallToolAction(...)) 并读取 obs.result['content']);address already in use(本地开发迭代常见,换一个 --port 即可);Docker 中 ModuleNotFoundError 但本地正常(server/app.py 或 server/<name>_environment.py 缺少双导入模式)。
参考资料:references/architecture.md(完整架构深入,需要时使用);官方文档:huggingface/OpenEnv 源码仓库、OpenEnv 文档(环境构建器 + Core API)、Build your first environment、HF org(示例部署)。上游在其仓库的 .claude/skills/generate-openenv-env/ 中提供了 generate-openenv-env 技能,行为不明确时可作为第二意见。