AI Agent框架探秘:拆解 OpenHands(8)--- CodeActAgent
AI Agent框架探秘:拆解 OpenHands(8)--- CodeActAgent
0x00 摘要
大模型是不可控的。不是‘给LLM一堆工具让它自由发挥’,而是大部分由确定性代码构成,在关键决策点巧妙地融入LLM能力。好的 Agent 应用,是工程设计与 AI 能力的精妙结合,而不是对 AI 的盲目放权。
在 OpenHands 智能框架的生态中,CodeActAgent 占据着核心地位,它是基于 CodeAct 理念构建的核心代理模块。其设计初衷极具巧思:将各类复杂任务统一转化为 “代码执行” 的形式来完成,同时兼顾自然语言对话的交互特性。这一设计既保障了任务执行的精准性与高效性,又为人类与智能代理的协作提供了灵活空间,使其成为框架中处理自动化编程、数据处理等复杂场景的核心载体。
因为本系列借鉴的文章过多,可能在参考文献中有遗漏的文章,如果有,还请大家指出。
0x01 背景
1.1 Agent的核心能力
根据Google电子书的定义,一个真正的 AI 智能体拥有四项核心能力:

- 做出动态决策 (Make dynamic decisions):它们不是遵循预定的路径,而是根据所学到的东西决定下一步做什么。
- 跨交互保持状态 (Maintain state across multiple interactions):它们能记住自己做过什么,并利用这些历史来为未来的决策提供信息。
- 自适应地使用工具 (Use tools adaptively):它们可以从可用工具中进行选择,并以非预先编程的方式组合它们。
- 根据结果修正方法 (Modify their approach based on results):当一种策略不起作用时,它们可以尝试不同的方法。
1.2 Agent设计原则
如何设计Agent?不同人有不同的理解,这可能是一个哲学问题。
我们使用 https://github.com/humanlayer/12-factor-agents 来切入,具体如下:
-
原则一:组织规划、工具调用,复杂Agent的工作范式
-
原则二:工具执行器,Agent“思考”和“行动”分离解耦、独立进化
-
原则三:通过工具调用来联系人类,Agent中的“人机协同”
-
原则四:提示词可调试、可迭代、可回滚、面向场景设计、A/B测试
-
原则五:建设上下文评估与仲裁,突破上下文有限束缚,提升“生成质量”
-
原则六:将错误压缩到上下文窗口,让Agent从错误中学习并尝试自我纠正
-
原则七:统一执行状态和业务状态,为Agent赋予了“自主恢复”的能力
-
原则八:使用简单的API来完成启动、暂停和恢复,实现“任务全生命周期”管理
-
原则九:有限状态机,拥有Agent的自主控制流
-
原则十:多个小而专注的Agent,共同组成“智能组织”
-
原则十一:从任何地方触发Agent,构建“无处不在的智能生产力”
-
原则十二:将Agent看做是一个“无状态的归约器”
论文 From Storage to Experience: A Survey on the Evolution of LLM Agent Memory Mechanisms 也给出了Agent的设计原则:
设计原则1:Memory as Experience, Not Storage
- 错误方向:追求更大的存储容量、更快的检索速度
- 正确方向:追求更深的经验抽象、更强的模式迁移
设计原则2:Proactive over Reactive
- 错误方向:优化响应速度和准确度
- 正确方向:增强主动探索和自我完善能力
设计原则3:Cross-trajectory Learning
- 错误方向:单任务优化、垂直领域深耕
- 正确方向:跨任务抽象、通用能力泛化
设计原则4:Continual Learning without Catastrophic Forgetting
- 错误方向:定期重训、版本迭代
- 正确方向:增量更新、经验积累
1.3 Agent in OpenHands
智能体抽象将配置与执行状态分离,智能体被定义为无状态、不可变的规格对象,包含 LLM 设置、工具规格、安全策略与智能体核心逻辑,可序列化并跨进程传输。
1.3.1. 事件驱动执行
智能体通过事件驱动循环逐步处理对话状态,不直接返回结果,而是通过回调函数 on_event (event: Event) -> None 输出结构化事件(如消息、动作、观察结果),实现事件生成与执行控制的分离。该设计支持:
- 安全介入 —— 基于风险分析在执行前审核或拦截动作;
- 增量执行 —— 智能体分步推进任务,支持暂停 / 恢复、上下文溢出恢复与长对话压缩;
- 事件流传输 —— 实时输出中间结果(如观察数据、推理轨迹),用于界面更新与监控。
1.3.2. 基于技能与提示的智能体上下文定制
AgentContext 集中管理所有影响 LLM 行为的输入,包括系统 / 用户消息的前缀 / 后缀、用户定义的 Skill(技能)对象。技能可通过编程方式定义,或从 Markdown 文件(如 .openhands/skills/,及 .cursorrules、agents.md 等兼容格式)加载:
-
永久激活技能(trigger=None):持续增强系统提示;
-
条件激活技能:基于用户输入的关键词匹配触发,可包含 MCP 工具。
该设计支持丰富的上下文与行为定制,无需修改智能体核心逻辑。
1.3.3. 子智能体委托机制
SDK 通过委托工具实现分层智能体协作,充分体现了工具抽象的可扩展性。子智能体作为独立对话存在,继承父智能体的模型配置与工作空间上下文,无需修改核心 SDK 即可实现结构化并行处理与隔离。当前实现提供阻塞式并行执行能力,作为 openhands.tools 包中的标准工具 —— 父智能体创建并监控子智能体,直至所有任务完成。这一模式证明:异步委托、动态调度、容错恢复等复杂协作行为,均可通过用户自定义工具实现,无需修改核心框架,彰显了 SDK “高级智能体编排无需改动核心” 的可扩展设计原则。
1.4 CodeAct
1.4.1 理念
CodeAct 理念的核心突破,在于将智能代理的动作空间提升至通用编程的高度 —— 通过让大语言模型(LLM)直接生成可执行代码,打破了传统工具调用的局限。以往的智能代理往往受困于固定的工具接口,只能机械地调用预设功能,而 CodeAct 赋予代理一个统一的 “可编程” 动作接口,就像为工匠配备了一套可灵活组合的精密工具,为解决复杂任务开辟了全新路径。
这一理念的本质,是深度挖掘 LLM 擅长编写代码的原生能力。它让代理的 “动作” 不再局限于单一的原子 API 调用,而是通过生成一段完整的 Python 代码,交由 Python 解释器执行来完成复杂任务。如此一来,代理能在单个动作中封装完整的逻辑流程:包括调用多个函数或工具、控制执行顺序、处理中间结果并存储,极大地提升了任务处理的连贯性与自主性。
1.4.2 模式
CodeAct Agent 是一个极简主义的智能体,以 ReAct的模式,根据已有的若干 Action-Observation 对的轨迹决定下一步需要采取什么 Action。在每一轮的交互循环中,CodeActAgent 具备两种核心操作模式,二者相辅相成,共同支撑任务推进:
-
对话模式(Converse):以自然语言为沟通桥梁,实现与人类的高效协作。例如当任务需求模糊时,代理会主动请求用户澄清细节;在执行关键操作前,也会向用户确认以规避风险,充分体现了人机协作的灵活性。
-
代码行动模式(CodeAct):依托一组标准化工具展开具体操作,覆盖多类任务场景:
-
调用execute_bash函数执行 Linux 系统的 bash 命令,实现系统级操作;
-
通过execute_ipython_cell在 IPython 环境中运行 Python 代码,处理数据计算、逻辑执行等核心任务;
-
借助browser与fetch工具与网页浏览器交互,完成信息爬取、页面操作等需求;
-
利用str_replace_editor或edit_file工具编辑文件内容,实现文档修改、代码编写等功能。
这种 “对话 + 代码” 的双轨模式,不仅简化了智能代理的操作体系,更在实际应用中显著提升了任务处理性能。
实际上 OpenDevin 中 CodeAct Agent 的实现与原始 CodeAct 并不完全一样,前者在原始 CodeAct 的基础上进行改进,并很大程度上借鉴了 SWE-Agent.
1.4.3 特色
CodeActAgent 的特色如下:
- 行动空间统一化:打破传统代理多行动类型的碎片化设计,将所有任务(文件操作、数据处理、系统交互等)统一为 “代码执行” 行动,简化架构且提升执行效率。
- 双模式交互能力:支持 “自然语言对话” 与 “代码行动” 双模式,既可以通过自然语言与人类协作(如请求澄清),也能通过代码自主完成复杂任务,适配多样场景。
- 插件化沙盒依赖:通过
sandbox_plugins定义沙盒环境所需插件,按顺序初始化确保依赖正确性,同时支持灵活扩展技能(如通过AgentSkillsRequirement新增工具函数)。 - 完善的记忆与上下文管理:集成
ConversationMemory管理 “行动 - 观察” 历史,搭配Condenser压缩长上下文,平衡上下文相关性与模型输入长度限制。 - 灵活的模型路由支持:通过
LLMRegistry.get_router获取路由 LLM,可根据任务复杂度动态选择适配模型,兼顾性能与成本。 - 极简主义设计:核心逻辑聚焦 “代码执行” 单一行动空间,架构简洁易懂,同时保持高扩展性,便于后续功能迭代与定制化开发。
0x02 定义
CodeActAgent的定义如下。
class CodeActAgent(Agent):
"""
CodeActAgent:极简主义的智能代理,基于 CodeAct 理念实现。
核心逻辑:将模型的行动统一到“代码执行”这一单一行动空间,通过传递“行动-观察”对列表,
引导模型决策下一步操作,兼顾简洁性与执行性能。
核心理念(源自论文:https://arxiv.org/abs/2402.01030):
打破传统代理多行动类型的复杂设计,用代码执行统一所有行动,既简化架构又提升效率。
"""
VERSION = '2.2' # 代理版本号
# 沙盒环境所需插件依赖(按初始化顺序排列)
sandbox_plugins: list[PluginRequirement] = [
# 注意:AgentSkillsRequirement 需在 JupyterRequirement 之前初始化
# 原因:AgentSkillsRequirement 提供大量 Python 工具函数,
# Jupyter 环境需要依赖这些函数才能正常工作
AgentSkillsRequirement(), # 提供代理核心技能函数的插件
JupyterRequirement(), # 提供交互式 Python 执行环境的插件
]
def __init__(self, config: AgentConfig, llm_registry: LLMRegistry) -> None:
"""
初始化 CodeActAgent 实例。
参数:
config (AgentConfig):当前代理的配置对象(包含模型路由、记忆策略等)
llm_registry (LLMRegistry):LLM 注册表实例,用于获取所需 LLM 或路由 LLM
"""
# 调用父类 Agent 的初始化方法,完成基础配置(如 LLM 注册、提示词管理器初始化)
super().__init__(config, llm_registry)
self.pending_actions: deque['Action'] = deque() # 待执行的行动队列(双端队列,支持高效进出)
self.reset() # 重置代理状态(初始化行动历史、观察记录等)
self.tools = self._get_tools() # 获取代理可使用的工具集(从插件或配置中提取)
# 初始化对话记忆实例:存储“行动-观察”对,支持记忆压缩、上下文管理
self.conversation_memory = ConversationMemory(self.config, self.prompt_manager)
# 初始化上下文压缩器:根据配置创建 Condenser 实例,用于压缩长对话历史
self.condenser = Condenser.from_config(self.config.condenser, llm_registry)
# 覆盖父类的 LLM 实例:如需模型路由,优先使用路由 LLM(根据代理配置动态选择模型)
self.llm = self.llm_registry.get_router(self.config)
2.1 可配置性
CodeActAgent 通过 AgentConfig,可以灵活启用 / 禁用各种功能,可配置的功能如下:
config.enable_cmd # 启用命令执行
config.enable_think # 启用思考功能
config.enable_finish # 启用完成功能
config.enable_browsing # 启用浏览器功能
config.enable_jupyter # 启用 Jupyter
config.enable_editor # 启用文件编辑器
2.2 插件系统
CodeActAgent 通过 sandbox_plugins 定义沙盒环境所需插件,按顺序初始化确保依赖正确性,同时支持灵活扩展技能(如通过 AgentSkillsRequirement 新增工具函数)。
sandbox_plugins: list[PluginRequirement] = [
# NOTE: AgentSkillsRequirement need to go before JupyterRequirement, since
# AgentSkillsRequirement provides a lot of Python functions,
# and it needs to be initialized before Jupyter for Jupyter to use those functions.
AgentSkillsRequirement(), # 提供Python函数
JupyterRequirement(), # 提供Jupyter支持
]
2.3 工具系统Tools
工具是智能代理拓展能力边界的关键,正是工具的存在,让 LLM 从单纯的对话机器人(ChatBot)进化为具备实际执行能力的智能代理(Agent)。CodeAct 的方案却反其道而行之,以极致简洁的思路重构了工具调用逻辑 —— 它将 Python 作为唯一的工具,让 LLM 通过自主编写代码的方式实现各类功能调用,摒弃了传统多工具集成的复杂设计。
传统工具调用模式中,开发者需要在系统提示词(system prompt)中明确告知 LLM 可用的工具接口(Available APIs),LLM 再通过生成工具名和参数列表的方式调用工具,无论输出格式是文本还是 JSON,本质上都受限于预设范围。而 CodeAct 省去了这一繁琐的预定义步骤,将 Python 作为统一接口,LLM 在每一轮交互中直接生成代码并交由解释器执行。这种设计让动作空间更标准化,工具调用过程简洁优雅,充分释放了 LLM 的原生潜力。
2.3.1 工具集
CodeActAgent 则有所不同。CodeActAgent是一个混合型代理,它既允许模型执行任意代码,也提供了一些特定工具供模型使用。具体来说:
-
允许模型执行任意代码。CodeActAgent的核心理念是让模型能够执行任意代码:
- 通过create_cmd_run_tool工具,模型可以执行任何有效的Linux bash命令
- 通过IPythonTool工具,模型可以执行任何有效的Python代码
- 这符合CodeAct论文中提出的统一代码操作空间的概念,旨在简化和提高代理性能。
-
提供特定工具集。CodeActAgent 也提供了一些预定义的工具供模型使用:
- ThinkTool:让模型记录其思考过程
- FinishTool:结束交互
- CondensationRequestTool:请求压缩对话历史
- BrowserTool:与浏览器交互(非 Windows 平台)
- LLMBasedFileEditTool 或 create_str_replace_editor_tool:编辑文件
- create_task_tracker_tool:任务管理工具
-
工具启用的灵活性通过配置,可以控制哪些工具被启用
CodeActAgent支持丰富的工具集如下:
def _get_tools(self) -> list['ChatCompletionToolParam']:
# For these models, we use short tool descriptions ( < 1024 tokens)
# to avoid hitting the OpenAI token limit for tool descriptions.
SHORT_TOOL_DESCRIPTION_LLM_SUBSTRS = ['gpt-4', 'o3', 'o1', 'o4']
use_short_tool_desc = False
if self.llm is not None:
# For historical reasons, previously OpenAI enforces max function description length of 1k characters
# https://community.openai.com/t/function-call-description-max-length/529902
# But it no longer seems to be an issue recently
# https://community.openai.com/t/was-the-character-limit-for-schema-descriptions-upgraded/1225975
# Tested on GPT-5 and longer description still works. But we still keep the logic to be safe for older models.
use_short_tool_desc = any(
model_substr in self.llm.config.model
for model_substr in SHORT_TOOL_DESCRIPTION_LLM_SUBSTRS
)
tools = []
if self.config.enable_cmd: # Bash命令执行工具
tools.append(create_cmd_run_tool(use_short_description=use_short_tool_desc))
if self.config.enable_think: # 思考工具,记录推理过程
tools.append(ThinkTool)
if self.config.enable_finish: # 完成工具,结束任务
tools.append(FinishTool)
if self.config.enable_condensation_request:
tools.append(CondensationRequestTool)
if self.config.enable_browsing: # 浏览器工具
if sys.platform == 'win32':
logger.warning('Windows runtime does not support browsing yet')
else:
tools.append(BrowserTool)
if self.config.enable_jupyter: # IPython工具
tools.append(IPythonTool)
if self.config.enable_plan_mode:
# In plan mode, we use the task_tracker tool for task management
tools.append(create_task_tracker_tool(use_short_tool_desc))
if self.config.enable_llm_editor: # 文件编辑工具
tools.append(LLMBasedFileEditTool)
elif self.config.enable_editor:
tools.append(
create_str_replace_editor_tool(
use_short_description=use_short_tool_desc,
runtime_type=self.config.runtime,
)
)
return tools
2.3.2 BrowserTool
BrowserTool 举例如下:
BrowserTool = ChatCompletionToolParam(
type='function',
function=ChatCompletionToolParamFunctionChunk(
name=BROWSER_TOOL_NAME,
description=_BROWSER_DESCRIPTION,
parameters={
'type': 'object',
'properties': {
'code': {
'type': 'string',
'description': (
'The Python code that interacts with the browser.\n'
+ _BROWSER_TOOL_DESCRIPTION
),
},
'security_risk': {
'type': 'string',
'description': SECURITY_RISK_DESC,
'enum': RISK_LEVELS,
},
},
'required': ['code', 'security_risk'],
},
),
)
_BROWSER_TOOL_DESCRIPTION 如下。
_BROWSER_TOOL_DESCRIPTION = """
The following 15 functions are available. Nothing else is supported.
goto(url: str)
Description: Navigate to a url.
Examples:
goto('http://www.example.com')
go_back()
Description: Navigate to the previous page in history.
Examples:
go_back()
go_forward()
Description: Navigate to the next page in history.
Examples:
go_forward()
noop(wait_ms: float = 1000)
Description: Do nothing, and optionally wait for the given time (in milliseconds).
You can use this to get the current page content and/or wait for the page to load.
Examples:
noop()
noop(500)
scroll(delta_x: float, delta_y: float)
Description: Scroll horizontally and vertically. Amounts in pixels, positive for right or down scrolling, negative for left or up scrolling. Dispatches a wheel event.
Examples:
scroll(0, 200)
scroll(-50.2, -100.5)
fill(bid: str, value: str)
Description: Fill out a form field. It focuses the element and triggers an input event with the entered text. It works for , SWE-agent 详解:打造大模型与计算机的编程界面 mannaandpoem
https://swe-agent.com/paper.pdf
"原文地址: https://www.cveoy.top/t/topic/qFZW 著作权归作者所有。请勿转载和采集!