本文详细剖析了 AI Agent (以 OpenHands CodeActAgent 为例) 的核心设计理念和内部工作流程。内容涵盖 Agent 的交互循环、Orchestrator 职责、状态管理机制(包括 State 对象、Action 与 Observation 的结构)、状态持久化策略以及 LLM Prompt 构建和 Agent 决策过程。
本文档总结了关于 AI Agent(特别是类似 OpenHands 项目中的 CodeActAgent)设计的核心概念、交互流程、状态管理、持久化策略以及内部工作机制的讨论。
Agent 的工作本质上是一个”思考-行动-观察-再思考”的循环。
graph TD
S["State Object <br> (包含历史记录, 当前规划)"] --> AGENT_STEP["Agent.step(State) <br> (LLM 思考与决策)"];
AGENT_STEP -- "1. 生成 Action (携带意图)" --> ORCH["Agent Orchestrator/Controller <br> (管理 Action 的生命周期)"];
ORCH -- "2. 分发 Action <br> (分配 Correlation ID)" --> HANDLER["Action Handler <br> (执行环境 / 前端 / API调用模块)"];
HANDLER -- "3. 返回 Observation <br> (携带结果和 Correlation ID)" --> ORCH;
ORCH -- "4. 更新 State <br> (将 Observation 添加到历史)" --> S;
流程说明:
State
对象 : Agent 的”记忆”,包含所有历史交互和当前任务规划。 Agent.step(State)
: Agent 的核心决策单元,通常利用 LLM 基于当前 State
思考并决定下一步行动,产出 Action
。 Action
,为其分配追踪标识 (Correlation ID
),并将其分发给合适的执行单元。 Action
的模块(如代码执行环境、前端交互接口、第三方 API 调用模块)。执行后返回包含结果和 Correlation ID
的 Observation
。 Observation
,通过 Correlation ID
关联到原 Action
,并用此 Observation
更新 State
对象,驱动 Agent 进入下一轮思考。 Orchestrator (或 Controller) 是 Agent 系统能够有序、有效地完成任务的核心协调者。主要职责包括:
agent.step()
,并提供完整的 State
上下文。 Action
。 Correlation ID
(或确保可追踪)。 Action
类型并分发给正确的执行单元(Runtime, 前端接口, API Handler)。 Action
执行后产生的 Observation
。 Correlation ID
将 Observation
与触发它的 Action
精确关联。 (Action, Observation)
对或相关事件更新到核心 State
对象中。 与 React 协调器的类比 :
State
对象是 Agent 的”记忆”和决策基础。
State
对象的构成 (以 OpenHands 为例推测) session_id: str
: 当前会话的唯一标识。 plan: Plan
由 Agent 内部管理或通过 inputs
间接定义, 非 State
对象直接的持久化属性 (详见 5.2 节) iteration: int
: Agent 已执行的迭代次数。 history: List[Event]
: 核心部分 ,按时间顺序记录了所有已发生的 Event
对象 (即 Action
和对应的 Observation
)。 Action
与 Observation
Action
: 代表 Agent 的意图或用户/系统的输入。
id: int
: Action
的唯一标识 (作为 Event
继承而来, 由 EventStream
分配)。 type/class
: 表明 Action
的种类 (如 CmdRunAction
, MessageAction
, AgentFinishAction
)。在 OpenHands 中具体体现为 action: str
字段。 payload/content
: Action
的具体内容 (如命令字符串、消息文本)。在 OpenHands 中通常体现为 args
字典或特定字段。 source
: 标记来源 (agent, user)。 Observation
: 代表 Action
执行后的结果或外部事件。
type/class
: 表明 Observation
的种类 (如 CmdOutputObservation
, UserMessageObservation
, AgentErrorObservation
)。在 OpenHands 中具体体现为 observation: str
字段。 content/payload
: Observation
的具体内容 (如命令输出、用户消息文本)。 cause: Optional[int]
: 关键关联字段 ,存储了触发此 Observation
的那个 Action
的 id
(作为 Event
继承而来)。 Observation
(来自代码执行结果、用户输入、API 响应等) 产生并被 Orchestrator 用来更新 State
对象 (尤其是 State.history
) 时,这个变化会驱动 Orchestrator 调用 agent.step(newState)
。 State
(包含最新的 Observation
)重新评估局势,做出新的决策,并产生新的 Action
。 State
变化 -> Agent 思考 -> 新 Action
-> 执行 Action
-> 新 Observation
-> State
再变化)是 Agent 不断前进的动力。 Action
(如提问) 时,Orchestrator 将指令和 correlation_id
发给前端。 Action
等待响应。 correlation_id
通过 新的请求 发回后端。 correlation_id
找到原 Action
,将用户输入封装为 Observation
,更新 State
,并重新调度 Agent。 agent.step()
内部核心:构建 Prompt 与解析响应 agent.step(state)
函数是 Agent “思考”的核心。
graph LR
subgraph "AGENT_STEP_DETAIL [agent.step() 内部细节]"
direction LR
STATE_INPUT["State (来自 S)"] --> CONDENSER["1. 历史精简"];
CONDENSER -- "精简历史" --> MSG_BUILDER["2. 构建LLM消息 <br> (例如: UserMsg 转为 {'role':'user', ...}, <br> Agent的CmdRunAction 转为 {'role':'assistant', 'tool_calls': [...]}, <br> CmdOutputObservation 转为 {'role':'tool', 'content': ...})"];
SYS_PROMPT[("系统提示")] --> PROMPT_ASSEMBLY;
MSG_BUILDER --> PROMPT_ASSEMBLY;
PROMPT_ASSEMBLY["3. Prompt组装"] -- "完整Prompt" --> LLM_CALL["4. 调用LLM"];
LLM_CALL -- "LLM原始响应" --> RESP_PARSER["5. 解析LLM响应"];
RESP_PARSER --> NEW_ACTION_OBJ_INTERNAL["6. 创建新Action对象"];
end
State.history
可能很长,需要精简 (condenser
) 以适应 LLM 上下文窗口。 Action
和 Observation
) 转换为 LLM API 所需的格式化消息列表。
{"role": "user", "content": "..."}
Action
-> {"role": "assistant", "tool_calls": [{"id": "...", "type": "function", "function": {"name": "...", "arguments": "..."}}]}
Observation
-> {"role": "tool", "tool_call_id": "...", "name": "...", "content": "..."}
Action
对象(如 CmdRunAction
, MessageAction
)。这个 Action
将作为 agent.step()
的返回值。 为了让 Agent 能够在会话中断后恢复,或在系统重启后继续,需要持久化 State
对象。
OpenHands 设计了一套基于文件系统的持久化机制,以确保 Agent 会话的状态(包括其交互历史)可以在需要时被保存和恢复。核心组件是 State
对象和 EventStream
。
主要策略:
State
对象元数据持久化 :
State
对象本身(包含如 session_id
, iteration
, max_iterations
, confirmation_mode
等元数据,以及对历史事件范围的引用如 start_id
, end_id
)会通过 Python 的 pickle
模块进行序列化。 State
对象被保存为一个 .pkl
文件,文件名通常基于 session_id
(例如,在 workspace/[session_id]/state.pkl
)。 State
对象的 pickle
文件本身并不直接包含 history
列表中的具体 Event
对象数据。 它只包含了足以让系统从 EventStream
中恢复历史的元信息。 EventStream
事件持久化 :
Action
和 Observation
(它们都是 Event
的子类) 都会被添加到 EventStream
中 (openhands/events/stream.py
)。 EventStream
继承自 EventStore
(openhands/events/event_store.py
),后者负责实际的事件读写逻辑。 Event
(Action 或 Observation) 在被添加到 EventStream
时,都会被序列化为 JSON 格式,并存储在其各自独立的文件中。
workspace/[session_id]/events/[event_id].json
。文件名基于 session_id
和事件自身的 id
。 openhands.events.serialization.event_to_dict()
完成的,它将 Event
对象转换为字典,然后写入 JSON 文件。 EventStore
还实现了一种缓存机制。它会将多个连续的事件聚合到一个”缓存页”(_CachePage
)文件中(例如 workspace/[session_id]/event_cache/[start_id]-[end_id].json
)。当读取一系列事件时,系统会首先尝试从缓存页加载,如果缓存未命中或不完整,则回退到读取单个事件的 JSON 文件。 状态恢复流程:
State
元数据 : 当需要恢复一个会话时,系统首先根据 session_id
找到并反序列化对应的 state.pkl
文件,得到 State
对象。此时,State.history
列表通常是空的或未完全填充。 EventStream
重建 history
:
AgentController
(在 _init_history()
或类似方法中) 利用 State
对象中存储的事件范围信息(如 start_id
, end_id
)或通过查询 EventStream
获取最新的事件范围。 EventStream
(即 EventStore
) 从磁盘加载相应范围内的事件。 EventStream
会读取对应的单个事件 JSON 文件(或利用缓存页),并通过 openhands.events.serialization.event_from_dict()
将每个 JSON 对象反序列化回具体的 Action
或 Observation
对象。 Event
对象随后被用来填充(重建)State.history
列表。 总结:
OpenHands 的持久化策略是一种混合方法:State
的核心元数据通过 pickle
存储,而详细的交互历史 (Action
和 Observation
事件) 则以独立的 JSON 文件形式由 EventStream
管理。这种分离的设计有助于处理非常长的交互历史,避免单个 State
pickle 文件变得过于庞大,同时也使得按需加载部分历史成为可能。
State
对象的属性 State
对象 (openhands/controller/state/state.py
) 是 Agent 执行任务过程中的核心状态容器。它封装了任务执行的上下文、历史记录、以及控制参数。以下是其主要属性:
session_id: str
: 当前会话的唯一标识符。用于区分不同的任务会话,也是持久化和恢复的关键 ID。 iteration: int
: Agent 已执行的迭代(step)次数。 local_iteration: int
: (意义待进一步确认,可能与子任务或特定上下文的迭代计数有关)。 max_iterations: int
: Agent 在当前任务中允许执行的最大迭代次数。这是一个重要的安全和资源控制参数。 confirmation_mode: bool
: 是否需要用户确认 Agent 的某些动作。
True
: 需要用户确认。 False
: Agent 可以自主执行动作(除非动作本身具有极高风险)。 history: list[Event]
: 一个列表,按时间顺序存储了当前会话中所有已发生的 Event
对象 (即 Action
和 Observation
的实例)。
State
对象被 pickle 持久化时,这个 history
列表本身 不包含 实际的 Event
对象数据。State.pkl
文件只存储了 history
的元信息(如 start_id
和 end_id
)。 State
对象从 pickle 文件恢复后,history
列表会由 AgentController
通过查询 EventStream
来动态填充(重建)。因此,在运行时,State.history
确实包含了实际的 Event
对象序列。 inputs: dict[str, Any]
: 任务的初始输入参数。 outputs: dict[str, Any]
: 任务最终的输出结果。 error: Optional[str]
: 如果任务执行过程中发生严重错误导致任务终止,这里会记录错误信息。 created_at: str
: State
对象创建时的时间戳 (ISO 格式字符串)。 updated_at: str
: State
对象最后更新时的时间戳 (ISO 格式字符串)。 last_error: Optional[str]
: (与 error
字段关系待确认,可能是最近一次非致命错误的信息)。 max_budget_per_task: Optional[float]
: (意义待确认,可能与任务预算控制有关)。 agent_name: Optional[str]
: (意义待确认,可能是执行此状态的 Agent 的名称或类型)。 view_idx_start_inclusive: int
: (意义待确认,可能与历史视图或分页有关)。 view_idx_end_exclusive: int
: (意义待确认,可能与历史视图或分页有关)。 start_id: Optional[int]
: history
中第一个事件的 ID。在从 EventStream
加载历史时使用。 end_id: Optional[int]
: history
中最后一个事件的 ID。在从 EventStream
加载历史时使用。 关于 plan
属性的说明:
openhands/controller/state/state.py
的代码分析中, 并未发现 State
对象直接拥有一个名为 plan
的字段 (例如 plan: Plan
)。 State.inputs
中的特定字段传入,或者由 Agent (如 CodeActAgent
) 在其内部逻辑中根据初始用户指令生成和管理。 State
对象本身更侧重于记录原子化的交互历史 (history
) 和当前的执行元数据,而不是一个结构化的计划对象。如果需要表示计划,它更可能存在于 Agent 的内部状态或通过 Event
序列(例如,一系列 AgentThinkAction
和 MessageAction
)来间接体现。 视图 (view
) :
State
对象有一个 @property def view(self) -> list[Event]:
。 self.history
的一个视图,可能基于 view_idx_start_inclusive
和 view_idx_end_exclusive
进行切片。这允许只关注历史记录的特定部分,例如用于历史精简或UI展示。 Event
, Action
, Observation
OpenHands 的核心交互流程是通过一系列的 Event
对象来记录和驱动的。State.history
存储的就是一个 list[Event]
。Event
是一个基类,它有两个主要的子类:Action
(由 Agent 或用户执行的动作) 和 Observation
(执行 Action
后环境返回的结果或发生的其他情况)。
Event
(基类) 所有 Event
对象共享以下核心属性,这些属性在通过 EventStream
持久化(序列化为 JSON)时会被保留:
id: int
: 事件的唯一标识符。在 EventStream
中分配。 timestamp: str
: 事件发生的时间戳 (ISO 8601 格式字符串,例如 "2023-10-27T10:30:00.123Z"
)。 source: str
: 事件的来源。通常是 EventSource
枚举的值,如:
EventSource.USER
(“user”): 事件由用户发起 (例如,用户输入的消息)。 EventSource.AGENT
(“agent”): 事件由 Agent 自身产生 (例如,Agent 的思考、执行的命令、发出的消息)。 EventSource.ENVIRONMENT
(“environment”): 事件由执行环境/沙箱产生(较少直接作为顶层事件来源,更多是 Observation 的隐含来源)。 cause: Optional[int]
: 引起此事件的先前事件的 id
。
Observation
,这通常是触发它的 Action
的 id
。 Action
(如 Agent 回复用户的消息),这可能是用户 MessageAction
的 id
。 Action
(如用户的第一条指令),cause
可能为 None
或一个特殊值。 message: Optional[str]
: 与事件相关的通用消息内容。许多子类会提供更具体的实现或覆盖此属性。 timeout: Optional[float]
: 主要用于某些 Action
,定义其执行的超时时间(秒)。 llm_metrics: Optional[dict]
: (可选) 如果事件的产生涉及到 LLM 调用,这里可能存储相关的指标数据 (如 token 使用量、耗时等)。实际结构为 openhands.llm.metrics.Metrics
对象。 tool_call_metadata: Optional[dict]
: (可选) 如果此 Event
(通常是 Action
) 是由 LLM 的工具调用(function calling)请求产生的,这里会存储 LLM 返回的原始工具调用信息。实际结构为 openhands.events.tool.ToolCallMetadata
。 response_id: Optional[str]
: (可选) 来自 LLM 响应的唯一标识符。 extras: dict
: 这是一个在序列化时通过 dataclasses.asdict()
收集的字典,用于存储特定于事件子类型的、未在基类中明确定义的附加元数据。例如,CmdOutputObservation
的 exit_code
会出现在这里或作为顶层字段。 Event
对象通过 openhands.events.serialization.event_to_dict()
和 event_from_dict()
进行序列化和反序列化。
Action
(Agent 或用户的动作) Action
代表了系统中发生的”动作”或”意图”,它可以由 Agent 决定执行,也可以由用户输入(如发送消息)。它继承自 Event
。
核心 Action
属性 (除了继承自 Event
的属性外):
action: str
: 必需 。表示动作的具体类型。通常是 openhands.core.schema.ActionType
枚举中的一个值 (例如 ActionType.MESSAGE
, ActionType.RUN
, ActionType.BROWSE
, ActionType.FINISH
, ActionType.THINK
等)。这是区分不同动作的关键字段。 args: dict
: 必需 (但在代码中并非显式字段,而是序列化时动态构建)。这是一个包含该 Action
类型所需的所有特定参数的字典。
args
可能包含 {"command": "ls -l", "thought": "I need to list files"}
。 args
可能包含 {"content": "Hello world"}
。 Action
子类中的字段,在序列化时,dataclasses.asdict()
会将这些字段收集到 args
字典中(或者,如 event_to_dict
实现所示,某些核心参数如 content
可能被提升到顶层,而其他则放入 args
)。为了简化理解,可以将 args
视为传递给动作执行者的参数包。 runnable: ClassVar[bool]
: 一个类级别的属性,指示该类型的 Action
是否设计为可由沙箱等执行环境直接运行。默认为 False
,具体的、可执行的 Action
子类(如 CmdRunAction
)会将其设置为 True
。 thought: Optional[str]
: (常见于许多 Action
子类) Agent 在决定执行此动作前的思考过程或理由。这对调试和理解 Agent 行为非常重要。 confirmation_status: Optional[str]
: (推测,基于 ActionConfirmationStatus
枚举) 可能用于需要用户确认的动作。 security_risk: Optional[str]
: (例如 MessageAction
中的 ActionSecurityRisk
) 评估该动作的潜在安全风险。 常见的 Action
子类及其关键字段 (这些字段会成为序列化时 args
的一部分或顶层字段):
MessageAction(action=ActionType.MESSAGE)
: Agent 或用户发送消息。
content: str
: 消息的文本内容。 image_urls: Optional[list[str]]
: 消息中包含的图像 URL 列表 (用于多模态)。 wait_for_response: bool
: (默认为 False
) Agent 发送消息后是否期望用户响应。 security_risk: Optional[ActionSecurityRisk]
: 此消息的评估安全风险。 SystemMessageAction(action=ActionType.SYSTEM)
: 通常是 history
中的第一个事件,用于设置 Agent 的系统提示。
content: str
: 系统提示的内容 (定义 Agent 的角色、能力、目标、约束等)。 tools: Optional[list[dict]]
: (可选) 明确提供给 Agent 的工具列表描述。 agent_class: Optional[str]
: 使用的 Agent 类名。 CmdRunAction(action=ActionType.RUN)
: Agent 执行一个 shell 命令。
command: str
: 要在沙箱中执行的命令字符串。 thought: Optional[str]
: Agent 决定执行此命令的思考。 background: bool
(可能): 命令是否在后台执行。 IPythonRunCellAction(action=ActionType.RUN_IPYTHON)
: Agent 在 IPython 内核中执行 Python 代码单元。
code: str
: 要执行的 Python 代码。 thought: Optional[str]
: Agent 的思考。 FileReadAction(action=ActionType.READ)
: Agent 读取文件内容。
path: str
: 要读取的文件的路径。 thought: Optional[str]
: Agent 的思考。 FileWriteAction(action=ActionType.WRITE)
: Agent 写入或覆盖文件内容。
path: str
: 要写入的文件的路径。 content: str
: 要写入文件的内容。 thought: Optional[str]
: Agent 的思考。 FileEditAction(action=ActionType.EDIT)
: Agent 编辑现有文件(例如,通过块编辑)。
path: str
: 要编辑的文件的路径。 editing_instructions: Any
: (具体结构待定) 编辑指令,例如查找和替换的块。 thought: Optional[str]
: Agent 的思考。 BrowseURLAction(action=ActionType.BROWSE)
: Agent 访问一个 URL。
url: str
: 要访问的 URL。 thought: Optional[str]
: Agent 的思考。 AgentThinkAction(action=ActionType.THINK)
: Agent 进行一次内部思考,不直接与环境交互,但其思考内容会被记录。
thought: str
: Agent 的思考过程。 Observation
通常是 AgentThinkObservation
,其 content
字段会包含此 thought
。 AgentFinishAction(action=ActionType.FINISH)
: Agent 认为任务已完成,结束交互。
thought: Optional[str]
: Agent 结束任务时的最终想法或总结。 NullAction(action=ActionType.NULL)
: 一个空操作,什么也不做。用于某些特殊流程或表示无有效动作。 Observation
(动作的结果或环境变化) Observation
代表了 Agent 执行一个 Action
后从环境中获得的结果,或者是环境中发生的一些其他值得注意的事件(例如用户输入)。它也继承自 Event
。
核心 Observation
属性 (除了继承自 Event
的属性外):
observation: str
: 必需 。表示观察的具体类型。通常是 openhands.core.schema.ObservationType
枚举中的一个值 (例如 ObservationType.RUN
, ObservationType.BROWSE_CONTENT
, ObservationType.READ
, ObservationType.ERROR
, ObservationType.SUCCESS
, ObservationType.AGENT_STATE_CHANGED
等)。这是区分不同观察结果的关键字段。 content: str
: 必需 。观察的主要文本内容或消息。
content
是命令的输出 (stdout/stderr)。 content
是文件的内容。 content
是错误信息。 Observation
子类也有一个 @property def message(self) -> str:
,它通常返回 self.content
或基于 content
的、更适合展示给用户或 Agent 的格式化消息。 extras: dict
: 与 Action
类似,这里可以包含特定于此 Observation
类型的附加结构化数据。这些数据通常是定义在具体 Observation
子类中的字段,在序列化时被收集。 常见的 Observation
子类及其关键字段/内容:
CmdOutputObservation(observation=ObservationType.RUN)
: CmdRunAction
的结果。
content: str
: 命令的 stdout 和 stderr。 command_id: int
: (通常是对应的 CmdRunAction
的 id
,但更准确地说是 cause
字段)。 command: str
: 被执行的命令。 exit_code: int
: 命令的退出码。 IPythonRunCellObservation(observation=ObservationType.RUN_IPYTHON)
: IPythonRunCellAction
的结果。
content: str
: IPython 单元执行的输出 (stdout, stderr, rich display outputs like images/plots as base64 or text representations)。 format: str
: (可能) 输出的格式,例如 ‘text/plain’, ‘image/png’。 BrowserOutputObservation(observation=ObservationType.BROWSE_CONTENT)
: BrowseURLAction
的结果。
url: str
: 被访问的 URL。 content: str
: 页面内容的文本表示、摘要或可访问性树信息。 screenshot: Optional[str]
: (可选) 页面的截图,可能是 base64 编码的字符串或文件路径。 status_code: Optional[int]
: HTTP 状态码。 FileReadObservation(observation=ObservationType.READ)
: FileReadAction
的结果。
path: str
: 被读取的文件的路径。 content: str
: 文件的内容。 FileWriteObservation(observation=ObservationType.WRITE)
/ FileEditObservation(observation=ObservationType.EDIT)
: 文件写入/编辑操作的结果。
path: str
: 被操作的文件的路径。 content: str
: 通常是一个确认消息,如 “File written successfully” 或 “Error writing file: reason”。 ErrorObservation(observation=ObservationType.ERROR)
: 表示在尝试执行动作或在 Agent 运行过程中发生了可恢复的错误。
content: str
: 详细的错误信息。 error_id: str
(或 error_type: str
): 错误的分类。 SuccessObservation(observation=ObservationType.SUCCESS)
: 表示一个通用的成功操作。
content: str
: 描述成功的消息。 NullObservation(observation=ObservationType.NULL)
: 表示没有产生有意义的观察。通常与某些非执行性 Action
(如用户消息) 配对,或当动作未产生显式输出时。
content: str
: 通常为空字符串或类似 “No observation” 的消息。 AgentThinkObservation(observation=ObservationType.THINK)
: 对应 AgentThinkAction
。
content: str
: Agent 的思考内容 (从对应的 AgentThinkAction.thought
复制而来)。 UserRejectObservation(observation=ObservationType.USER_REJECTED)
: 当用户明确拒绝了一个待确认的动作时产生。
content: str
: 用户拒绝的原因或简单的拒绝消息。 AgentStateChangedObservation(observation=ObservationType.AGENT_STATE_CHANGED)
: 当 Agent 内部状态发生改变时 (例如,从一个子任务切换到另一个,或进入/退出某种模式)。
agent_state: str
: 新的 Agent 状态描述。 reason: str
: 状态改变的原因。 content: str
: 相关的消息。 如果需要更强的查询能力和扩展性,可以设计如下数据库模型:
Sessions
表 : 存储会话/任务的元数据。
session_id (PK)
, user_id
, agent_type
, current_iteration
, plan_main_goal
, agent_state
, created_at
, updated_at
, raw_metadata (JSONB)
等。 Events
表 : 存储 State.history
中的每个 Action
和 Observation
。
event_id (PK)
, session_id (FK)
, sequence_number (用于排序)
, timestamp
, source
(AGENT, USER, RUNTIME), event_class
(具体 Action/Observation 类名), event_data (JSONB 存储序列化的 Action/Observation 内容)
, action_id_ref
(Action 的 ID 或 Observation 的 cause ID)。 恢复逻辑 (基于数据库) :
session_id
从 Sessions
表获取元数据。 session_id
从 Events
表查询所有事件,按 sequence_number
排序。 event_data
重建 Action
和 Observation
对象,填充到 State.history
。 State
对象恢复完成。 Agent.step()
详解) Agent 的核心 “思考” 过程发生在 Agent.step(state)
方法中。此方法接收当前的 State
对象,经过一系列处理,最终调用大语言模型 (LLM) 并根据其响应生成下一步的 Action
。在 OpenHands 中,CodeActAgent
是一个典型的实现。
graph LR
subgraph "AGENT_STEP_DETAIL [CodeActAgent.step(State) 内部核心流程]"
direction LR
STATE_INPUT["State (当前状态)"] --> HISTORY_CONDENSE["1. 历史精简 <br> (self.condenser.condensed_history)"];
HISTORY_CONDENSE -- "精简后的事件列表 (condensed_history)" --> GET_MSGS["2. 构建消息列表 <br> (self._get_messages)"];
STATE_INPUT -- "完整历史 (state.history)" --> GET_INITIAL_USER_MSG["2a. 获取初始用户消息 <br> (self._get_initial_user_message)"];
GET_INITIAL_USER_MSG -- "initial_user_message" --> GET_MSGS;
GET_MSGS -- "格式化消息列表 (List[Message])" --> PREPARE_LLM_PARAMS["3. 准备LLM调用参数"];
TOOLS_DEF[("Agent工具定义 (self.tools)")] --> PREPARE_LLM_PARAMS;
PREPARE_LLM_PARAMS -- "包含消息和工具的参数" --> LLM_CALL["4. 调用LLM <br> (self.llm.completion)"];
LLM_CALL -- "LLM原始响应 (ModelResponse)" --> PARSE_LLM_RESPONSE["5. 解析LLM响应 <br> (self.response_to_actions)"];
PARSE_LLM_RESPONSE -- "生成的Action列表 (List[Action])" --> ADD_TO_PENDING["6. 添加到待处理队列"];
ADD_TO_PENDING --> RETURN_ACTION["7. 返回首个Action"];
end
详细步骤说明 (以 CodeActAgent
为例):
历史精简 (self.condenser.condensed_history
) :
state.history
(一个 list[Event]
)。 Condenser
(例如 openhands.memory.condenser.Condenser
) 会根据特定策略(如保留最近的 N 个事件、总结旧事件、丢弃不重要的事件等)处理 state.history
。 View(events=...)
: 一个精简后的事件列表 (condensed_history
)。 Condensation(action=...)
: 如果精简过程本身需要一个动作(例如,生成一个 CondensationAction
来通知有事件被遗忘或总结),则 step()
方法会先返回这个 CondensationAction
,由 AgentController
处理后再次调用 step()
。 构建消息列表 (self._get_messages
) :
ConversationMemory
对象 (openhands.memory.conversation_memory.ConversationMemory
) 的 process_events
方法。 condensed_history
: 上一步精简后的事件列表。 initial_user_message
: 通过 self._get_initial_user_message(state.history)
从 完整历史记录 中获取的第一个用户 MessageAction
。这有助于确保对话上下文的正确起始。 max_message_chars
(用于截断过长的观察内容) 和 vision_is_active
(用于处理图像)。 ConversationMemory.process_events
):
_ensure_system_message
) :
condensed_history
的开头是否存在 SystemMessageAction
。 PromptManager
(openhands.utils.prompt.PromptManager
) 从预定义的模板文件 (位于 openhands/agenthub/codeact_agent/prompts/
目录,如 system.j2
) 加载并渲染系统提示。 SystemMessageAction
会被添加到消息列表的开头。 _ensure_initial_user_message
) : 确保初始用户消息紧随系统消息之后。 Message
对象 :
Action
事件 (_process_action
):
MessageAction
: 转换为 {"role": "user", "content": "..."}
格式的 Message
对象。如果 vision_is_active
且 image_urls
存在,图像信息也会被包含。 MessageAction
(非工具调用): 转换为 {"role": "assistant", "content": "..."}
格式的 Message
。 Action
(如 CmdRunAction
, IPythonRunCellAction
, FileEditAction
等):
Action
对象通常包含一个 tool_call_metadata
属性,其中存储了生成此动作的 LLM 的原始工具调用请求。 {"role": "assistant", "tool_calls": [{"id": "...", "type": "function", "function": {"name": "...", "arguments": "..."}}]}
格式的 Message
。tool_calls
部分直接来自 tool_call_metadata
中的 LLM 响应。content
部分可能包含 Agent 在调用工具前的思考或发言。 AgentThinkAction
: 其 thought
内容通常会通过对应的 AgentThinkObservation
转换。 Observation
事件 (_process_observation
):
Observation
(如 CmdOutputObservation
, BrowserOutputObservation
): 转换为 {"role": "tool", "tool_call_id": "...", "name": "...", "content": "..."}
格式的 Message
。tool_call_id
对应于先前 assistant
消息中 tool_calls
数组里相应工具调用的 id
。content
是工具执行的输出结果。 ErrorObservation
: 可能转换为 {"role": "tool", ...}
(如果与工具调用相关) 或 {"role": "assistant", ...}
(如果是一般性错误提示)。 UserMessageObservation
或用户通过其他方式提供的反馈: 转换为 {"role": "user", ...}
。 AgentThinkObservation
: 其 content
(即 Agent 的思考) 转换为 {"role": "assistant", "content": "..."}
或包含在其他相关的 assistant 消息中。 RecallObservation
: 如果包含微代理知识 (RecallType.KNOWLEDGE
) 或工作区上下文 (RecallType.WORKSPACE_CONTEXT
), PromptManager
会被用来构建相应的提示片段 (build_microagent_info
, build_workspace_context_info
),并作为 user
角色的消息插入。 Observation.content
可能会被截断。连续的同角色消息可能会被合并。 apply_prompt_caching
) : 如果 LLM 支持,某些消息(如系统提示或不常变化的用户输入)会被标记以便 LLM 进行缓存,减少重复处理。 list[Message]
对象,其中每个 Message
都有 role
(system
, user
, assistant
, tool
) 和 content
(以及可选的 tool_calls
或 tool_call_id
)。 准备 LLM 调用参数 :
list[Message]
对象通过 self.llm.format_messages_for_llm()
进行特定于 LLM 的最终格式化。 self.tools
(这是一个列表,其中每个工具都定义了其名称、描述和参数 schema,符合 OpenAI function calling 或类似工具使用的格式) 添加到参数中。 gemini-2.5-pro-preview-03-25
模型移除工具参数中的 default
字段,以避免兼容性问题。 extra_body
,其中包含如 session_id
等元数据,用于日志记录或追踪。 调用 LLM (self.llm.completion
) :
解析 LLM 响应 (self.response_to_actions
) :
ModelResponse
对象。 openhands.agenthub.codeact_agent.function_calling.response_to_actions
(或类似模块) 中处理。 MessageAction
。 Action
对象 (如 MessageAction
, CmdRunAction
, IPythonRunCellAction
, AgentFinishAction
等)。 Action
子类实例的属性 (这些属性在序列化时会构成 args
字典)。 Action
对象会保留来自 LLM 的 response_id
和 tool_call_metadata
(如果适用)。 list[Action]
对象。 添加到待处理队列 :
list[Action]
中的所有动作都被添加到 self.pending_actions
(一个 deque
) 的末尾。 返回首个 Action :
CodeActAgent.step()
方法从 self.pending_actions
队列的左侧弹出一个 Action
并返回。 pending_actions
队列中有多个 Action
(例如 LLM 一次请求调用多个工具,或者先思考再说一句话再调用工具),这些 Action
会在后续对 step()
的调用中(当 pending_actions
非空时,会直接从队列头部取)依次返回并执行,直到队列为空。 这个详细的流程解释了 Agent 如何基于历史记录、通过 LLM 的智能决策,并利用预定义的工具来生成具体的行动指令,从而驱动任务的进展。
AgentController
(在 OpenHands 中具体实现为 openhands.controller.agent_controller.AgentController
) 是整个 Agent 系统运行的”指挥中心”。它负责管理 Agent
的生命周期、维护核心状态 (State
)、协调与 EventStream
和 Runtime
(执行环境) 的交互,并驱动 Agent 的核心”思考-行动”循环。
以下是 AgentController
与 Agent
交互的关键方面和流程:
核心职责:
初始化与设置 :
AgentController
接收一个 Agent
实例 (如 CodeActAgent
)、一个 EventStream
实例、最大迭代次数等配置。 State
对象。如果提供了先前的状态 (例如,用于会话恢复),则加载该状态;否则,创建一个新的 State
。 _init_history()
方法,该方法从 EventStream
中读取历史事件,并用它们填充 State.history
列表。这确保了即使是新创建的 State
对象也能拥有完整的历史上下文。 AgentController
会向 EventStream
发布一个初始的 SystemMessageAction
(通过 _add_system_message()
),其中包含了 Agent 的角色定义和高级指令。Agent
在其 step()
方法中会处理这个系统消息以配置其行为。 AgentController
会订阅 EventStream
的事件,通过其 on_event
(内部为 _on_event
) 回调方法接收新的 Event
。 事件驱动的控制流 :
AgentController
的主要运作模式是事件驱动的,而非一个持续轮询的循环。 EventStream
中有新的 Event
(例如用户输入 MessageAction
,或 Runtime
执行完命令后的 Observation
) 发布时,AgentController
的 on_event
方法会被调用。 _on_event
方法内部,控制器会调用 should_step(event)
来判断当前事件是否应该触发 Agent 进行下一步思考。例如,用户的消息或一个非空的 Observation
通常会触发 step
。 驱动 Agent 执行 (_step
方法) :
should_step()
返回 True
,并且没有其他阻塞条件 (如 Agent 状态不是 RUNNING
、有待处理的 Action
、达到最大迭代次数等),AgentController
就会执行 _step()
方法。 Agent
之前,它会更新状态,例如增加 state.iteration
计数。 action = await self.agent.step(self.state)
。这里,当前的 State
对象被传递给 Agent
(例如 CodeActAgent
)。Agent
内部会进行历史精简、构建 Prompt、调用 LLM、解析响应,并返回一个具体的 Action
对象。 Agent
返回的 Action
后,AgentController
通过 self.event_stream.add_event(action, EventSource.AGENT)
将此 Action
发布回 EventStream
。 state.local_metrics
。 Action 的执行与 Observation 的接收 :
AgentController
将 Agent
生成的 Action
发布到 EventStream
后:
Runtime
(或其他订阅了相应 Action
类型的模块) 会从 EventStream
接收到这个 Action
。 Runtime
负责在相应的环境 (如沙箱) 中执行该 Action
(例如,运行一个 shell 命令)。 Runtime
会将执行结果封装成一个 Observation
(例如 CmdOutputObservation
),并将其发布回 EventStream
。 AgentController
(作为 EventStream
的订阅者) 会通过其 on_event
方法接收到这个新的 Observation
。 Observation
满足 should_step()
的条件,那么 AgentController
又会调用 _step()
,从而使 Agent
基于新的观察结果进行下一轮思考和决策,形成闭环。 状态管理与持久化 :
AgentController
持有并管理 State
对象的运行时实例。 State
对象本身负责其内容的序列化 (pickle) 和反序列化逻辑。AgentController
在初始化时确保从持久化存储 (通过 EventStream
间接) 加载历史。 close()
方法被调用) 时,它会确保 state.history
被 EventStream
中的完整事件序列填充。这一点很重要,因为在运行期间,State.history
可能只包含部分或经过筛选的事件视图,而 EventStream
中的文件才是所有事件的权威记录。 处理 Agent 的特殊输出 :
AgentFinishAction
: 如果 Agent
决定任务已完成并返回 AgentFinishAction
,AgentController
会相应地更新其内部状态至 AgentState.FINISHED
(通过 set_agent_state_to()
),这通常会停止 дальнейшие шаги. AgentDelegateAction
: OpenHands 支持多 Agent 协作。如果 Agent
返回 AgentDelegateAction
,当前的 AgentController
会启动一个新的(代理)AgentController
和 Agent
实例来处理子任务。 confirmation_mode
开启,并且 Agent
返回了一个需要确认的 Action
,AgentController
会将状态设置为 AgentState.AWAITING_USER_INPUT
,等待用户通过某种方式(如前端界面)提供确认或拒绝,然后将用户的响应作为新的 Event
注入 EventStream
。 错误处理与迭代限制 :
AgentController
监控迭代次数是否超过 state.max_iterations
。如果超过,它可能会根据配置暂停 Agent 或提示用户。 Agent
或 Runtime
在执行过程中遇到错误,通常会产生 ErrorObservation
。AgentController
接收到这个 Observation
后,可能会将 Agent 状态设置为 AgentState.ERROR
并记录错误信息。 简化流程图表示:
sequenceDiagram
participant User
participant EventStream as ES
participant AgentController as AC
participant Agent
participant Runtime
User->>ES: 输入 (MessageAction)
ES->>AC: on_event(MessageAction)
AC->>AC: should_step()? (True)
AC->>AC: _step()
AC->>Agent: step(State)
Agent-->>AC: 返回 Action
AC->>ES: add_event(Action)
ES->>Runtime: Action (若为可执行类型)
Runtime->>Runtime: 执行 Action
Runtime->>ES: add_event(Observation)
ES->>AC: on_event(Observation)
AC->>AC: should_step()? (True)
AC->>AC: _step()
AC->>Agent: step(State) // Agent 基于新 Observation 思考
Agent-->>AC: 返回 Action
AC->>ES: add_event(Action)
Note right of AC: 循环继续...
通过这种方式,AgentController
精心编排了 Agent
的思考、Action
的执行以及 Observation
的反馈,有效地驱动了整个智能任务的逐步完成。
Leave a Comment
Comments
Loading comments...