1. 从一次失败的 CodeActAgent 执行说起
如果你正在本地跑 OpenHands,大概率遇到过这种场景:任务描述写得很清楚,CodeActAgent 也顺利解析出了execute_bash或execute_ipython_cell动作,但执行结果迟迟回不来,日志里反复出现local proxy failed或者401 Unauthorized。这不是 Agent 逻辑写错了,而是模型 endpoint 和鉴权通道没有打通。
CodeActAgent 是 OpenHands 里把“自然语言指令”翻译成“可执行代码”的核心模块。它的工作链路可以粗略拆成四段:接收用户消息 → 组装上下文与工具描述 → 调用 LLM 生成 Action → 在沙盒里执行代码并把 Observation 回传给下一轮。任何一段的模型通道出问题,整条链路就会卡住。
这篇内容面向已经在本地部署 OpenHands、想让 CodeActAgent 稳定跑起来的开发者。我会先拆解 CodeActAgent 的代码执行链路,然后重点讲怎么把模型 endpoint 和auth.json改到 TaoToken 统一 Key/API 通道,最后给一次端到端验证动作,确认 Agent 能正常发起代码执行并返回结果。适合谁:手里有 OpenHands 源码、想统一管理多个模型 Key、不想在每个项目里重复配环境变量的人。
2. CodeActAgent 代码执行链路拆解与模型通道定位
CodeActAgent 的核心设计理念是“把动作空间统一到代码执行”。它不像传统 Agent 那样为每个工具定义独立的 JSON schema 调用,而是让模型直接生成 Python 或 bash 代码,交给沙盒解释器执行。理解这条链路,才能知道模型通道该改哪里。
2.1 从 step() 到 Action 的决策流程
CodeActAgent 的step()方法是整条链路的起点。它接收当前State,先检查pending_actions队列里有没有待执行动作;如果没有,就调用condenser.condensed_history(state)压缩历史事件,再用_get_messages()把压缩后的事件转成 LLM 能理解的消息列表。
关键点在这里:params['tools'] = check_tools(self.tools, self.llm.config)会把当前启用的工具描述塞进请求体,然后response = self.llm.completion(**params)发起模型调用。这个self.llm就是模型通道的入口,它的 endpoint、api_key、model 三个参数决定了请求发往哪里。
response_to_actions(response)把模型返回的内容解析成具体 Action,比如CmdRunAction(command='ls -la')或IPythonRunCellAction(code='import pandas')。这些 Action 被 append 到pending_actions,然后逐个 popleft 返回给控制器执行。
2.2 工具集与沙盒插件的依赖顺序
CodeActAgent 的sandbox_plugins定义了两个必须按顺序初始化的插件:
sandbox_plugins: list[PluginRequirement] = [ AgentSkillsRequirement(), # 提供 Python 工具函数 JupyterRequirement(), # 提供 IPython 执行环境 ]AgentSkillsRequirement必须在JupyterRequirement之前,因为它提供了大量 Python 函数,Jupyter 环境需要依赖这些函数才能正常工作。这个顺序如果搞反,沙盒启动时会报ModuleNotFoundError。
工具集通过_get_tools()动态组装,受AgentConfig控制:
| 配置项 | 对应工具 | 作用 |
|---|---|---|
enable_cmd | create_cmd_run_tool | 执行 bash 命令 |
enable_think | ThinkTool | 记录推理过程 |
enable_finish | FinishTool | 结束任务 |
enable_jupyter | IPythonTool | 执行 Python 代码 |
enable_editor | create_str_replace_editor_tool | 编辑文件 |
enable_browsing | BrowserTool | 浏览器交互(非 Windows) |
2.3 模型通道在链路中的位置
整条链路里,模型通道出现在self.llm.completion(**params)这一行。self.llm来自self.llm_registry.get_router(self.config),而LLMRegistry的配置最终来源于 OpenHands 的config.toml和auth.json。
也就是说,要让 CodeActAgent 正常发起代码执行,必须保证三件事同时成立:config.toml里的base_url指向可用的模型服务、auth.json里的 api_key 有效、model字段与目标服务支持的模型 ID 一致。任何一项不对,completion()就会抛异常,Agent 的代码执行链路直接断在第一步。
3. 把 OpenHands 模型通道改到 TaoToken 的可复制配置
这一节给可直接复制的配置片段。OpenHands 的模型配置分散在两个文件:~/.openhands/config.toml管 endpoint 和 model,~/.openhands/auth.json管鉴权。两个都要改,缺一不可。
3.1 config.toml 的 endpoint 与 model 配置
打开~/.openhands/config.toml,找到[llm]段,改成下面这样:
[llm] # 模型服务地址,指向 TaoToken 的 API 通道 base_url = "https://taotoken.net/api" # 模型 ID,按你实际使用的模型填写 model = "claude-sonnet-4-20250514" # 请求超时,CodeActAgent 执行长任务时建议调大 timeout = 300 # 最大输出 token max_output_tokens = 8192 # 温度,代码生成任务建议低一些 temperature = 0.1base_url这里填https://taotoken.net/api,不要带 UTM 参数,保持干净。model字段必须和目标服务支持的模型 ID 完全一致,大小写敏感。
3.2 auth.json 的 Key 配置
~/.openhands/auth.json管鉴权,格式如下:
{ "api_key": "sk-你的TaoToken密钥" }如果你用的是 OpenHands 的 Codex 模式,auth.json可能还包含openai_api_key字段。统一改成 TaoToken 的 Key 即可:
{ "api_key": "sk-你的TaoToken密钥", "openai_api_key": "sk-你的TaoToken密钥" }Key 的获取入口在 TaoToken 控制台的 API Keys 页面,创建后复制完整字符串。注意不要提交到 Git,auth.json应该在.gitignore里。
3.3 三件套对照表
配置改完后,用这张表自查一遍:
| 配置项 | 文件 | 值 | 说明 |
|---|---|---|---|
| Base URL | config.toml | https://taotoken.net/api | 不带 UTM |
| API Key | auth.json | sk-... | 从控制台复制 |
| Model ID | config.toml | 如claude-sonnet-4-20250514 | 大小写敏感 |
三件套必须同时正确。我试过只改base_url忘了改auth.json,结果 Agent 一直报 401,排查了半小时才发现 Key 还是旧的。
3.4 环境变量方式的备选配置
如果你不想改文件,也可以用环境变量覆盖:
export LLM_BASE_URL="https://taotoken.net/api" export LLM_API_KEY="sk-你的TaoToken密钥" export LLM_MODEL="claude-sonnet-4-20250514"环境变量的优先级高于config.toml,适合临时切换模型做对比测试。但长期使用还是建议写进配置文件,避免每次开终端都要 export。
4. 端到端验证:确认 CodeActAgent 能发起代码执行并返回结果
配置改完后,需要一次完整的端到端验证,确认从指令解析到代码执行、结果回传的整条链路都通。下面给一个最小验证动作。
4.1 启动 OpenHands 并观察初始化日志
在终端启动 OpenHands:
cd /path/to/OpenHands python -m openhands.core.main启动后观察日志,重点看这几行:
INFO: LLM config loaded: base_url=https://taotoken.net/api, model=claude-sonnet-4-20250514 INFO: CodeActAgent initialized with plugins: AgentSkillsRequirement, JupyterRequirement INFO: Sandbox started successfully如果base_url显示的不是你配置的地址,说明config.toml没被正确加载,检查文件路径和 TOML 语法。
4.2 发一个触发代码执行的任务
在 OpenHands 的交互界面里输入一个明确需要代码执行的任务:
在当前目录创建一个 test_codeact.py 文件,写入一个计算斐波那契数列前10项的函数,然后运行它并打印结果。这个任务会触发 CodeActAgent 的多个 Action:create_str_replace_editor_tool写文件、IPythonTool或create_cmd_run_tool执行代码、FinishTool结束任务。
4.3 检查执行结果与 Observation 回传
正常情况下,你会看到类似这样的输出:
Action: create_str_replace_editor_tool path: test_codeact.py command: create file_text: def fib(n): ... Observation: File created successfully Action: IPythonRunCellAction code: exec(open('test_codeact.py').read()); print(fib(10)) Observation: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] Action: AgentFinishAction message: 任务完成,斐波那契数列前10项已计算并打印。看到Observation里有实际执行结果,说明模型通道、代码执行、结果回传三段都通了。如果卡在Action之后没有Observation,问题多半在沙盒执行环境,而不是模型通道。
4.4 用 curl 单独验证模型通道
如果 Agent 链路有问题,可以先用 curl 单独验证模型通道是否可用:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoToken密钥" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [{"role": "user", "content": "print hello"}], "max_tokens": 100 }'返回 200 且有choices字段,说明通道本身没问题,问题在 OpenHands 的配置加载或沙盒环境。
5. 本篇常见报错排查
配置过程中最容易踩的坑集中在鉴权、代理、响应解析和 OAuth 四类。下面按真实报错逐条排查。
5.1 401 Unauthorized
报错原文:
openai.AuthenticationError: Error code: 401 - {'error': {'message': 'Invalid API key'}}排查顺序:先确认auth.json里的api_key是否完整复制,有没有多余空格或换行;再确认config.toml里的base_url是否指向https://taotoken.net/api;最后用 curl 单独测 Key 是否有效。如果 curl 能通但 OpenHands 报 401,说明 OpenHands 读的不是你改的那个auth.json,检查OPENHANDS_CONFIG_DIR环境变量指向的目录。
5.2 local proxy failed
报错原文:
ConnectionError: local proxy failed to connect to upstream这个报错通常出现在base_url配置了本地代理地址但代理没启动的情况。检查config.toml里的base_url是否误填了http://localhost:xxxx。正确做法是直接填https://taotoken.net/api,不需要本地代理层。
5.3 reading choices 相关解析错误
报错原文:
KeyError: 'choices'或者:
IndexError: list index out of range这说明模型返回的响应体里没有choices字段。常见原因有两个:一是model字段填了目标服务不支持的模型 ID,服务返回了错误信息而不是正常响应;二是请求被中间层拦截,返回了 HTML 错误页。用 curl 发同样的请求,看原始响应体就能定位。
5.4 OAuth 相关报错
报错原文:
OAuth token expired or invalid如果你用的是 Codex 模式,auth.json里可能残留了旧的 OAuth token。把auth.json清空,只保留api_key和openai_api_key两个字段,重新填入 TaoToken 的 Key。OAuth 流程和 API Key 鉴权是两套机制,不要混用。
5.5 沙盒启动失败导致 Observation 缺失
报错原文:
RuntimeError: Sandbox failed to start: JupyterRequirement initialization failed检查sandbox_plugins的顺序,AgentSkillsRequirement必须在JupyterRequirement之前。如果顺序对了还报错,检查 Docker 是否正常运行,OpenHands 的沙盒依赖 Docker 容器。
6. 统一模型通道后的 CodeActAgent 使用建议
把模型通道统一到 TaoToken 之后,CodeActAgent 的调试成本会明显下降。以前每个项目要配一套 Key,现在一个 Key 走通所有模型,切换模型只改config.toml里的model字段。
几个实用建议:第一,config.toml里的timeout建议设到 300 秒以上,CodeActAgent 执行复杂任务时单次 LLM 调用可能超过默认的 60 秒。第二,temperature设低一些,代码生成任务不需要创造性,0.1 左右比较稳。第三,如果要做长期编码任务或 Agent 编排,可以了解 Coding Plan 的额度方案,比按次调用更划算。
验证模型本身的能力时,可以直接在模型对话页面测试目标模型对代码生成任务的响应质量,确认模型 ID 和实际能力匹配后再写进config.toml。接入文档里有完整的 endpoint 和参数说明,遇到配置问题可以先查文档。
最后一步,把改好的config.toml和auth.json备份一份,下次换机器直接复制,省去重新排查 401 的时间。