☰
拆解 OpenHands(1)--- 核心理念与 TaoToken 统一 Key 接入骨架
2026/9/27 18:26:00 网站建设 项目流程

1. 为什么我要从 OpenHands 开始拆 Agent

OpenHands 是一个开源的 AI 软件开发代理框架,前身叫 OpenDevin,它能用自然语言驱动一个 Agent 去读写文件、跑命令、浏览网页,最终把一个小需求变成可运行的代码改动。它适合谁?适合想搞明白 Agent 到底怎么运转、又不想从零造轮子的开发者。我选它做拆解对象,不是因为它功能最全,而是因为它的架构足够“透明”:事件流、AgentController、Runtime、Memory 这些模块职责清晰,源码翻起来不费劲,跑起来也快。

但很多人卡在第一步:Agent 跑不起来,或者跑起来了却连不上模型。原因往往不是 OpenHands 本身,而是模型接入这一层没配好——Key 散落在环境变量、配置文件、命令行参数里,换一个模型就要改三处。这篇是系列第一篇,先把核心理念讲清楚,再给出一套可复制的 config.toml 与 settings.json 骨架,用 TaoToken 统一 Key/API 通道接入,最后用一条最小验证动作确认 Agent 真的能发起请求。你跟着做,十分钟内能看到 Agent 回话。

2. OpenHands 的核心理念:事件驱动 + 状态机

2.1 Agent 不是“更聪明的模型”,而是“能闭环的系统”

很多人以为 Agent 就是给 LLM 加几个工具函数。实际跑起来你会发现,真正难的不是让模型生成一段代码,而是让它在“感知 → 规划 → 行动 → 反馈”这个循环里不跑偏。OpenHands 的做法是把整个循环拆成事件:Agent 产生一个 Action(比如“编辑文件”),Runtime 执行后返回一个 Observation(比如“文件已写入”),这两个东西都作为 Event 进入 EventStream,AgentController 再根据最新状态决定下一步。

这个设计的好处是:Agent、Runtime、UI 三者解耦。你可以换一个 Agent 实现,也可以换一个 Runtime(Docker 或本地),只要它们都说“事件”这门语言,就能拼在一起。

2.2 核心组件各管什么

LLM 负责与模型交互,底层走 LiteLLM,所以理论上任何兼容 OpenAI 接口的模型都能接。Agent 负责看当前状态、产生下一个 Action。AgentController 是驱动循环的引擎,它初始化 Agent、管理 State、一步步推进任务。State 是 Agent 的“记忆大脑”,记录当前步骤、历史事件、长期计划,还支持断点恢复。EventStream 是事件中枢,任何组件都能发布或订阅事件。Runtime 提供隔离的执行环境,Sandbox 是其中跑命令的那部分。

把这些串起来的一句话是:ReAct 范式定下“先想再做再收反馈”的行为准则,事件驱动模型搭起系统骨架,State 保证长任务不丢进度。

2.3 为什么接入层值得单独拎出来讲

OpenHands 支持多种 LLM 后端,配置入口有好几个:环境变量、config.toml、settings.json、启动参数。如果你同时用几个模型做对比,或者团队里几个人共用一台开发机,Key 管理很快就会乱。更麻烦的是,有些模型走的是 OpenAI 兼容接口,有些走 Anthropic 风格接口,base_url 和鉴权头都不一样。统一 Key/API 通道的价值就在这里:你只维护一份凭证和一个入口地址,OpenHands 那边只认这一套配置,换模型时改的是模型名,不是接入方式。

3. TaoToken 前置:把 Key 和入口先准备好

TaoToken 在这里扮演的是统一接入层:它提供一个兼容 OpenAI 风格的 API 入口,你拿一个 Key 就能调用多种模型。对 OpenHands 来说,它只需要知道“base_url 指向哪里、api_key 是什么、模型名写什么”,剩下的路由由接入层处理。

