news 2026/10/2 23:29:14

拆一个范本:OpenHands skill 长什么样,从 headless terminal 到 LiteLLM 配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
拆一个范本:OpenHands skill 长什么样,从 headless terminal 到 LiteLLM 配置

1. 从一次终端任务说起:OpenHands skill 到底解决什么问题

如果你最近在折腾 Agent 编排,大概率会碰到一个尴尬:模型能选、工具能接,但真正跑起来的时候,每个 skill 的目录结构、启动方式、模型配置各写各的,换一个模型就得改一堆代码。OpenHands 这个 skill 之所以值得单独拆,是因为它把「模型无关」这件事做成了工程范本——背后接的是 LiteLLM,OpenAI、Anthropic、OpenRouter、DeepSeek、Ollama、vLLM 都能挂,而对外只暴露一个 headless terminal 调用入口。

先说清楚它是什么。OpenHands 本身是一个开源的软件工程 Agent 框架,而这个 skill 是把它包装成一个可被上层编排系统调用的标准单元。它能做的事很具体:接收一个任务描述,在隔离环境里自主读写文件、执行命令、跑测试,最后把结果以 JSON 形式吐回来。适合谁?适合那些已经在用 Claude Code 或 Codex 做原生开发、但需要「换模型对比效果」或者「多模型混跑」的团队。因为要 Claude 原生能力就走 claude-code,要 OpenAI 原生就走 codex,只有当你需要灵活切换 provider 时,OpenHands 这条链路才真正发挥价值。

我试过把同一个重构任务分别丢给三个 provider,OpenHands 的 headless 模式是唯一一个不需要改 skill 代码、只改环境变量就能切换的。这个特性决定了它的目录结构必须足够克制——配置文件、启动脚本、模型映射三者分离,谁都不越界。接下来我会把这份 skill 的骨架拆开,从目录结构到 headless 启动命令,再到 LiteLLM 的配置片段,最后用一次真实的终端任务验证整条链路。你照着抄,就能得到一个可复用的 skill 范本。

2. 目录骨架与 headless terminal 启动链路拆解

一个好 skill 的第一特征是「目录会说话」。OpenHands 这份的骨架大致长这样,我按职责分层列出来:

openhands-skill/ ├── SKILL.md # 能力声明与使用边界 ├── config/ │ ├── litellm.yaml # 模型路由配置 │ └── runtime.env # 运行时环境变量 ├── scripts/ │ ├── run_headless.sh # headless 启动入口 │ └── healthcheck.sh # 链路自检 └── workspace/ # 任务执行沙箱目录

注意SKILL.md的位置。它放在根目录,开头就写清楚「什么时候不该用这个 skill」——比如需要 Claude 原生工具链时应该走 claude-code,需要 OpenAI 原生函数调用时走 codex。这个细节很关键,好 skill 会主动帮你做选择题,而不是让你在报错之后才反应过来选错了。

config/目录承担模型接入的全部职责。litellm.yaml定义 provider 和模型映射,runtime.env存放 API Key 和 Base URL 这类敏感信息。两者分离的好处是:你可以把litellm.yaml提交到版本库做团队共享,而runtime.env只留在本地或密钥管理服务里。

scripts/run_headless.sh是整条链路的触发点。它的核心就是一行 headless 调用,把任务描述、工作目录、模型配置通过参数和环境变量传进去。workspace/则是沙箱,Agent 的所有文件操作都被限制在这个目录内,避免污染宿主机。

这里要强调 headless terminal 的意义。所谓 headless,就是没有交互式界面,Agent 完全靠命令行参数和标准输入输出完成任务。这对编排系统极其友好——上层只需要拼接一条命令、读取一段 JSON 输出,不需要处理 TTY 或伪终端。OpenHands 的 headless 模式通过--json参数把执行过程结构化,每一步的工具调用、文件变更、命令输出都能被上层解析。

链路顺序是这样的:编排层触发run_headless.sh→ 脚本加载runtime.env注入密钥 → LiteLLM 根据litellm.yaml路由到具体 provider → OpenHands 在workspace/内执行任务 → 结果以 JSON 返回标准输出。整条链路没有隐藏状态,每一步都可观测、可复现。这也是为什么我说它适合当范本:你换任何模型,改的只是litellm.yaml里的一行,其余部分纹丝不动。

3. 可复制的 LiteLLM 配置与 headless 启动命令

这一节直接给可复制的片段。先看config/litellm.yaml,这是模型路由的核心:

