
1. OpenHands 本地部署里模型请求到底卡在哪一层OpenHands 是一个开源的 AI 软件工程代理能读代码、跑命令、改文件适合想在自己机器上跑一个「会动手的编程助手」的开发者。它默认通过 LiteLLM 统一管理模型调用而 LiteLLM 的 endpoint、API Key、模型名这三样东西决定了代理的每一次思考请求最终发到哪里。很多人本地部署完 OpenHands界面能打开、会话能创建但一发消息就报litellm.AuthenticationError或者APIConnectionError问题基本都出在这一层请求从 OpenHands 的 API 网关出去之后没有正确落到你想要的模型服务上。这篇笔记聚焦的就是这条链路OpenHands 的 API 网关怎么路由请求、鉴权信息怎么传递、以及怎么把模型 endpoint 统一改到 TaoToken让所有对话和工具调用都走同一个出口。我会给出可复制的配置文件、请求转发示例以及用 curl 验证链路是否生效的具体动作。如果你正在做 OpenHands 本地部署或者想把它的模型出口收敛到一个统一网关这篇可以直接跟着操作。先说清楚 OpenHands 的请求处理结构。它启动后主容器监听 3000 端口Web 界面和 REST API 都从这里进。用户在前端发一条消息请求先到 OpenHands 自己的 API 层这一层做会话管理、请求校验、工具调度然后把「需要模型生成」的部分交给 LiteLLM。LiteLLM 再根据配置里的model字段决定用哪个 provider、发到哪个 base_url。所以「改 endpoint」这件事改的不是 OpenHands 的 3000 端口而是 LiteLLM 指向的上游地址。这里有个容易混淆的点OpenHands 的 API 网关和模型网关是两层。前者管的是「用户请求怎么进 OpenHands」后者管的是「OpenHands 怎么调模型」。我们要动的是后者。理解这一点后面配置就不会改错地方。我试过在本地用 Docker 跑 OpenHands最初把LLM_BASE_URL写成了 OpenHands 自己的地址结果请求在容器里打转日志里全是连接超时。后来才理清LLM_BASE_URL必须是模型服务的地址跟 OpenHands 的 3000 端口没关系。这个坑先记下第五节还会展开。TaoToken 在这里的角色就是那个统一的模型出口。它提供 OpenAI 兼容的接口base_url 是https://taotoken.net/api模型 ID 用标准的 provider 前缀格式。OpenHands 通过 LiteLLM 调用时只要把 base_url、api_key、model 三件套配对请求就能正常转发。下面进入具体配置。2. TaoToken 前置准备拿到 Base URL、Key 和 Model ID 三件套在改 OpenHands 配置之前先把 TaoToken 这边的三样东西准备好。这三样是后面所有配置的基础缺一个请求都发不出去。第一样是 Base URL。TaoToken 的 API 入口是https://taotoken.net/api注意这里不带任何路径后缀LiteLLM 和 OpenAI SDK 会自动在它后面拼/v1/chat/completions这类路径。如果你手动加了/v1反而可能拼成/v1/v1/...导致 404。这一点在配置LLM_BASE_URL时尤其要注意。第二样是 API Key。登录 TaoToken 控制台在 API Keys 页面创建一个新 Key。创建时建议给它起个能认出来的名字比如openhands-local方便以后排查是哪个环境在用。Key 只在创建时完整显示一次复制下来存好。如果你还没账号可以先到官网了解https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台里生成 Key。第三样是 Model ID。TaoToken 的模型 ID 采用provider/model的格式比如anthropic/claude-sonnet-4-20250514、openai/gpt-4o这类。具体有哪些可用模型可以在模型对话页面直接试或者查接入文档里的模型列表。选模型时注意一点OpenHands 的代理循环对模型的工具调用能力有要求最好选支持 function calling 的模型否则代理可能无法正确触发文件编辑、命令执行这些动作。把这三样记下来格式大概是这样配置项值说明Base URLhttps://taotoken.net/api不带/v1后缀API Keysk-xxxxxxxx控制台创建只显示一次Model IDanthropic/claude-sonnet-4-20250514按 provider/model 格式拿到之后建议先用 curl 单独验证一次确认 Key 和模型都可用再去改 OpenHands。这样能把「TaoToken 侧的问题」和「OpenHands 侧的问题」分开排障时省很多时间。验证命令如下把$TAOTOKEN_KEY换成你的真实 Keycurl -s https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $TAOTOKEN_KEY \ -d { model: anthropic/claude-sonnet-4-20250514, messages: [{role: user, content: reply with ok}], max_tokens: 16 }如果返回里能看到choices数组和一段回复内容说明 Key 和模型都没问题。如果返回 401检查 Key 是否复制完整、有没有多余空格如果返回 404 或模型不存在检查 Model ID 拼写。这一步过了再进 OpenHands 配置。需要提醒的是TaoToken 的 Key 要当成密码对待不要写进会提交到 Git 的文件里。后面配置 OpenHands 时我会用环境变量和.env文件的方式管理避免 Key 泄漏。3. 可复制配置把 OpenHands 的模型 endpoint 统一改到 TaoTokenOpenHands 的模型配置有几个入口最直接的是通过环境变量和config.toml。不同版本略有差异但核心就是让 LiteLLM 拿到正确的 base_url、api_key 和 model。下面给出两种方式你可以按自己的部署方式选。方式一Docker 环境变量。如果你用docker run启动 OpenHands直接在命令里注入这几个变量docker run -it --rm \ --pullalways \ -e SANDBOX_RUNTIME_CONTAINER_IMAGEdocker.all-hands.dev/all-hands-ai/runtime:0.20-nikolaik \ -e LOG_ALL_EVENTStrue \ -e LLM_API_KEY$TAOTOKEN_KEY \ -e LLM_BASE_URLhttps://taotoken.net/api \ -e LLM_MODELanthropic/claude-sonnet-4-20250514 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v ~/.openhands-state:/.openhands-state \ -p 3000:3000 \ --add-host host.docker.internal:host-gateway \ --name openhands-app \ docker.all-hands.dev/all-hands-ai/openhands:0.20这里三个变量是关键LLM_API_KEY放 TaoToken 的 KeyLLM_BASE_URL放https://taotoken.net/apiLLM_MODEL放模型 ID。注意LLM_BASE_URL不要带/v1LiteLLM 会自己处理路径。方式二config.toml文件。OpenHands 支持从~/.openhands-state/config.toml读取配置适合想持久化、不想每次敲一长串环境变量的场景。文件内容如下[core] workspace_base ./workspace [llm] model anthropic/claude-sonnet-4-20250514 api_key sk-xxxxxxxx base_url https://taotoken.net/api custom_llm_provider openai [llm.retry] num_retries 3 retry_min_wait 2 retry_max_wait 10这里custom_llm_provider openai是让 LiteLLM 用 OpenAI 兼容协议去发请求TaoToken 的接口正好是 OpenAI 兼容的所以这样配能通。base_url同样不带/v1。api_key这里直接写了明文生产环境建议改成从环境变量读或者用密钥管理工具注入。如果你用的是 OpenHands 的 CLI 模式配置读取逻辑一致只是启动命令不同。CLI 下可以用--config指定配置文件路径或者同样用环境变量覆盖。配置改完之后重启 OpenHands 容器或进程。重启后OpenHands 的 API 网关在收到对话请求时会把模型调用转发到https://taotoken.net/api而不是默认的 provider 地址。这一步做完链路的上游就切过来了。有个细节值得说OpenHands 的会话状态存在~/.openhands-state里如果你之前用别的 endpoint 跑过会话重启后旧会话可能还带着旧的模型配置。稳妥做法是新建一个会话测试别在旧会话里验证。另外如果你在 OpenHands 里同时配了多个模型LiteLLM 会按model字段路由。想统一出口的话把所有用到的模型 ID 都确认在 TaoToken 侧可用避免某个模型没开导致代理中途报错。4. 验证请求处理链路用 curl 和日志确认请求真的走到了 TaoToken配置改完不代表链路就通了得实际验证。验证分两层先验证 OpenHands 的 API 网关能正常接收请求再验证它转发出去的模型请求确实到了 TaoToken。第一层验证 OpenHands 自己的 API。容器起来后先打健康检查curl -s http://localhost:3000/api/health正常会返回类似{status:ok}的 JSON。如果这里就失败说明 OpenHands 没起来先看容器日志docker logs openhands-app别急着查模型配置。第二层通过 OpenHands 的对话接口发一条消息观察它是否成功调用模型。OpenHands 的对话接口大致是POST /api/chat或通过 WebSocket 流式返回具体路径随版本变化。更稳的验证方式是直接看容器日志里的 LiteLLM 调用记录。启动时加上-e LOG_ALL_EVENTStrue日志里会打印每次模型请求的 provider、model 和响应状态。发一条测试消息后日志里应该能看到类似这样的记录LiteLLM completion() modelanthropic/claude-sonnet-4-20250514; provideropenai POST Request to https://taotoken.net/api/v1/chat/completions看到taotoken.net这个域名出现在日志里就说明请求确实转发到了 TaoToken而不是别的地址。如果日志里出现的是api.openai.com或api.anthropic.com说明LLM_BASE_URL没生效检查环境变量有没有拼错、容器有没有重启。第三层直接对 TaoToken 发一次带工具调用的请求验证 function calling 链路。因为 OpenHands 依赖工具调用光验证普通对话不够。命令如下curl -s https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $TAOTOKEN_KEY \ -d { model: anthropic/claude-sonnet-4-20250514, messages: [{role: user, content: What is the weather in Beijing?}], tools: [{ type: function, function: { name: get_weather, description: Get weather for a city, parameters: { type: object, properties: {city: {type: string}}, required: [city] } } }], tool_choice: auto }如果返回的choices[0].message里带tool_calls字段说明模型支持工具调用OpenHands 的代理循环能正常工作。如果返回的是普通文本、没有tool_calls那这个模型可能不支持 function calling换一个支持工具调用的模型 ID。验证通过后回到 OpenHands 界面新建一个会话让它做一个简单任务比如「列出当前目录下的文件」。如果代理能正常执行命令并返回结果说明整条链路——从 OpenHands API 网关到 LiteLLM 再到 TaoToken——都通了。这一步的日志观察很关键。很多人配置完只看界面能不能回复忽略了日志里的实际请求地址结果某天 provider 切换了都不知道。养成看日志的习惯排障会快很多。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth配置过程中最容易撞上的几类报错这里逐个对照。401 AuthenticationError。日志里出现litellm.AuthenticationError: OpenAIException - Invalid API key或401 Unauthorized。原因通常是 Key 没传对要么环境变量名写错OpenHands 认的是LLM_API_KEY不是OPENAI_API_KEY要么 Key 里带了换行或空格要么用了过期/被删的 Key。排查动作在容器里执行echo $LLM_API_KEY确认值正确再用第 2 节的 curl 单独验证 Key。如果 curl 能通、OpenHands 不通那就是环境变量没注入到容器检查docker run的-e参数。local proxy failed / Connection error。日志里出现litellm.APIConnectionError: OpenAIException - Connection error或local proxy failed。这类多半是LLM_BASE_URL写错或者容器网络出不去。常见错误是把 base_url 写成了http://localhost:3000OpenHands 自己的地址或者写成了https://taotoken.net/api/v1多了/v1。排查动作确认 base_url 是https://taotoken.net/api然后在容器内执行curl -sI https://taotoken.net/api看能不能通。如果容器内不通、宿主机通检查 Docker 的 DNS 和网络配置。reading choices / KeyError choices。日志里出现KeyError: choices或reading choices。这通常说明返回的响应结构不是预期的 OpenAI 格式可能是 base_url 指到了非兼容接口或者模型 ID 不存在导致返回了错误结构。排查动作用第 2 节的 curl 直接打 TaoToken看返回的 JSON 里有没有choices。如果没有检查 Model ID 是否正确、该模型是否在 TaoToken 侧可用。OAuth / 登录态相关报错。如果你在 OpenHands 里启用了 GitHub 集成或 OAuth 登录可能会看到OAuth token invalid这类报错。这跟模型 endpoint 是两回事属于 OpenHands 自身的鉴权层。排查时先确认模型链路是否独立可用别把 OAuth 问题和模型配置混在一起。如果只是想让模型走 TaoTokenOAuth 可以先不配。模型不支持工具调用。表现是代理能回复文字但不会执行文件操作或命令日志里没有tool_calls。这不是报错是模型能力问题。换一个支持 function calling 的模型 ID 即可。配置改了但没生效。最常见的原因是容器没重启或者旧会话缓存了旧配置。排查动作docker restart openhands-app然后新建会话测试。另外确认config.toml的路径对不对OpenHands 读的是~/.openhands-state/config.toml不是项目目录下的。把这几类对照着看基本能覆盖 90% 的配置问题。核心思路就一条先用 curl 把 TaoToken 侧验证通再查 OpenHands 侧的注入和转发两层分开排。6. 把模型出口收敛之后下一步可以做什么模型 endpoint 统一到 TaoToken 之后OpenHands 的所有对话和工具调用都走同一个出口管理起来清爽很多。你可以在这个基础上做几件事一是把不同任务路由到不同模型比如代码生成用强模型、简单问答用快模型在 LiteLLM 层做路由二是给 Key 加上用量监控观察代理的 token 消耗三是把配置模板化团队里每个人用同一份config.toml只替换自己的 Key。如果你还没开始配建议按这个顺序走先到控制台创建 Key用 curl 验证模型可用再改 OpenHands 的环境变量或config.toml重启后看日志确认请求打到了taotoken.net最后在界面里跑一个真实任务验证工具调用。每一步都验证过再进下一步比一次性改完再排障快得多。需要长期跑编码代理、或者想把多个工具统一到一个模型出口的话可以了解下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。配置过程中遇到鉴权或转发问题接入文档里有更细的字段说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。想先试试模型效果直接开模型对话页面发一条消息就行https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。Key 管理在控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后留一个实操建议把LLM_BASE_URL、LLM_MODEL这些值写进一个.env文件用docker run --env-file .env启动别每次手敲。.env加进.gitignoreKey 就不会误提交。这个习惯在本地部署里能省不少事。