Day 15 / 共 20 天 · 第 3 周收官
CodeAct 范式精读
OpenHands 的招牌思想。默认 Agent 叫 CodeActAgent——它把"写并执行代码"作为核心动作空间。今天讲清 CodeAct 为什么比"一堆固定工具"更强大,收官 Agent 大脑。
📍 第 3 周 Agent 大脑(SDK)· 你在这里
Day11 SDK概念→Day12 工具体系→Day13 Runtime→Day14 LLM&提示词→Day15 CodeAct
L01
CodeAct 是什么
CodeAct = Code as Action(以代码为动作)。核心主张:与其给 Agent 一堆各式各样的固定工具,不如让它"写代码并执行"——因为代码本身就是最通用、最强大的动作。OpenHands 默认 Agent(default_agent = "CodeActAgent",Day 04)正是这个范式的实现。
先建直觉
想让 Agent 完成"统计这个目录下所有 .py 文件的总行数"。固定工具派:得预先造一个
count_lines 工具、还要一个 list_files 工具、可能还要 filter_by_extension……每种需求造一个工具,造不完。CodeAct 派:直接让 Agent 写一行 find . -name '*.py' | xargs wc -l 并执行——一条命令搞定,不需要任何专门工具。代码/命令是"图灵完备"的——理论上能表达任何操作。给 Agent 一个终端,胜过给它一百个专用按钮。🤔 痛点:CodeAct 听起来抽象,它在代码里到底"长什么样"?"以代码为动作"是个理念,但落到 OpenHands 里,一个"动作"具体是什么数据?光讲思想容易悬空。
💡 本质:CodeAct = LLM 用"函数/工具调用"表达动作,而非自定义 DSL。看前端类型
CodeAct 就像生活中的"让老员工直接上手干":固定工具派像是"员工每做一件事都得填一张专用申请表(借用表、报销表、请假表……)",表格永远不够用;CodeAct 派则是"给员工一台电脑和权限,让他自己动手操作"——想干啥直接干,不受表格种类限制。
frontend/src/types/v1/core/base/action.ts 里的 ExecuteBashAction/FileEditorAction/MCPToolAction——它们就是这些"工具调用动作"的落地形态。而 action-event.ts 里的 ActionEvent 带 thought/tool_name/tool_call 三个字段,正是 CodeAct 的"先想(thought)再调工具(tool_call)"。这和你 Day 02 学的 Action、Day 12 学的工具体系是同一件事,只是换了个"以代码为核心动作"的视角看。CodeAct 就像生活中的"让老员工直接上手干":固定工具派像是"员工每做一件事都得填一张专用申请表(借用表、报销表、请假表……)",表格永远不够用;CodeAct 派则是"给员工一台电脑和权限,让他自己动手操作"——想干啥直接干,不受表格种类限制。
L02
固定工具 vs 代码动作
❌ 纯固定工具派
- 每种能力造一个专用工具
- 工具越堆越多,LLM 在几十个工具里容易选晕
- 组合能力弱:想"先筛选再统计"得有组合工具
- 遇到没预置工具的需求就卡住
✅ CodeAct(代码为动作)
- 核心就几个:跑 bash/写代码、编辑文件
- 任意逻辑用代码表达——图灵完备
- 组合天然:管道、循环、条件全都行
- 遇到新需求,现写代码即可
读法:固定工具像"给你一堆专用厨具(切蒜器、开瓶器…)",CodeAct 像"给你一把好菜刀 + 会用刀的手"。专用厨具用起来顺手但有限,好菜刀 + 手能应付任何食材。OpenHands 选了后者为主。
👶💬 对话体:为什么让模型"写代码调用"而不是"从菜单里选一个动作"?
👶 小白:给模型一个下拉菜单,让它选"删文件/读文件/跑命令",不是更省事、更可控吗?
👨🏫 老师:菜单能列的动作是有限的,真实任务千变万化,总有菜单里没有的。而"写代码"能表达无限种操作。
👶 小白:可写代码不是更容易出错?
👨🏫 老师:恰恰相反——大模型是在海量代码上训练的,"写代码"是它最擅长的事;反而"在几十个菜单项里精确选一个、填对参数"更容易选晕。让它用最拿手的方式行动,命中率更高。
👨🏫 老师:菜单能列的动作是有限的,真实任务千变万化,总有菜单里没有的。而"写代码"能表达无限种操作。
👶 小白:可写代码不是更容易出错?
👨🏫 老师:恰恰相反——大模型是在海量代码上训练的,"写代码"是它最擅长的事;反而"在几十个菜单项里精确选一个、填对参数"更容易选晕。让它用最拿手的方式行动,命中率更高。
L03
代码作为动作的威力
- 表达力无上限:任何计算、文件操作、数据处理,一段代码就能表达,不受预置工具限制。
- 组合天然:管道
|、循环、条件判断——编程语言天生擅长组合,Agent 一步就能表达复杂逻辑。 - 复用生态:能
pip install任何库、调用任何命令行工具——整个开源生态都是 Agent 的工具箱。 - LLM 本来就擅长写代码:大模型在海量代码上训练过,"写代码"是它最强的能力之一。让它用最擅长的方式行动。
最后一点尤其妙
LLM 见过无数 Python/bash 代码,"写代码"这件事它极其在行。而"从 50 个自定义工具里精确选一个、填对参数"反而容易出错。CodeAct 让 Agent 用它最训练有素的方式(写代码)来行动,命中率自然高。这是 CodeAct 论文的核心洞察:动作空间对齐 LLM 的强项。
L04
OpenHands 的实际取舍(不是纯粹主义)
OpenHands 没有极端到"只有一个 bash 工具"。它以 CodeAct 为核心,但也保留了几个精心设计的专用工具(Day 02/12 的动作全家福):
- bash 执行:CodeAct 的核心——万能。
- 文件编辑(str_replace):为什么不直接用 bash 的 sed?因为 str_replace 更可靠、能生成 diff、可撤销(Day 02/12)。
- 浏览器、搜索(glob/grep):高频操作做成专用工具,比每次写代码更省 token、更稳。
务实的平衡:核心用 CodeAct(通用),高频/需要可靠性的操作用专用工具(好用)。"能用代码解决的绝不造工具,但高频关键操作值得打磨专用工具"——这个平衡是 OpenHands 工程成熟度的体现。纯理论派会说"只要 bash 就够",但实践中 str_replace 的 diff、glob 的结构化结果,确实比裸 bash 更适合 Agent。
💡 简化版 → 真实版:一个"动作"要带多少信息
如果让你实现,"执行一个动作"你大概会写:
os.system(cmd) 一行了事。但真实的 ActionEvent(action-event.ts)远不止命令本身,多出来的每个字段都在解决一个问题:thought(可解释——它为什么这么做)、tool_name/tool_call(结构化——到底调了哪个工具、参数是啥)、security_risk(安全——这动作多危险,Day 17 用)、id(配对——结果回来时靠它找到这条动作,Day 16 用)。朴素做法只管"跑起来",工程级做法还要管"可解释、可审计、可配对、可控风险"。L05
一次 CodeAct 循环(把全周串起来)
任务:"找出项目里最大的 3 个文件"。CodeActAgent 的实际运转:
# step 1: LLM 决定写命令(CodeAct)→ ExecuteBashAction
$ find . -type f -printf '%s %p\n' | sort -rn | head -3
# → ExecuteBashObservation: "1048576 ./data.bin\n524288 ./model.pkl\n..."
# step 2: LLM 看到结果,判断任务完成 → FinishAction
# "最大的三个文件是 data.bin(1MB)、model.pkl(512KB)、..."
读法:一条命令 + 一次判断,任务就完成了——不需要任何"找大文件"的专用工具。这一步用到了:LLM 智力(Day 14)决定写什么命令 → 工具体系(Day 12)的 bash 工具 → Runtime(Day 13)在沙箱执行 → 产生 Action/Observation(Day 02)→ 进事件流(Day 03)→ app_server 记录(Day 08)→ 前端实时显示。全周知识在这一条命令里全用上了。
📝 举个例子任务"找最大的 3 个文件"。LLM 产出一条
ActionEvent:thought="我该用 find 排序" + tool_name="execute_bash" + tool_call=find . -type f -printf '%s %p\n' | sort -rn | head -3 → 沙箱执行 → 回来一条 ObservationEvent:1048576 ./data.bin ... → LLM 判断完成,产出 FinishAction。整个过程没有任何"找大文件"专用工具,全靠一条 bash。图:CodeAct 循环把 LLM 智力、沙箱执行、事件模型黏合成自主闭环
L06
为什么 OpenHands 在 SWE-bench 领先
SWE-bench 是衡量"AI 解决真实 GitHub issue 能力"的权威榜单。OpenHands 长期是开源方案的领先者,CodeAct 是重要原因:
- 真实 bug 修复千变万化,固定工具覆盖不全,CodeAct 的通用性正好对症。
- 能跑测试验证(
pytest)→ 看结果 → 自我纠错,闭环完整。 - 持久终端 + 完整开发环境(Day 12)让它像真人程序员一样操作。
本质:修 bug 这件事,人类程序员就是"看代码、改、跑测试、再改"——CodeAct + 沙箱 + 事件循环,把这个人类工作流忠实地搬给了 AI。动作空间越接近人类程序员的真实操作,Agent 越强。CodeAct 的胜利,是"让 AI 用人类最自然的方式(写代码、敲命令)干活"的胜利。
一句话复述
CodeAct 就像生活中"能干的老师傅":你不用给他 100 个专用工具,给他一间备齐材料的车间(终端 + 沙箱),他自己就能拼出任何解法。⚠️ 小白常误以为"OpenHands 强是因为工具多",其实恰恰相反——它强在工具少而通用,把复杂度交给了 LLM 最擅长的"写代码"。
L07
全周知识串联(一图流)
一个 CodeActAgent 完成任务,用到你第 3 周学的所有东西:
- Day 11 Agent SDK:step 循环、感知历史、产出 Action
- Day 12 工具:bash 为核心动作(CodeAct)+ 专用工具(str_replace/glob)
- Day 13 Runtime:在沙箱里真正执行代码、返回 Observation
- Day 14 LLM:litellm 接模型、流式、系统提示、成本控制
- Day 15 CodeAct:以代码为动作,把一切黏合成强大的自主循环
再往前,这一切又建立在第 1 周的 Action/Observation 事件模型、第 2 周的 app_server 编排之上。从"一条 find 命令"能追溯到整个系统每一层——你已经读懂了一个 SOTA 级自主 AI 软件工程师的骨架。
L08
🎓 第 3 周收官 + 动手
第 3 周(Day 11-15)你已读懂 Agent 大脑
- Day 11 Agent SDK:step 循环、三要素、停止条件
- Day 12 工具体系:三件套、function calling、MCP
- Day 13 Runtime:执行引擎、三种实现、vs Sandbox
- Day 14 LLM:litellm、流式、系统提示、成本
- Day 15 CodeAct:以代码为动作的招牌范式
下周(Day 16-20)进入 前端 / 安全 / 生态:事件如何渲染成 UI、安全权限机制、微代理定制、企业版、收官。
✋ 动手
# 默认 Agent 是 CodeActAgent
grep -n 'default_agent' config.template.toml
# 回看 bash 动作(CodeAct 核心)与专用工具
sed -n '25,42p' frontend/src/types/v1/core/base/action.ts
# 思考题:如果只保留 bash 一个工具,OpenHands 还能工作吗?为什么仍保留 str_replace?
下周预告 · Day 16:回到前端——事件流如何渲染成界面:type-guards 类型守卫、
handleEventForUI 的"观察替换动作"规则、WebSocket 连接管理。看第 1 周的事件模型如何变成你看到的聊天界面。