一次代码修复背后
当你让 OpenHands "修复这个 Bug",30 秒内到底发生了什么?
你用一句话改了代码,但背后是一台机器在运作
OpenHands 是一个开源 AI 编程 Agent 平台,它让 LLM(大语言模型)不只是"说",而是真正地打开文件、运行代码、提交 Git、操作浏览器。GitHub 评分超过 40K 星。
想象你发现了一个网站 Bug:登录按钮在移动端点击没反应。你对 OpenHands 说"帮我修这个 Bug",它不是给你一段代码让你复制粘贴 —— 它打开你的项目文件、定位问题、修改代码、运行测试、提交 PR,一气呵成。
理解这套架构,你就能更精准地指挥 AI 做复杂任务、快速定位"为什么 AI 卡住了",以及判断什么任务适合交给 Agent,什么必须自己来。
从你发送消息,到代码被提交 —— 完整旅程
让我们跟着一次真实请求,看每一步发生了什么。
真实代码长什么样?
这是 OpenHands 启动一个 Agent 会话的核心代码。左边是源码,右边是白话翻译:
async def create_conversation(
request: Request,
session_id: str = uuid4(),
):
# 创建隔离的代码运行环境
runtime = await create_runtime(config)
# 启动 Agent Controller 管理对话
controller = AgentController(
agent=agent,
runtime=runtime,
event_stream=stream,
)
return {"session_id": session_id}
快速测验:你理解了多少?
Agent 的演员阵容
OpenHands 里有哪些"角色"?它们各自负责什么?
把 OpenHands 想象成一个工程师团队
一个软件公司要修复 Bug 需要多个角色:有人理解需求、有人写代码、有人运行测试、有人管理代码仓库。OpenHands 的架构也是这样 —— 不同组件扮演不同角色,通过明确接口协作。
这不只是一个"AI 聊天机器人",而是一套精心设计的多层架构,每个组件都有精确的职责边界。
组件之间是怎么"说话"的?
当你发一条消息,这些组件的一次真实内部对话看起来是这样的:
源码在哪里找?
如果你想深入某个组件,这是项目关键文件的地图:
当你让 AI 修改 OpenHands 的某个功能时,用组件名称精确描述位置:"在 AgentController 里加一个超时限制" 比 "让 AI 不要一直转圈" 能让 AI 更准确理解你的意图。
拖拽练习:为每个组件匹配职责
信息如何流动
EventStream:OpenHands 的神经系统
像电台广播一样的事件总线
想象一个城市的电台系统:交通台、新闻台、音乐台各自播出内容,司机只收听自己感兴趣的频道,无需电台知道谁在听。这就是 EventStream(事件总线) 的工作方式。
在 OpenHands 里,每次 Agent 思考、执行、报错、完成,都会向 EventStream 发布一个事件。前端、后端、其他 Agent 各自订阅自己关心的事件,无需任何直接连接。
EventStream 让整个对话变成了可重放的历史记录。断线重连?从最后一个事件继续。调试 Agent 行为?查看完整事件序列。这是生产级 AI 系统的必备设计。
OpenHands 里有哪些事件类型?
事件分两大类:Action(动作)是 Agent 决定要做什么,Observation(观察)是执行后得到的反馈。
Agent 向用户说一句话,或用户发了新消息。对话的起点。
运行一条 Shell 命令,比如 `git diff`、`pytest tests/`、`npm install`。
写入文件内容。Agent 修复完代码后,把新内容写回磁盘。
读取文件内容。Agent 理解问题前,通常先读相关文件。
执行某个动作时出错。Agent 会把错误信息纳入下一轮推理。
Agent 认为任务完成,给出最终答案。触发前端显示结果。
事件如何被记录和传播?
class EventStream:
def add_event(
self,
event: Event,
source: EventSource,
):
# 给事件分配递增的 ID
event._id = self._cur_id
self._cur_id += 1
# 持久化存储
self.file_store.write(event)
# 通知所有订阅者
for listener in self._subscribers:
listener(event)
场景判断:这是 Action 还是 Observation?
沙箱安全机制
为什么 AI 写的代码可以安全执行?Docker Runtime 揭秘
如果没有沙箱,会发生什么?
假设你让 AI 帮你"清理无用文件",它写出了 rm -rf /(删除全部文件),然后直接在你的电脑上执行。结果就是… 什么都没了。
这不是假设 —— 在 AI 真正获得"执行权"之前,这是一个必须解决的核心安全问题。OpenHands 的答案是:所有代码执行都发生在 Docker 容器内部,容器就是一个"一次性工作间"。
容器内的文件系统、网络、进程与宿主机完全隔离。Agent 删光容器内的文件,你的电脑丝毫无损。
你的项目文件通过"挂载"方式映射到容器,Agent 只能访问你授权的目录,无法触碰其他地方。
对话结束后容器销毁,临时文件、安装的软件包、环境变量全部消失,下次任务从干净状态开始。
可以配置容器的网络访问权限:只允许访问特定域名,或完全隔离网络防止数据泄露。
Agent 在沙箱里能做什么?
OpenHands 给 Agent 提供了一套"工具箱",每个工具都对应一种能力。这些工具通过 MCP(Model Context Protocol) 进行标准化描述。
你可以给 OpenHands 添加自定义 MCP 工具,比如接入内部数据库、私有 API、或者特殊的代码分析工具。Agent 会自动发现并使用这些工具。
Agent 如何在沙箱里执行代码?
# Agent 生成的行动
action = CmdRunAction(
command="pytest tests/ -v",
timeout=60,
)
# DockerRuntime 在容器里执行
obs = await runtime.run_action(action)
# obs.output = "FAILED test_login"
# obs.exit_code = 1
# Observation 放入 EventStream
event_stream.add_event(obs)
安全判断:哪个操作需要沙箱隔离?
LLM 与工具调用
大脑是如何和手连接起来的?
LLM 的"思考-行动"循环
LLM 本质上只能生成文字。那它是怎么执行代码的?答案是 工具调用(Tool Use):LLM 生成"我要执行 X 命令"的指令,由 OpenHands 的 Runtime 真正去执行,再把结果返回给 LLM。
这个循环就像一个外科医生(LLM)和一个手术助理(Runtime)的配合:医生说"给我那把钳子",助理取来,医生再说下一步。
LLM 看到任务描述和工具列表
LLM 决定调用哪个工具、传什么参数
Runtime 在沙箱里执行工具调用
结果回传 LLM,进入下一轮推理
CodeAct:用代码作为行动语言
OpenHands 采用了一种叫 CodeAct 的范式:LLM 直接生成 Python 代码作为"行动",代码在沙箱里执行,结果返回给 LLM。
这比"用 JSON 描述工具调用"更强大:Python 代码可以包含逻辑判断、循环、复杂数据处理,把多步操作合并成一次执行。
# LLM 生成这段 Python 代码
import os
# 找到所有 .py 文件里的 TODO
todos = []
for root, dirs, files in os.walk("."):
for f in files:
if f.endswith(".py"):
content = open(f).read()
if "TODO" in content:
todos.append(f)
print(todos)
JSON 工具调用每次只能调用一个工具,复杂任务需要多轮往返。Python 代码可以在一次执行里完成多步逻辑,还能使用条件判断和循环。同等任务,CodeAct 通常减少 50% 以上的 LLM 调用次数。
LLM 的"内心独白"是什么样的?
OpenHands 用 Chain of Thought 让 LLM 先"思考"再"行动"。这是一次修复 Bug 时的内心独白:
测验:你的 AI 指令够精确吗?
当 Agent 卡住了
如何读懂 OpenHands 的行为,打破 AI 的 Bug 循环
Agent 为什么会"转圈"?
OpenHands Agent 有时会陷入循环:反复尝试同一种修复方法、不断读取同一个文件、或者做出超出能力范围的承诺然后卡死。理解为什么会发生这些,你就能有效干预。
对话历史太长,超过 LLM 的 Token 限制。Memory 模块压缩历史时可能丢失关键信息,导致 Agent 忘记之前的结论。
某个工具调用总是失败(比如权限问题),但 Agent 不断重试相同的方式。ErrorObservation 没有被正确利用。
你说"帮我优化性能",Agent 不知道从哪入手,在多个方向间摇摆,什么都做了一点,什么都没做完。
安装的包版本冲突、系统依赖缺失,Agent 尝试修复但沙箱里缺少必要工具,陷入"修了这里、坏了那里"的循环。
如何读懂 Agent 的行为日志?
OpenHands 把每一步都写入 EventStream,这是你诊断问题的最好工具。学会看这些关键信号:
找出让 Agent 卡死的那行"事件"
下面是一段真实的 EventStream 日志。点击你认为导致 Agent 循环的那一行:
FileReadAction: path="package.json" → OK (2.1KB)
CmdRunAction: "npm install" → exit_code=0
ErrorObservation: EACCES: permission denied '/usr/local/lib'
CmdRunAction: "npm install -g typescript" → exit_code=1
MessageAction: "Retrying installation with elevated permissions..."
打破循环的三种方法
直接发消息"停下,忽略之前的思路,改用方法 B"。Agent 会把你的新消息加入上下文,重新推理。
如果 Agent 在循环猜测某个值(API key、文件路径、权限配置),直接告诉它正确答案,比让它自己试探更高效。
一个 3 分钟无进展的大任务,拆成 3 个小任务分别完成。每个步骤有明确的完成标准,AI 不容易偏离。
在 OpenHands 配置里,max_iterations 控制最大迭代次数(默认 100)。如果一个任务超过 15-20 步还没结束,通常意味着任务太复杂或描述不清,应该手动干预而不是继续等待。
最终测验:综合诊断
OpenHands 的完整架构:EventStream 事件总线、Docker 沙箱隔离、CodeAct 工具调用范式、AgentController 状态机,以及如何诊断和打破 Agent 的 Bug 循环。这些知识将帮助你更自信地指挥 AI 完成复杂工程任务。