model_list: - model_name: openhands-default litellm_params: model: anthropic/claude-sonnet-4-20250514 api_base: https://taotoken.net/api api_key: os.environ/TAOTOKEN_API_KEY - model_name: openhands-fast litellm_params: model: openai/gpt-4o-mini api_base: https://taotoken.net/api api_key: os.environ/TAOTOKEN_API_KEY - model_name: openhands-local litellm_params: model: ollama/qwen2.5-coder:7b api_base: http://127.0.0.1:11434 litellm_settings: drop_params: true set_verbose: false

三个model_name对应三种场景:openhands-default走主力模型做复杂重构,openhands-fast走轻量模型做快速补全,openhands-local走本地 Ollama 做离线验证。api_base统一指向https://taotoken.net/api,密钥通过os.environ/TAOTOKEN_API_KEY从环境变量读取,不硬编码在文件里。

再看config/runtime.env:

export TAOTOKEN_API_KEY="sk-你的密钥" export OPENHANDS_MODEL="openhands-default" export OPENHANDS_WORKSPACE="./workspace" export LITELLM_CONFIG="./config/litellm.yaml"

然后是scripts/run_headless.sh,这是 headless 启动的完整入口:

#!/usr/bin/env bash set -euo pipefail source ./config/runtime.env TASK_DESC="${1:?用法: run_headless.sh \"任务描述\"}" openhands --headless \ --json \ --override-with-envs \ --exit-without-confirmation \ --model "$OPENHANDS_MODEL" \ --workspace "$OPENHANDS_WORKSPACE" \ --config "$LITELLM_CONFIG" \ --task "$TASK_DESC"

四个关键参数逐个说。--headless关闭交互界面,--json让输出结构化,--override-with-envs允许环境变量覆盖配置里的默认值,--exit-without-confirmation让 Agent 执行完自动退出而不是等待人工确认。这四个参数组合起来,才构成一个真正可被编排系统调用的无头单元。

如果你用的是 Cline MCP 或 Codex 的auth.json体系,三件套要写全:Base URL 填https://taotoken.net/api,Key 填你的密钥,Model ID 填openhands-default或你在litellm.yaml里定义的任意model_name。三者缺一,链路就会在鉴权或路由阶段断掉。

4. 验证一次终端任务:从触发到 JSON 返回

配置写完,必须验证。我拿一个最小任务来跑:让 Agent 在workspace/里创建一个 Python 文件并运行它。

触发命令:

chmod +x scripts/run_headless.sh ./scripts/run_headless.sh "在 workspace 下创建 hello.py,内容为打印当前目录下所有 .py 文件,然后运行它"

预期返回的 JSON 结构大致如下(截取关键字段):

{ "status": "completed", "model": "openhands-default", "steps": [ { "action": "write_file", "path": "workspace/hello.py", "result": "success" }, { "action": "run_command", "command": "python workspace/hello.py", "stdout": "workspace/hello.py\n", "exit_code": 0 } ], "elapsed_seconds": 12.4 }

看到status: completed和exit_code: 0,说明整条链路通了:编排层触发脚本 → LiteLLM 路由到openhands-default→ OpenHands 在沙箱内写文件、跑命令 → 结果结构化返回。

如果你想验证模型切换是否生效,把runtime.env里的OPENHANDS_MODEL改成openhands-fast再跑一次,返回 JSON 里的model字段会变成openhands-fast,而 skill 代码一行没动。这就是模型无关的价值。

再补一个自检脚本scripts/healthcheck.sh,用来快速确认 LiteLLM 配置是否可加载:

#!/usr/bin/env bash set -euo pipefail source ./config/runtime.env python -c " import yaml, os cfg = yaml.safe_load(open(os.environ['LITELLM_CONFIG'])) for m in cfg['model_list']: print(m['model_name'], '->', m['litellm_params']['model']) "

跑通这个自检,再去触发真实任务,能省掉大量「配置写错但报错信息看不懂」的时间。

5. 常见报错排查:401、local proxy failed 与 reading choices

链路跑不通时,报错信息往往指向几个固定位置。我按实际踩过的坑逐个对照。

401 Unauthorized。最常见的原因是TAOTOKEN_API_KEY没被正确注入。检查顺序:先确认runtime.env里export了密钥,再确认run_headless.sh里source了该文件,最后确认litellm.yaml里写的是os.environ/TAOTOKEN_API_KEY而不是硬编码的空字符串。三者任一断掉都会 401。如果用的是 Codex 的auth.json,确认 Base URL 和 Key 字段没有多余空格。

