OpenHands 架构解析:从零实现一个 AI 编程 Agent
目标读者:中高级开发者、想搞懂 AI Agent「底层怎么转」的工程师
预计阅读:15~20 分钟
关键词:OpenHands、AI Agent、Action-Observation、自主编程、LLM Tool Calling
写在前面
2026 年,AI 编程已经从「补全一行代码」走到了「交给 Agent 改仓库、跑测试、修报错」。
GitHub 数据里,AI 代码生成工具使用率从 2024 年的约 32% 飙到 67%+;微软研究院统计,AI 生成代码占比约 41%。
商业侧有 Claude Code、Cursor、Trae、Copilot;开源侧则有一众「能动手干活」的 Agent:OpenHands、Aider、Cline、Goose、Devika……
其中 OpenHands(GitHub 40K+ Stars)特别适合做架构学习:它把「大模型思考 → 选工具 → 执行 → 观察结果 → 再思考」这条闭环,做成了可拆、可扩、可落地的工程框架。
本文将完成三件事:
- 把 OpenHands 的核心架构讲清楚
- 用最少代码「从零」搭一个可跑的迷你编程 Agent
- 指出可改进点,以及如何把它落到你自己的业务里
配套科普文:《2026 AI Agent 框架全景:OpenHands / Aider / Cline / Goose 对比与落地指南》(同目录第二篇)。
一、先搞清:什么是「AI 编程 Agent」
很多人把「聊天写代码」和「Agent」混为一谈。区别很简单:
| 形态 | 能力边界 | 典型产品 |
|---|---|---|
| 补全助手 | 根据光标上下文建议下一行 | Copilot 补全、通义灵码补全 |
| 对话助手 | 解释代码、生成片段,你手动粘贴 | ChatGPT / 侧边栏对话 |
| 编程 Agent | 自己读文件、改文件、跑命令、看报错、再改 | OpenHands、Claude Code、Aider、Devika |
Agent 的本质不是「更聪明的聊天」,而是:
目标(Goal)
→ 推理(Reason)
→ 行动(Action / Tool Call)
→ 观察(Observation)
→ 再推理……直到完成或失败
OpenHands 把这条循环做成了事件驱动、类型安全、可安全拦截的工程系统。
二、OpenHands 整体架构(一张图看懂)
结合 OpenHands Software Agent SDK 的公开架构(参见 docs.openhands.dev 与相关论文),可以抽象成:
┌─────────────────────────────────────────────────────────────┐
│ Conversation(会话) │
│ 管理生命周期 / 事件日志 / 暂停恢复 / 状态持久化 │
└───────────────────────────┬─────────────────────────────────┘
│ step()
┌───────────────────────────▼─────────────────────────────────┐
│ Agent(无状态推理引擎) │
│ · 读事件历史 → 可选 Condenser 压缩上下文 │
│ · 查询 LLM → 得到 Message 或 Tool Call │
│ · Security Analyzer 评估风险 → 是否需人工确认 │
│ · 调用 Tool Executor │
└───────┬───────────────────────────────┬─────────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌──────────────────┐
│ LLM Provider │ │ Tool System │
│ 多模型适配 │ │ Action→Observation│
└───────────────┘ └────────┬─────────┘
│
┌────────▼─────────┐
│ Workspace │
│ 本地 / Docker / │
│ 远程沙箱执行环境 │
└──────────────────┘
2.1 五个关键组件
| 组件 | 职责 | 为什么重要 |
|---|---|---|
| Conversation | 会话状态与事件流 | 可暂停、可回放、可断点续跑 |
| Agent | 单步推理循环 | 无状态:配置与执行状态分离,便于序列化/远程部署 |
| LLM | 模型调用封装 | 重试、遥测、多厂商兼容 |
| Tool System | Action / Observation / Executor | 工具有类型、可校验、可扩展(含 MCP) |
| Workspace | 代码真正跑在哪 | 本地试验 vs 容器隔离 vs 远程生产 |
2.2 核心心智模型:Action → Observation
OpenHands 工具不是「随便调个函数」,而是严格契约:
- LLM 提出 JSON Tool Call
- 框架校验并解析成 Action(输入 schema)
- ToolExecutor 在 Workspace 里执行
- 返回 Observation(输出 schema)
- Observation 作为事件写回历史,下一轮再喂给 LLM
这和 ReAct、Toolformer 一脉相承,但 OpenHands 把它工程化了:校验、安全、事件溯源、压缩、MCP 统一入口。
三、单步循环:Agent 的一次 step() 在干什么
可以把一次 step() 理解成:
1. Conversation 把当前事件视图交给 Agent
2. (可选)Condenser 压缩过长历史,避免上下文爆掉
3. 注入 Skills / System Prompt / 可用工具列表
4. 调 LLM:下一步该干什么?
5. 若返回纯文本 → 本轮结束,回复用户
6. 若返回 Tool Call:
a. Security Analyzer 打风险等级
b. 确认策略:自动执行 / 询问人 / 拒绝
c. Executor 执行 → Observation
d. 写入事件日志
7. 回到 1,直到任务完成或达到步数上限
对应时序(简化):
你 ──► Conversation:"帮我建 hello.txt"
│
▼
Agent ──► LLM:"下一步?"
│◄── BashTool("touch hello.txt")
│
▼
Security OK?──► Tool 在 Workspace 执行
│◄── Observation: 成功
│
▼
Agent ──► LLM:"还要继续吗?"
│◄── Done
▼
Conversation ──► 你:"文件已创建"
设计亮点:
- 事件溯源(Event Sourcing):交互以不可变事件追加到日志,可回放、可审计
- 安全插入点:危险命令(
rm -rf、推远程、改权限)可在执行前拦截 - 增量执行:一步一步走,支持 pause/resume、上下文溢出后的压缩恢复
四、从零实现:一个「迷你 OpenHands」
下面用 Python 100 行量级 复刻核心闭环。它不是 OpenHands 的 fork,而是用来理解架构:读文件、写文件、跑命令、看结果、再决策。
实战提示:生产环境请用官方 OpenHands / Claude Code 等;迷你版只适合学习与二次开发原型。
4.1 定义 Action / Observation
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Callable, Literal
import json, subprocess, os
from pathlib import Path
@dataclass
class Action:
tool: str
args: dict[str, Any]
@dataclass
class Observation:
tool: str
ok: bool
content: str
@dataclass
class Event:
role: Literal["user", "assistant", "tool", "system"]
content: str
meta: dict[str, Any] = field(default_factory=dict)
4.2 工具执行器(Workspace)
class MiniWorkspace:
def __init__(self, root: str):
self.root = Path(root).resolve()
self.root.mkdir(parents=True, exist_ok=True)
def _safe(self, rel: str) -> Path:
p = (self.root / rel).resolve()
if not str(p).startswith(str(self.root)):
raise ValueError(f"路径越界: {rel}")
return p
def execute(self, action: Action) -> Observation:
try:
if action.tool == "read_file":
text = self._safe(action.args["path"]).read_text(encoding="utf-8")
return Observation("read_file", True, text[:8000])
if action.tool == "write_file":
path = self._safe(action.args["path"])
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(action.args["content"], encoding="utf-8")
return Observation("write_file", True, f"written: {path}")
if action.tool == "bash":
cmd = action.args["command"]
# 教学版:简单黑名单;生产请用沙箱 + 确认策略
if any(x in cmd for x in ["rm -rf /", "mkfs", ":(){:|:&};:"]):
return Observation("bash", False, "拒绝执行高危命令")
r = subprocess.run(
cmd, shell=True, cwd=self.root,
capture_output=True, text=True, timeout=30,
)
out = (r.stdout or "") + (r.stderr or "")
return Observation("bash", r.returncode == 0, out[:8000])
return Observation(action.tool, False, f"未知工具: {action.tool}")
except Exception as e:
return Observation(action.tool, False, str(e))
4.3 安全分析(极简版)
def risk_of(action: Action) -> str:
"""返回 low / medium / high"""
if action.tool == "bash":
cmd = action.args.get("command", "")
if any(k in cmd for k in ["rm ", "sudo", "curl | sh", "git push"]):
return "high"
return "medium"
if action.tool == "write_file":
return "medium"
return "low"
4.4 Agent 循环(核心)
下面用「伪 LLM」接口:你可接 OpenAI / Anthropic / 云雾等兼容 API。关键是:让模型返回严格 JSON。
TOOLS_SPEC = """
你是编程 Agent。每次只能输出一个 JSON,不要 Markdown:
{"type":"message","content":"..."} 或
{"type":"action","tool":"read_file|write_file|bash","args":{...}}
可用工具:
- read_file: {"path":"相对路径"}
- write_file: {"path":"...","content":"..."}
- bash: {"command":"..."}
完成任务时用 type=message 总结。
"""
class MiniAgent:
def __init__(self, llm_call: Callable[[list[Event]], str], workspace: MiniWorkspace, max_steps=12):
self.llm_call = llm_call
self.workspace = workspace
self.max_steps = max_steps
self.events: list[Event] = [
Event("system", TOOLS_SPEC),
]
def run(self, goal: str) -> str:
self.events.append(Event("user", goal))
for step in range(self.max_steps):
raw = self.llm_call(self.events)
try:
data = json.loads(raw)
except json.JSONDecodeError:
self.events.append(Event("assistant", raw))
return f"模型未返回合法 JSON:{raw[:500]}"
if data.get("type") == "message":
msg = data.get("content", "")
self.events.append(Event("assistant", msg))
return msg
action = Action(tool=data["tool"], args=data.get("args", {}))
risk = risk_of(action)
if risk == "high":
# 生产里应弹窗确认;这里直接拒绝示例
obs = Observation(action.tool, False, f"高风险操作已拦截: {action}")
else:
obs = self.workspace.execute(action)
self.events.append(Event("assistant", json.dumps(data, ensure_ascii=False), {"action": True}))
self.events.append(Event("tool", obs.content, {"ok": obs.ok, "tool": obs.tool}))
print(f"[step {step+1}] {action.tool} risk={risk} ok={obs.ok}")
return "达到最大步数,任务未完成"
4.5 接上真实 LLM(示例)
# pip install openai
from openai import OpenAI
client = OpenAI(base_url="https://yunwu.ai/v1", api_key=os.environ["API_KEY"])
def llm_call(events: list[Event]) -> str:
messages = [{"role": e.role if e.role != "tool" else "user",
"content": f"[{e.role}] {e.content}"} for e in events]
resp = client.chat.completions.create(
model="gpt-5.5", # 或你有权限的模型
messages=messages,
temperature=0.2,
)
return resp.choices[0].message.content.strip()
if __name__ == "__main__":
ws = MiniWorkspace("./sandbox")
agent = MiniAgent(llm_call, ws)
print(agent.run("在 sandbox 下创建一个 Python 文件 hello.py,打印 Hello Agent,并运行它。"))
跑通后你会清晰看到:
[step 1] write_file risk=medium ok=True
[step 2] bash risk=medium ok=True
任务完成:已创建并执行 hello.py
这就是 OpenHands 的「魂」:不是一次生成完所有代码,而是边做边看、边看边改。
五、OpenHands 相对「手写循环」多了什么
迷你版能跑,但生产级 OpenHands 还补齐了这些能力:
| 能力 | 迷你版 | OpenHands |
|---|---|---|
| 事件持久化 / 回放 | 内存 list | 事件溯源日志 |
| 上下文压缩 Condenser | 无 | 有,长任务刚需 |
| 安全分析器 | 关键词黑名单 | Pattern / LLM / Ensemble |
| 沙箱 | 本地目录 | Docker / 远程 Workspace |
| 工具扩展 | 3 个硬编码 | 插件化 + MCP |
| Skills | 无 | 可注入领域技能 |
| 无状态 Agent | 状态混在循环里 | Agent 配置可序列化、可远程调度 |
这也是为什么说:理解迷你循环 → 再读 OpenHands 源码,事半功倍。
六、优缺点与改进方向
6.1 优点
- 架构清晰:Action-Observation + 事件驱动,可教可扩
- 开源可私有化:适合企业二次开发与安全审计
- 工具生态:原生工具 + MCP,外部能力可插拔
- 生产意识:安全确认、压缩、pause/resume 都考虑到了
6.2 局限
- 学习曲线高于「装个 IDE 插件就能用」
- 对模型 Tool Calling 质量敏感:弱模型会乱调工具、死循环
- Token 成本:长仓库 + 多步循环,cache/上下文费用上升快
- 与 Claude Code / Cursor 等「深度 IDE 体验」仍有差距
6.3 可改进方向(给你二次开发时参考)
- 小模型路由:简单读文件用 Haiku/Flash,复杂重构再上 Sonnet/GPT 旗舰
- 更强 Condenser:按「已确认事实摘要」压缩,而不是粗暴截断
- 测试驱动闭环:强制「改代码 → 跑测试 → 失败再修」作为默认 Skill
- A2A 多 Agent:规划者 / 编码者 / 审查者分工(见下一篇科普)
- 企业 Harness:把团队规范(分层、命名、禁止事项)做成 Skills + 安全策略
七、如何落地实操体验
7.1 最快体验路径
- 打开仓库:OpenHands/OpenHands
- 按官方文档用 Docker 启动(推荐沙箱)
- 配置 LLM API(官方 Anthropic / OpenAI,或兼容中转)
- 给一个小目标:「给当前目录加一个 hello 单元测试并跑通」
- 观察日志里的 Action / Observation,对照本文第四节
7.2 和日常工具怎么搭配
| 场景 | 更合适的工具 |
|---|---|
| IDE 里边写边问 | Cursor / Trae / Copilot |
| 终端 Git 友好改码 | Aider |
| 学习 Agent 架构 / 二次开发 | OpenHands |
| 企业复杂任务、高代码质量 | Claude Code |
| 要把公司内部系统接到 Agent | MCP Server + OpenHands/Claude |
7.3 团队落地建议(供应链金融 / 企业研发可参考)
- 先沙箱后生产:Agent 默认只动指定仓库副本
- 高风险命令必须人审:
git push、数据库变更、删文件 - 任务粒度:一次只交一个 Story,不要「重构整个贷后模块」
- 度量:记录步数、Token、一次通过率、回滚率
- 规范进 Skills:把你们的 Java/Vue Harness 规则写成可注入技能
八、小结
OpenHands 值得学,不是因为它「一定比某个商业产品更强」,而是因为它把 AI 编程 Agent 的通用骨架摊开了:
Conversation(状态)
+ Agent(推理循环)
+ LLM(决策)
+ Tools(行动)
+ Workspace(执行环境)
+ Security / Condenser / Skills(工程化护栏)
你自己手写一个迷你版,再对照官方架构,很快就能:
- 看懂 Claude Code / Cursor Agent / Trae SOLO 在干什么
- 评估团队该「买成品」还是「基于开源二次开发」
- 把 MCP、Skills、多 Agent 接到自己的工具链上
参考资料
- OpenHands 架构文档:https://docs.openhands.dev/sdk/arch/overview
- OpenHands GitHub:https://github.com/OpenHands/OpenHands
- MCP 协议:https://modelcontextprotocol.io/
更多推荐



所有评论(0)