你需要先做两件事。第一,在 TaoToken 控制台创建一个 API Key,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。创建时给它起个能认出来的名字,比如 openhands-dev,方便后面轮换。第二,确认你要用的模型名。如果你不确定哪个模型适合 OpenHands 这种需要长上下文和工具调用的场景,可以先去模型对话页试一下: https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。在对话页里发一条带工具调用意图的消息,看它能不能正确返回结构化结果,再决定写进配置。

注意:API Key 只显示一次,创建后立刻复制到安全的地方。不要把它写进会提交到 Git 的配置文件里,后面我会用环境变量引用的方式处理。

如果你打算长期用 OpenHands 做编码任务,建议顺手看一下 Coding Plan 页面,了解配额和模型选择策略: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到参数不确定时以文档为准。

4. 可复制配置:config.toml 与 settings.json 骨架

4.1 先理解 OpenHands 的配置优先级

OpenHands 读取配置的顺序大致是:命令行参数 > 环境变量 > config.toml > 默认值。settings.json 主要用于前端和部分运行时偏好。为了避免“改了文件但不生效”,我的做法是:敏感信息全部走环境变量,config.toml 只写非敏感的模型参数和运行时选项,settings.json 保持最小化。

4.2 config.toml 骨架

在 OpenHands 的工作目录下创建或编辑 config.toml。下面这份是可直接复制的骨架,关键行我都加了注释:

[core] # 工作区路径,按你的实际目录改 workspace_base = "./workspace" # 缓存目录,避免每次重跑都重新拉依赖 cache_dir = "./cache" [llm] # 模型名按 TaoToken 文档里支持的写法填 model = "gpt-4o" # 统一入口,注意这里不带 UTM,API 地址就是纯入口 base_url = "https://taotoken.net/api" # 从环境变量读取,不把 Key 写死在文件里 api_key = "${TAOTOKEN_API_KEY}" # 长任务建议调大,OpenHands 的上下文消耗不低 max_input_tokens = 32768 max_output_tokens = 8192 # 工具调用场景下温度别太高 temperature = 0.2 [agent] # 用默认的 CodeActAgent 即可,后续拆解再换 name = "CodeActAgent" # 最大迭代次数,防止无限循环烧配额 max_iterations = 30 [runtime] # 本地跑用 local,要隔离就换 docker runtime = "local" # 命令超时,单位秒 timeout = 120

这里有两个点容易踩坑。第一,base_url 结尾不要带斜杠,也不要带 /v1,OpenHands 内部会自己拼路径,写多了会 404。第二,api_key 用${TAOTOKEN_API_KEY}这种占位符,前提是你的启动方式支持环境变量展开;如果你直接跑二进制不经过 shell,就把这行改成从环境读取的写法,或者用启动脚本 export 后再启动。

4.3 settings.json 骨架

settings.json 放在 OpenHands 的配置目录下,通常和 config.toml 同级。它的作用是给前端和部分运行时提供偏好,不要在这里重复写 LLM 的 Key:

{ "language": "zh-CN", "theme": "dark", "runtime": "local", "workspace": "./workspace", "llm": { "provider": "openai-compatible", "base_url": "https://taotoken.net/api", "model": "gpt-4o" }, "ui": { "show_events": true, "auto_scroll": true } }

注意 provider 写 openai-compatible,因为 TaoToken 的入口是 OpenAI 风格。model 字段和 config.toml 保持一致,避免两处不一致导致 Agent 初始化时用了错的模型。

4.4 环境变量与启动

在 shell 里导出 Key,然后启动 OpenHands:

export TAOTOKEN_API_KEY="你的Key" # 确认变量已生效,输出应该是你的 Key 前几位 echo ${TAOTOKEN_API_KEY:0:6} # 启动 OpenHands,具体命令按你的安装方式调整 python -m openhands.core.main

如果你用的是 Docker 方式,把环境变量通过 -e 传进去:

docker run -it --rm \ -e TAOTOKEN_API_KEY="${TAOTOKEN_API_KEY}" \ -v "$(pwd)/workspace:/workspace" \ openhands/openhands:latest

5. 验证请求:一条最小动作确认 Agent 能发起请求