local proxy failed。这个报错通常出现在api_base指向本地服务但服务没起来的时候。如果你配的是openhands-local走 Ollama,先确认ollama serve在跑、端口11434可访问。如果配的是远程api_base,检查网络连通性和 URL 是否漏了/api后缀。https://taotoken.net/api是完整路径,少写/api会路由失败。

reading choices 相关报错。这类错误一般出现在响应解析阶段,根因是 provider 返回的结构和 LiteLLM 预期不一致。排查方向:确认litellm.yaml里的model字段格式正确,比如anthropic/claude-sonnet-4-20250514这种provider/model的写法不能少 provider 前缀。另外drop_params: true建议保留,它能过滤掉某些 provider 不支持的参数,减少解析冲突。

OAuth 相关报错。如果你在 skill 里集成了需要 OAuth 的工具,确认 token 刷新逻辑没有和 headless 模式冲突。headless 环境下没有浏览器回调,OAuth 必须走 device code 或预置 token 的方式。把 token 放在runtime.env里注入,不要依赖交互式授权。

任务卡住不返回。检查--exit-without-confirmation是否生效。如果 Agent 在等待人工确认,headless 模式下会一直挂起。另外确认workspace/目录存在且有写权限,沙箱目录不可写会导致 Agent 反复重试。

排查的通用思路是:先跑healthcheck.sh确认配置可加载,再用最小任务触发一次,看 JSON 里status和steps停在哪一步。报错信息里的关键词——401、proxy、choices、OAuth——基本能定位到具体环节。

6. 把这条链路接进你的工作流

拆完这份范本,你会发现它的可复用性来自三个分离:配置与代码分离、模型与逻辑分离、执行与观测分离。你要做的不是照抄每一行,而是把这套结构迁移到自己的 skill 里。

具体动作:先把litellm.yaml的model_list换成你实际要用的 provider,Base URL 统一填https://taotoken.net/api,密钥走环境变量。然后确认run_headless.sh的四个核心参数齐全,尤其是--json和--exit-without-confirmation,这两个决定了它能不能被编排系统无头调用。最后用healthcheck.sh加一次最小任务验证,看到status: completed再接入生产流程。

如果你需要长期跑编码类 Agent 任务,建议把模型路由和密钥管理拆到独立配置里,方便团队共享和轮换。密钥申请和接入文档可以从 API Keys 页面入手,模型对话能力可以在模型对话页面试跑,长期编码和 Agent 编排则适合用 Coding Plan 来承载。链路通了之后,换模型就是改一行配置的事,这才是这份范本真正值钱的地方。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 23:29:11

EMC设计实战:从共模电流路径到辐射整改与RJ45防护

做硬件这些年,我越来越觉得EMC是个"平时没人管,测试时教你做人"的科目。前阵子帮朋友救火,一块工控板做辐射发射测试,120MHz附近超标6个dB,换了好几种滤波方案都没用,最后发现是机箱出线孔和屏蔽…

作者头像 李华
网站建设 2026/10/2 23:27:05

HMC575LP有源倍频器工程应用:本振扩展、杂散抑制与设计要点

1. 从一颗小芯片说起:为什么倍频器在射频链路里这么重要做射频收发系统的人,几乎都绕不开频率合成这个话题。很多场景下,我们需要把本振信号从低频端搬到高频端,但又不想用一个额外的高频振荡器——成本高、相位噪声难控、电路面积…

作者头像 李华
网站建设 2026/10/2 23:26:47

金额存储选型:Long还是BigDecimal?精度、单位与工程实践全解析

这个题目我太有发言权了。老读者都知道,我过去几年一直在做交易结算类的系统,几乎每个迭代都要跟金额打交道。组里新来的同事几乎都问过同一个问题:金额到底用Long还是BigDecimal?面试的时候我也常拿这个当考点,十个人…

作者头像 李华
网站建设 2026/10/2 23:23:42

26届课程论文怎么写?实测一学期,这些坑和捷径都告诉你

课程论文看着篇幅不长,真动笔才发现处处是坎:选题拿不准、文献理不清、初稿逻辑散、改三轮还被导师说表述不严谨。这学期我前后用了四款辅助工具,把踩过的坑和真正有用的功能一次说清。 passbug官网直达入口:https://passbug.cn/ …

作者头像 李华
网站建设 2026/10/2 23:20:43

DeepSeek Harness桌面端安装配置与skill部署全指南

1. 桌面端来了,为什么这件事比想象中重要DeepSeek Harness 出官方桌面端这件事,我第一反应不是"终于不用开浏览器了",而是"这套工作流终于可以脱离浏览器标签页活下去了"。如果你之前用过 DSH(社区里对 DeepS…

作者头像 李华