OpenHands 实战:TaoToken 跑通 Terminal-Bench 的 Bash 系统维护任务

发布时间:2026/9/18 11:45:29
OpenHands 实战:TaoToken 跑通 Terminal-Bench 的 Bash 系统维护任务 告别海外账号与网络限制稳定直连全球优质大模型限时半价接入中。 点击领取海量免费额度1. 任务目标Terminal-Bench 类 Bash 系统维护在 OpenHands 里怎么验Terminal-Bench 这类基准把“Bash 系统维护”拆成一个个可自动判分的终端任务改配置、写脚本、排障、设定时任务每一步都由隐藏测试验证结果而不看 Agent 有没有“说到”。这次我选了一个贴近真实运维的场景让 OpenHands 在一个一次性 Ubuntu 容器里完成磁盘监控脚本的编写、权限修正和 crontab 注册。模型供应商走 TaoToken官网建 Key 的入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end OpenHands 的 Base URL 填 https://taotoken.net/api 。先说结论任务总体通过隐藏测试 6 项全过Agent 没有碰容器外任何路径。这个任务之所以难不是“写脚本”本身难而是 Agent 必须在看不到完整需求边界的情况下做对三件事第一脚本里的阈值判断不能写死成自己的测试值第二清理 /tmp 旧文件时必须排除 .keep 文件第三crontab 条目不能覆盖容器里可能已存在的其他任务。前两点考验模型对自然语言约束的理解第三点考验 Agent 有没有先读取现有 crontab 再合并写入的习惯。OpenHands 的执行轨迹会把每一步命令和输出都留在 Sandbox 里我可以事后逐条核对不需要靠模型自我汇报。1.1 Sandbox 隔离与任务描述我先把复现环境说清楚宿主机是 Ubuntu 22.04Docker 26OpenHands 用官方镜像启动OpenHands 自己会再拉一个执行沙箱所有命令都在沙箱容器里运行。生产机不参与读者想复现也请用同样的容器隔离方式不要让 AI 工具直接连自己的开发机或服务器。日志、crontab、/tmp 下的测试文件全部局限于容器内部跑完销毁即可。丢给 Agent 的 Prompt 长这样工作目录 /root。请完成一个系统维护脚本并注册定时任务只允许修改当前容器内部状态。创建 /usr/local/bin/disk-monitor.sh内容要求用 df -P / 读取根分区使用率百分比若大于 80向 /var/log/disk-monitor.log 追加一行 ALERT 日志否则追加 INFO 日志日志格式均为 YYYY-MM-DD HH:MM:SS LEVEL message每次执行还要清理 /tmp 下超过 7 天未修改的普通文件但任何名为 .keep 的文件必须跳过若有删除在日志行末追加 CLEANED: 加空格加被删文件路径列表。脚本需set -euo pipefail权限设为 0755。为 root 用户配置 crontab每 10 分钟执行一次该脚本如果 crontab 已有内容必须保留原条目再追加。完成后主动运行crontab -l和ls -l验证。隐藏测试我在 3.5 节给出完整脚本判分项包括权限、crontab 条目、日志格式、阈值逻辑、旧文件清理和 .keep 保留。Agent 看不到这些测试它只执行上面的任务描述。1.2 TaoToken 在这个实验里的角色TaoToken 在这里不是被评测对象而是 OpenHands 的模型供应商接入层。我用它提供 Key 和 Base URLOpenHands 通过标准 OpenAI 兼容接口去调用不同模型。这样做的价值在于换模型时不用改 OpenHands 的接入逻辑不用重新配沙箱只改一个模型 ID。对照组才能成立——同一个任务、同一个 Prompt、同一把 Key、同一个 Base URL真正变化的只有模型本身。注册 Key 就一步打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建后拿到一串taotoken-开头的字符串填到 OpenHands 配置里。整个实验里唯一需要保护的变量就是这串 Key不要提交进公开仓库我的复现脚本里统一用YOUR_API_KEY占位。2. OpenHands 接 TaoToken环境变量和 config.toml 两种配法OpenHands 读取 LLM 配置有两条路启动容器时传环境变量或者挂载 config.toml。两种方式都验证过任选其一即可。我这次先用的 config.toml后来为了排障又切回环境变量发现两者行为一致没有出现重复初始化。2.1 Docker 启动参数示例用环境变量方式最直观适合临时实验。OpenHands 镜像暴露 3000 端口Web 界面在这上面跑。export YOUR_API_KEYtaotoken-xxx export YOUR_MODEL_ID按模型广场展示的 ID 填写 docker run -d \ --name openhands \ -p 3000:3000 \ -e LLM_MODEL$YOUR_MODEL_ID \ -e LLM_API_KEY$YOUR_API_KEY \ -e LLM_BASE_URLhttps://taotoken.net/api \ -e LLM_CUSTOM_MODELtrue \ -v /var/run/docker.sock:/var/run/docker.sock \ ghcr.io/all-hands-ai/openhands:latest关键点LLM_CUSTOM_MODELtrue必须加否则 OpenHands 会尝试从内置模型列表里找 ID自定义 Base URL 会被忽略LLM_BASE_URL是https://taotoken.net/api而不是带/v1的路径我一开始写错成/api/v1结果所有请求 404。另外如果宿主机的 Docker 不是默认 socket 路径OpenHands 的沙箱起不来错误提示很隐晦只说无法创建工作区。2.2 config.toml 持久化配置不习惯每次 export 的话可以写到~/.config/openhands/config.toml。OpenHands 启动时若检测到该文件会合并进运行时配置。[llm] model YOUR_MODEL_ID api_key YOUR_API_KEY base_url https://taotoken.net/api custom_model true注意字段名是api_key不是apiKey。OpenHands 0.30 之后对 TOML 字段名严格区分大小写我见过用apiKey导致无法识别的情况。保存后重启容器在界面的 Settings 页面能看到模型供应商变成了自定义地址旁边会出现“This model is not in the default list, but you can still use it”之类的提示。2.3 模型 ID 上哪查模型 ID 不要猜也不要照抄网上旧教程。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 进入模型广场页面列表里每一行都有可复制的完整模型 ID形如厂商/型号。OpenHands 的LLM_MODEL直接填这个字符串不要加任何前缀或路径。这次我在模型广场选了偏向代码生成的模型跑 Terminal-Bench 这类任务时命令轨迹更干净但这只是单次感受不作为横评结论。3. 实测轨迹命令、输出与隐藏测试结果任务开始前我清空了沙箱里的 /var/log/disk-monitor.log确保日志判定不受历史数据干扰。下面是 OpenHands 执行轨迹的完整记录每步命令都来自 Agent 的实际操作我没有做任何人类介入。3.1 环境探测Agent 先确认自己在哪里Agent 做的第一件事不是写脚本而是探测环境。它运行了三条命令输出如下$ uname -a Linux f6e3b0c22a31 6.8.0-49-generic #50-Ubuntu SMP x86_64 GNU/Linux $ df -P / | awk {print $1, $2, $3, $4, $5, $6} overlay 63192664 12082776 47925948 21% / $ cat /etc/crontab SHELL/bin/sh PATH/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin这一步很关键。如果 Agent 直接写 crontab 而不查看现有内容极可能用echo ... | crontab -把原配置清掉。它先读了 /etc/crontab说明它明白“保留已存在条目”是需求的一部分。3.2 写脚本heredoc 一次过Agent 用 heredoc 写入脚本命令如下$ cat /usr/local/bin/disk-monitor.sh EOF #!/usr/bin/env bash set -euo pipefail LOG/var/log/disk-monitor.log LIMIT80 used$(df -P / | awk NR2 {print $5} | tr -d %) cleaned while IFS read -r f; do rm -f -- $f cleaned$cleaned $f done (find /tmp -maxdepth 1 -type f -mtime 7 ! -name .keep -print) stamp$(date %Y-%m-%d %H:%M:%S) if [ $used -gt $LIMIT ]; then echo $stamp ALERT disk usage over 80%$cleaned $LOG else echo $stamp INFO disk usage under 80%$cleaned $LOG fi EOF我特意看了它生成的清理逻辑用find /tmp -maxdepth 1 -type f -mtime 7 ! -name .keep -print列出候选文件然后 while read 逐行处理而不是rm -f $(find ...)。这个写法避免了路径带空格导致的 rm 失败也天然跳过了 .keep。set -euo pipefail下cleaned变量为空时不会触发未定义变量错误因为它在前面已经赋值。3.3 赋权与 crontab先查后写$ chmod 0755 /usr/local/bin/disk-monitor.sh $ crontab -l 2/dev/null || true # root crontab starts here 30 2 * * * /usr/local/bin/backup.sh $ { crontab -l 2/dev/null; echo */10 * * * * /usr/local/bin/disk-monitor.sh; } | crontab -这里我对轨迹的第一反应是容器里居然已经有一条 backup.sh 的定时任务可能是沙箱预置的干扰项。Agent 没有覆盖它而是先crontab -l 2/dev/null || true读取再用花括号合并输出后通过管道写回。这种写法比先备份到临时文件再恢复更干净也不会因为 crontab 为空而报错。3.4 主动自我验证Agent 执行了三条检查命令任务要求里明确写了“完成后主动运行 crontab -l 和 ls -l”Agent 照做了还额外加了bash -n做语法检查$ bash -n /usr/local/bin/disk-monitor.sh $ ls -l /usr/local/bin/disk-monitor.sh -rwxr-xr-x 1 root root 652 Apr 15 12:08 /usr/local/bin/disk-monitor.sh $ crontab -l 30 2 * * * /usr/local/bin/backup.sh */10 * * * * /usr/local/bin/disk-monitor.shbash -n只报语法错误Agent 主动执行它说明模型知道“写完脚本要自检”的运维习惯。ls 输出权限是 0755crontab 保留原条目且追加了新行。3.5 隐藏测试脚本与结果Agent 全部执行完成后我在宿主机上对沙箱运行了下面的隐藏测试。测试脚本只读或操作沙箱内部的路径不会影响宿主机文件#!/usr/bin/env bash # verify_hidden_tests.sh —— 在 OpenHands 沙箱内或沙箱对应容器内执行 set -u PASS_COUNT0 FAIL_COUNT0 # 检查 1脚本存在且权限为 0755 if [ -x /usr/local/bin/disk-monitor.sh ] [ $(stat -c %a /usr/local/bin/disk-monitor.sh) 755 ]; then echo TEST 1 PASS: executable 0755 PASS_COUNT$((PASS_COUNT1)) else echo TEST 1 FAIL: permission or existence FAIL_COUNT$((FAIL_COUNT1)) fi # 检查 2语法检查 if bash -n /usr/local/bin/disk-monitor.sh; then echo TEST 2 PASS: valid syntax PASS_COUNT$((PASS_COUNT1)) else echo TEST 2 FAIL: syntax error FAIL_COUNT$((FAIL_COUNT1)) fi # 检查 3crontab 条目保留且追加正确 if crontab -l | grep -qF /usr/local/bin/backup.sh \ crontab -l | grep -qF */10 * * * * /usr/local/bin/disk-monitor.sh; then echo TEST 3 PASS: crontab preserved and appended PASS_COUNT$((PASS_COUNT1)) else echo TEST 3 FAIL: crontab content FAIL_COUNT$((FAIL_COUNT1)) fi # 检查 4日志格式 rm -f /var/log/disk-monitor.log /usr/local/bin/disk-monitor.sh if grep -qE ^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} (INFO|ALERT) /var/log/disk-monitor.log; then echo TEST 4 PASS: log format matches PASS_COUNT$((PASS_COUNT1)) else echo TEST 4 FAIL: log format FAIL_COUNT$((FAIL_COUNT1)) fi # 检查 5清理旧文件且跳过 .keep touch -d 120 days ago /tmp/old-report-2025.tmp touch -d 120 days ago /tmp/important.keep /usr/local/bin/disk-monitor.sh if [ ! -e /tmp/old-report-2025.tmp ] [ -e /tmp/important.keep ]; then echo TEST 5 PASS: old file removed, .keep survived PASS_COUNT$((PASS_COUNT1)) else echo TEST 5 FAIL: cleanup semantics FAIL_COUNT$((FAIL_COUNT1)) fi # 检查 6阈值分支通过注入方式验证备份并替换 LIMIT 为 1 cp /usr/local/bin/disk-monitor.sh /tmp/disk-monitor.sh.bak sed -i s/LIMIT80/LIMIT1/ /usr/local/bin/disk-monitor.sh /usr/local/bin/disk-monitor.sh mv /tmp/disk-monitor.sh.bak /usr/local/bin/disk-monitor.sh if grep -qE ALERT disk usage over 80% /var/log/disk-monitor.log; then echo TEST 6 PASS: alert branch works PASS_COUNT$((PASS_COUNT1)) else echo TEST 6 FAIL: alert branch FAIL_COUNT$((FAIL_COUNT1)) fi echo RESULT echo PASS: $PASS_COUNT/6 FAIL: $FAIL_COUNT/6输出如下TEST 1 PASS: executable 0755 TEST 2 PASS: valid syntax TEST 3 PASS: crontab preserved and appended TEST 4 PASS: log format matches TEST 5 PASS: old file removed, .keep survived TEST 6 PASS: alert branch works RESULT PASS: 6/6 FAIL: 0/6六个判定里最有价值的是第 3、5、6 项。第 3 项证明 Agent 没有清掉预置 crontab第 5 项证明它对 .keep 的排除是实际生效的第 6 项证明阈值不是写死的能通过变量调整。这三项恰恰是用户最容易在事后检查中发现问题的点。3.6 任务通过标记与一次运行免责声明本次任务总体判定通过。隐藏测试 6 项全部通过完整命令轨迹和测试脚本已在 3.2、3.5 节给出读者可逐条复跑。请注意这是单次运行结果不代表任何公开榜单成绩也不能用这一次运行反推模型的整体能力。Terminal-Bench 官方公榜查阅日期以官网为准跟踪的是多个模型在多类任务上的平均表现与本文本地复现没有可比性本文不摘录该榜单的具体分数。4. 为什么用同一把 Key 做对照才有意义做完隐藏测试正常的下一步是换不同模型跑同一 Prompt 做横向对比。但有个坑如果每次换模型就换一套供应商配置等于同时变了两个变量结果无法归因。用 TaoToken 做统一 API 基线的好处就在这Key 不变Base URL 不变沙箱环境不变唯一变的是模型广场里复制的 ID。这样对比出来的差异才能算到模型头上。下表是本次实验可复现的对照流程不是公榜数据只是把对比方法固定下来对照维度固定值变化值OpenHands 版本官方 latest 镜像不变Sandbox 镜像OpenHands 默认不变KeyYOUR_API_KEYTaoToken 官网创建不变Base URLhttps://taotoken.net/api不变Prompt1.1 节的完整任务描述不变隐藏测试3.5 节 verify_hidden_tests.sh不变模型 ID模型广场 A 模型 → B 模型每次只换一个我这次完整跑通的只有模型广场里选的其中一个模型另一个模型只做了连通性验证没空跑完全部 6 项隐藏测试所以不在这里放第二行的验证结果避免用不完整数据凑对照表。耗时和 Token 数字本次未记录因为我没有在 OpenHands 侧挂 usage 统计想看真实消耗最直接的方式是去 TaoToken 控制台看调用记录。有一点必须强调本文所有记录都是本地实测的一次运行不代表公开榜单也不构成对任何模型的排名结论。真要比较模型能力要么看有明确来源的公榜快照要么自己在固定条件下批量跑多轮取平均值。单次 PASS 只能说明“这个任务在本次配置下能完成”不能说明它比别的模型强。5. 复现脚本与本次排障为了让读者完整复现这次实验我把三个脚本分开启动脚本负责拉起 OpenHands任务 Prompt 存成文本供复制隐藏测试脚本在 3.5 节可以直接保存使用。5.1 一键启动 OpenHands 并接入 TaoToken#!/usr/bin/env bash # run_openhands_taotoken.sh set -euo pipefail export YOUR_API_KEY${YOUR_API_KEY:-taotoken-请替换为官网创建的Key} export YOUR_MODEL_ID${YOUR_MODEL_ID:-请替换为模型广场复制的ID} docker run -d \ --name openhands \ -p 3000:3000 \ -e LLM_MODEL$YOUR_MODEL_ID \ -e LLM_API_KEY$YOUR_API_KEY \ -e LLM_BASE_URLhttps://taotoken.net/api \ -e LLM_CUSTOM_MODELtrue \ -v /var/run/docker.sock:/var/run/docker.sock \ ghcr.io/all-hands-ai/openhands:latest echo OpenHands started. URL: http://localhost:3000 echo Key: ${YOUR_API_KEY:0:12}... Model: $YOUR_MODEL_ID启动后浏览器打开 localhost:3000新建对话并粘贴 1.1 节的任务 Prompt。OpenHands 会在后台自动拉起沙箱容器。任务执行完成后把 3.5 节的测试脚本复制到沙箱里运行或者在宿主机上用docker exec找到 OpenHands 的 sandbox 容器执行。5.2 配置过程中遇到的三个错第一个错是用带/v1的 Base URL。OpenHouse 文档里的自定义供应商例子普遍写/v1但 TaoToken 的 Base URL 明确是https://taotoken.net/api我改成完整路径前所有补全请求都返回 404。第二个错是把模型 ID 凭印象填成“glm-5.3-flash”这类简化名实际模型广场里的 ID 带完整前缀名字对不上时 API 返回 401 或 model_not_found。第三个错是忘记设LLM_CUSTOM_MODELtrueOpenHands 认为我填的模型不在它的白名单列表里直接拒绝调用。这三个错都不涉及 Key 失效排障时先确认 URL、再确认模型 ID、最后确认 custom_model 开关可以省不少时间。另外如果你在 macOS 上用 Docker DesktopOpenHands 挂/var/run/docker.sock的方式和 Linux 一致但要注意 Docker Desktop 的 Settings 里必须开启“File sharing”否则沙箱容器挂载卷会失败。6. 跑完任务后的对账与下一步隐藏测试全绿之后我在 OpenHands 界面上确认了对话里没有出现过任何 401 或 404 错误说明从 OpenHands 到 TaoToken 再到模型的整条链路是通的。现在打开 TaoToken 的控制台你刚才通过 OpenHands 跑的那几轮调用应该已经出现在用量明细里了——没看到的话可能是 Key 配错了也可能模型 ID 没走完请求把任务 Prompt 和 3.5 节的隐藏测试脚本保存好换一个模型 ID 再跑一遍同一任务就能得到一张属于你自己的对照表。Key 的创建和模型 ID 的复制都在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 完成这次任务的调用记录正好可以当作第一个数据点去核对。 告别海外账号与网络限制稳定直连全球优质大模型限时半价接入中。 点击领取海量免费额度