1. OpenHands 本地部署为什么总卡在模型接入这一步
OpenHands 是一个开源的 AI 编程代理平台,能理解任务、读写文件、跑终端命令、调浏览器,最后把改动整理成 Pull Request。它适合想自己掌控 Agent 行为、不想被单一模型绑死的开发者,也适合团队做二次开发和实验。但很多人第一次跑docker compose up之后,Web UI 能打开,任务一提交就报错,最常见的就是模型通道没配好。
我试过在本地把 OpenHands 跑起来,前面环境都顺,真正耗时间的是让 Agent 稳定拿到模型响应。OpenHands 的 LLM Backend 设计上支持对接任意服务商,配置项集中在config.toml和环境变量里。只要 Base URL、API Key、Model ID 三件套对齐,Agent 就能正常规划任务。这篇按“项目架构理解 → 统一 Key 接入 → 可复制配置 → 启动验证 → 报错排查”的顺序走,每一步都能直接跟做。
先说清楚 OpenHands 的架构,不然后面配置容易懵。它大致分几层:前端 Web UI 负责展示 Chat、Changes、Terminal、Browser 等面板;后端核心是 Agent,负责解析任务、规划步骤、调 LLM 推理;Runtime 是隔离的执行环境,通常跑在 Docker 容器里,Agent 通过它读写文件、执行命令;LLM Backend 是模型交互层,把 Agent 的 Prompt 发给模型再解析响应;Microagents 是可扩展的小工具单元,比如读文件、跑测试、搜网页。模型接入改的就是 LLM Backend 这一层,其他层不用动。
为什么推荐用统一 Key 通道而不是每个模型单独配?因为 OpenHands 支持多模型切换,但如果你同时维护 OpenAI、Anthropic、Google 几套 Key,配置文件会越来越乱,切换模型时容易漏改。统一通道的好处是一个 Base URL 加一个 Key,通过改 Model ID 就能切模型,配置面收敛到三个变量,排障也简单。下面所有配置都围绕这三个变量展开。
2. TaoToken 统一 Key 前置准备与 OpenHands 环境变量映射
在动 OpenHands 之前,先把统一 Key 通道准备好。你需要拿到两样东西:一个 API Key,一个 Base URL。Base URL 用https://taotoken.net/api,注意这个地址不带任何查询参数,直接填进配置即可。Key 在控制台的 API Keys 页面创建,创建后复制保存,后面写进环境变量。
OpenHands 读取模型配置有两条路径:一条是config.toml文件,一条是环境变量。环境变量优先级通常更高,适合容器化部署;config.toml适合本地调试时固定配置。两者不要同时写冲突的值,否则会出现“明明改了配置却不生效”的情况。建议容器部署用环境变量,本地裸跑用config.toml,二选一。
先把三个核心变量定下来:
| 变量 | 值 | 说明 |
|---|---|---|
LLM_BASE_URL | https://taotoken.net/api | 统一通道地址,不带 UTM |
LLM_API_KEY | 控制台创建的 Key | 只存环境变量,别写进代码 |
LLM_MODEL | 例如claude-sonnet-4-20250514 | 按需切换,决定实际调用哪个模型 |
OpenHands 的配置字段名在不同版本里略有差异,常见的是base_url、api_key、model,环境变量侧对应LLM_BASE_URL、LLM_API_KEY、LLM_MODEL。如果你用的是较新版本,配置模板里可能叫llm.base_url这种嵌套写法。以你本地config.template.toml的实际字段为准,下面给的是通用映射关系。
这里要提醒一个坑:Base URL 末尾不要多加/v1或斜杠。统一通道的地址就是https://taotoken.net/api,OpenHands 内部会按服务商适配器拼接路径。你多写一层路径,请求就会打到不存在的端点,报 404 或 401。这个错误很隐蔽,因为 UI 上只显示“模型调用失败”,不会告诉你路径错了。
环境变量准备好后,先别急着启动 OpenHands。用一条 curl 验证通道本身是通的,能排除掉一半问题。命令如下:
curl -sS https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $LLM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "'"$LLM_MODEL"'", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 16 }'如果返回里有choices字段和内容,说明 Key、Base URL、Model ID 三件套是对的。如果返回 401,检查 Key 是否复制完整、有没有多余空格;如果返回 404,检查 Base URL 路径;如果返回模型不存在,检查 Model ID 拼写。这一步过了,再进 OpenHands 配置,成功率会高很多。
3. 可复制配置:config.toml 与 docker-compose 环境变量片段
这一节给两份可直接复制的配置。第一份是config.toml,适合本地直接运行 OpenHands 的场景。第二份是docker-compose.yml的环境变量片段,适合容器部署。两份配置的字段名以你本地模板为准,核心是三个值对齐。
先看config.toml。在 OpenHands 项目根目录下,通常有一个config.template.toml,复制一份改名为config.toml,然后填入以下内容:
[llm] # 统一通道地址,不要加 /v1 或末尾斜杠 base_url = "https://taotoken.net/api" # 从控制台创建的 Key,建议用环境变量注入而非硬编码 api_key = "sk-你的Key" # 模型 ID,按需切换 model = "claude-sonnet-4-20250514" # 部分版本需要显式指定服务商类型,通用通道填 openai 兼容 provider = "openai" # 超时设置,Agent 任务链路长,建议放宽 timeout = 300 # 最大输出 token,按模型能力调整 max_output_tokens = 8192如果你不想把 Key 写进文件,可以用环境变量占位。OpenHands 支持在config.toml里引用环境变量,写法类似${LLM_API_KEY},具体语法看版本。更稳妥的做法是文件里不写 Key,靠环境变量覆盖。
再看docker-compose.yml的环境变量片段。找到 OpenHands 服务定义,在environment下加入:
services: openhands: environment: - LLM_BASE_URL=https://taotoken.net/api - LLM_API_KEY=${LLM_API_KEY} - LLM_MODEL=claude-sonnet-4-20250514 - LLM_PROVIDER=openai - LLM_TIMEOUT=300然后在同目录建一个.env文件,写入:
LLM_API_KEY=sk-你的Key.env要加进.gitignore,别提交到仓库。这样docker compose启动时会自动注入,Key 不落盘到 compose 文件里。
如果你用的是 Cline 或 Claude Code 这类工具配合 OpenHands 做开发,配置逻辑一样,都是 Base URL、Key、Model ID 三件套。Cline 的 MCP 配置里,baseUrl填https://taotoken.net/api,apiKey填同一个 Key,model填同一个 Model ID。三处保持一致,切换工具时不用重新申请。
配置写完后,检查一遍三个值:Base URL 是不是https://taotoken.net/api,没有多余路径;Key 是不是完整;Model ID 是不是当前通道支持的。这三个对齐,后面启动基本不会卡在模型接入上。
4. 启动 OpenHands 并验证代理任务执行是否成功
配置就绪后启动 OpenHands。容器部署执行:
docker compose up -d本地裸跑按项目 README 的方式启动后端和前端。启动后打开 Web UI,通常是http://localhost:3000。先别急着提复杂任务,用一个最小任务验证链路:在 Chat 面板输入“在当前目录创建一个 hello.py,打印 hello openhands,然后运行它”。
观察几个关键面板。Chat 面板应该出现 Agent 的规划文本,说明它拿到了模型响应并开始推理。Terminal 面板应该出现python hello.py的执行记录和输出hello openhands。Changes 面板应该显示新建了hello.py文件。这三个面板都有动静,说明 Agent、Runtime、LLM Backend 三层都通了。
如果 Chat 面板一直转圈没有文本输出,大概率是模型通道没通。回到上一节的 curl 命令再验一次。如果 Chat 有输出但 Terminal 没动静,说明模型通了但 Runtime 有问题,检查 Docker 容器是否正常、Runtime 镜像是否拉取成功。如果 Changes 面板没显示文件,检查 Runtime 的持久化挂载配置,v0.39.0 之后持久化挂载有改进,确认工作目录挂载正确。
再做一个稍复杂的验证:让 Agent 修复一个故意写错的 Python 文件。先手动创建一个bug.py,里面写print(1/0),然后让 Agent“运行 bug.py 并修复报错”。成功的表现是:Agent 先运行文件,捕获到ZeroDivisionError,然后修改文件加入异常处理或修正逻辑,再次运行通过。这个过程能验证 Agent 的反馈分析和迭代纠错能力,也就是它能不能根据 Runtime 返回的错误信息调整行动。
验证通过后,你可以试着切换 Model ID 再跑一次同样的任务。把LLM_MODEL改成另一个模型,重启服务,重复上面的最小任务。如果也能跑通,说明统一通道的多模型切换是生效的,你后续可以根据任务类型选模型,比如复杂规划用推理强的,简单改动用响应快的。
5. 本篇常见报错排查:401、local proxy failed、reading choices、OAuth
这一节对照真实报错给排查路径。这些错误在 OpenHands 接入统一通道时出现频率最高,按顺序排查能省很多时间。
401 Unauthorized。最常见的原因是 Key 不对。检查三处:.env里的 Key 有没有多余空格或换行;config.toml里的 Key 是不是被环境变量覆盖成了空值;Key 是不是在控制台被删除或过期。还有一种情况是 Base URL 写错导致请求打到了别的服务,对方返回 401。确认 Base URL 是https://taotoken.net/api,不带/v1。
local proxy failed。这个报错通常出现在容器网络层。OpenHands 的 Runtime 容器要访问外部模型通道,如果容器网络配置有问题,请求出不去。检查 Docker 的 DNS 设置,确认容器能解析外部域名。在容器内执行curl -sS https://taotoken.net/api看能不能通。如果容器内不通但宿主机通,是 Docker 网络问题,检查docker-compose.yml的network_mode和 DNS 配置。
Error reading choices / reading choices。这个报错说明请求发出去了,但响应结构不符合 OpenHands 的解析预期。常见原因是 Base URL 路径不对,请求打到了返回非标准 JSON 的端点。确认 Base URL 没有多余路径。另一个原因是 Model ID 不被通道支持,通道返回了错误结构。用 curl 单独验证该 Model ID 是否可用。还有一种情况是响应被中间层截断,检查max_output_tokens是否设得过大导致超时截断。
OAuth / authentication error。如果你在配置里误开了 OAuth 相关选项,或者服务商类型provider填错,会走到 OAuth 流程。统一通道用 API Key 认证,provider填openai兼容模式即可,不要选需要 OAuth 的服务商类型。检查config.toml里有没有残留的 OAuth 配置项,清掉。
排查时建议开日志。OpenHands 后端日志会打印实际请求的 URL 和响应状态码,比 UI 上的报错信息详细得多。容器部署用docker compose logs -f openhands看实时日志。看到实际请求 URL 后,和你的 Base URL 对比,路径错误一眼就能看出来。
如果报错信息里出现model not found,说明 Model ID 拼写有问题。Model ID 是大小写敏感的,claude-sonnet-4-20250514和Claude-Sonnet-4-20250514可能被当成两个模型。从控制台的模型列表里复制,别手打。
6. 接入后的下一步:模型对话验证与长期编码方案
配置跑通、最小任务验证成功后,建议先做一轮模型对话验证,确认通道在不同模型下的稳定性。打开模型对话页面,用同一个 Prompt 分别测几个 Model ID,观察响应速度和输出质量。这一步能帮你建立“哪个模型适合哪类任务”的直觉,后续配 OpenHands 时按任务选模型。
如果你打算把 OpenHands 长期用于编码和 Agent 任务,建议走 Coding Plan 方案。它适合高频调用场景,比按量计费更可控,尤其是 Agent 任务链路长、单次任务可能触发几十次模型调用的情况下。接入方式不变,还是 Base URL、Key、Model ID 三件套,只是 Key 从按量计费换成套餐类型。
接入文档里有各工具的详细配置示例,包括 OpenHands、Cline、Claude Code 等。遇到配置字段不确定时,对照文档里的字段名,比猜要快。API Keys 页面管理你的 Key,可以创建多个 Key 分别给不同工具用,方便排查是哪个工具的问题。
最后给一个实用技巧:把 OpenHands 的config.toml和.env分开管理,config.toml提交到仓库(不含 Key),.env本地保存并加进.gitignore。团队协作时,每个人用自己的 Key,配置文件共享。这样既保证配置一致,又不会泄露 Key。切换模型时只改.env里的LLM_MODEL,重启服务即可,不用动其他文件。