配置写完不代表通了。最稳的验证方式是让 Agent 做一个极小的、可观察的动作,而不是直接扔一个复杂需求。我通常用“创建一个文件并写入一行内容”来验证。

启动 OpenHands 后,在交互界面输入:

在当前工作区创建一个名为 hello_agent.txt 的文件,内容写一行:agent is alive

如果接入正常,你会看到事件流里依次出现:Agent 产生一个文件编辑 Action,Runtime 执行后返回 Observation,State 更新,最后界面显示文件已创建。然后你在终端确认:

cat ./workspace/hello_agent.txt # 期望输出:agent is alive

这一步能同时验证三件事:LLM 能收到请求并返回结构化 Action,Runtime 能执行文件操作,EventStream 能把结果回传给 Agent。如果文件没出现,或者事件流里只有 Action 没有 Observation,问题基本出在接入层或 Runtime,而不是 Agent 逻辑。

想更直接地确认模型通道本身是否通,可以单独发一条 curl:

curl -s https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer ${TAOTOKEN_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "reply with ok"}], "max_tokens": 10 }'

返回里能看到 choices 字段就说明 Key 和入口都没问题。这一步和 OpenHands 无关,但能帮你快速定位是接入层的问题还是框架配置的问题。

6. 本篇常见错排查

6.1 401 或 invalid api key

最常见的原因是环境变量没传进 OpenHands 进程。如果你在 shell 里 export 了,但用 systemd 或 IDE 启动,环境变量不会自动继承。解决方式是显式在启动脚本里 export,或者用 .env 文件配合加载。另一个原因是 Key 复制时带了空格或换行,用echo ${TAOTOKEN_API_KEY:0:6}检查前几位,再和创建时对比。

6.2 404 或 model not found

先检查 base_url 是不是写成了https://taotoken.net/api/或https://taotoken.net/api/v1。正确写法是https://taotoken.net/api,不带尾斜杠,不带版本段。然后检查 model 名是否在 TaoToken 支持的列表里,写错一个字符就会 404。如果模型名对但依然报错,去接入文档确认该模型是否需要额外的请求头。

6.3 Agent 一直循环不结束

这通常不是接入问题,而是 max_iterations 设太大加上任务描述太模糊。先把 max_iterations 降到 10 做测试,任务描述尽量具体,比如“在 workspace 下创建 a.txt 并写入 123”,而不是“帮我整理一下项目”。如果循环里反复出现同一个 Action,说明 Observation 没有被正确回传,检查 Runtime 是否真的执行了命令。

6.4 文件写到了错误的位置

OpenHands 的 workspace_base 和 settings.json 里的 workspace 要指向同一个目录。如果两处不一致,Agent 可能把文件写到默认路径,你在预期目录里找不到。用pwd和ls确认当前工作目录,再对照配置里的相对路径。

6.5 长任务中途断掉后无法恢复

State 支持断点恢复,但前提是 cache_dir 和 workspace 没有被清空。如果你每次启动都挂载一个全新的临时目录,历史状态就丢了。做长任务时把 workspace 和 cache 挂到宿主机固定路径,重启后 Agent 能从上次的 State 继续。

7. 下一步:把统一 Key 用在长期编码任务上

这一篇的重点是核心理念和接入骨架,验证动作只做到“Agent 能发起请求”。如果你打算把 OpenHands 当成日常编码助手,下一步要处理的是配额、模型切换和会话管理。统一 Key 的好处在这里会放大:你不需要为每个模型维护一套凭证,换模型只改 config.toml 里的 model 字段,base_url 和 api_key 不动。

长期跑编码任务前,建议先去 Coding Plan 页面确认配额策略: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。如果你更想先手动试几个模型再决定,模型对话页可以直接对比输出质量: https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。Key 管理和轮换在控制台: https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。接入参数以文档为准: https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。

下一篇我会拆 OpenHands 的 EventStream 和 AgentController,把“一个 Action 从产生到执行再到 Observation 回传”的完整链路用日志和源码对照讲清楚。你现在要做的,是把这篇的配置跑通,确认 hello_agent.txt 真的被写出来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询