深度解析 OpenHands CodeActAgent: 智能代理的核心架构

CodeActAgent 是 OpenHands 框架中的核心组件,旨在通过统一的代码操作空间(CodeAct)简化和增强 LLM(大语言模型)代理的功能。本文将详细解析 CodeActAgent 的组成、功能和运作方式,并结合一个具体案例(TODO 应用)展示其工作流程。


CodeActAgent 的核心组成

CodeActAgent 是一个高度模块化的代理,其核心组成部分包括 Profile、Prompt 和 Memory。这些模块共同协作,确保代理能够高效地分解任务、调用工具并与用户交互。

1. Profile

Profile 是什么

  • Profile 是 CodeActAgent 的配置文件,定义了代理的行为、工具支持、内存管理等。
  • 它通过 AgentConfig 类进行配置,包含以下关键参数:
    • codeact_enable_browsing:是否启用浏览器工具。
    • codeact_enable_jupyter:是否启用 IPython 工具。
    • codeact_enable_llm_editor:是否启用基于 LLM 的文件编辑工具。
    • condenser:事件压缩器的配置,用于优化内存中的历史记录。
    • enable_prompt_extensions:是否启用扩展提示(Prompt)。

Profile 的结构

Profile 是一个字典或对象,包含以下关键参数:

config = AgentConfig(
    codeact_enable_browsing=True,
    codeact_enable_jupyter=True,
    codeact_enable_llm_editor=False,
    condenser={"type": "default", "max_events": 100},
    enable_prompt_extensions=True,
    disabled_microagents=["npm", "github"],
)

Profile 的作用

  • Profile 决定了 CodeActAgent 的能力范围和行为模式。
  • 例如:
    • 如果启用 codeact_enable_browsing,代理可以使用浏览器工具与网页交互。
    • 如果启用 codeact_enable_jupyter,代理可以运行 Python 代码来完成任务。

2. Prompt

Prompt 是什么

  • Prompt 是 CodeActAgent 与 LLM 交互的输入内容,定义了任务的上下文、目标和约束。
  • 它通过 PromptManager 动态生成,包含以下部分:
    • 系统消息:定义代理的角色和目标。
    • 用户消息:用户输入的任务描述。
    • 工具调用消息:代理调用工具的记录。
    • 观察消息:工具返回的结果。

Prompt 的结构

Prompt 是一个消息列表,包含以下类型的消息:

  1. 系统消息

    System: You are CodeActAgent, a highly capable assistant designed to perform tasks by executing code and interacting with tools.
    
  2. 用户消息

    User: 创建一个 TODO 应用,支持添加、删除和查看任务。
    
  3. 工具调用消息

    Assistant: Calling tool: execute_ipython_cell
    Tool arguments: {"code": "tasks = []"}
    
  4. 观察消息

    Observation: Tool returned: "Initialized an empty task list."
    

Prompt 的生成逻辑

Prompt 的生成是 CodeActAgent 与 LLM 交互的关键步骤,以下是其详细逻辑:

  1. 系统消息初始化

    • CodeActAgent 使用 PromptManager 添加系统消息,定义代理的角色和目标。
  2. 用户消息处理

    • 用户输入的任务描述被添加到消息列表中。
  3. 工具调用与观察消息

    • CodeActAgent 从 ConversationMemory 中提取工具调用记录和观察结果,并将其添加到 Prompt。
  4. 消息增强

    • 使用 PromptManager 为用户消息添加上下文信息(如示例或扩展信息)。

3. Memory

Memory 的作用

Memory 是 CodeActAgent 的会话内存模块,负责记录代理的对话历史、工具调用记录以及观察结果。它通过 ConversationMemoryCondenser 协同工作,确保代理能够在多轮交互中保持上下文一致性。

Memory 的结构

Memory 包含以下几个关键部分:

  1. 事件历史:存储所有的动作和观察,完整记录代理的交互过程。
  2. 压缩历史:通过 Condenser 压缩后的关键事件,优化存储空间并提高检索效率。
  3. 缓存:用于特定 LLM 的提示缓存,减少重复计算并提高响应速度。

Memory 的工作流程

  1. 事件记录

    • 每次工具调用或用户交互都会生成一个事件,并存储到事件历史中。
    • 示例:
      event = {"type": "tool_call", "tool": "execute_ipython_cell", "result": "Initialized task list."}
      memory.add_event(event)
      
  2. 事件压缩

    • 使用 Condenser 对事件历史进行压缩,提取关键事件以减少冗余。
    • 示例:
      condensed_events = memory.condenser.condensed_history(state)
      
  3. 上下文检索

    • 在生成 Prompt 时,Memory 提供上下文信息以确保对话的连贯性。
    • 示例:
      messages = memory.process_events(condensed_history=condensed_events)
      

在 TODO 应用中的作用

在实现 TODO 应用时,Memory 的主要作用包括:

  • 记录任务分解过程:存储每个子任务的工具调用记录和结果。
  • 维护上下文一致性:确保代理能够根据用户的后续输入调整任务。
  • 优化交互效率:通过事件压缩减少冗余信息,提高响应速度。

Memory 是什么

  • Memory 是代理的会话内存,用于存储对话历史、工具调用记录和观察结果。
  • 它通过 ConversationMemory 管理,支持事件压缩和历史记录的动态更新。

Memory 的结构

Memory 包含以下部分:

  1. 事件历史:存储所有的动作和观察。
  2. 压缩历史:通过 Condenser 压缩后的关键事件。
  3. 缓存:用于特定 LLM 的提示缓存。

Memory 的作用

  • Memory 记录了任务的分解过程和工具调用结果,确保代理能够在多轮对话中保持上下文一致。

结合案例:实现一个 TODO 应用

任务描述

用户输入任务:“创建一个 TODO 应用,支持添加、删除和查看任务。”

任务分解

CodeActAgent 的任务分解通过与 LLM 的交互实现,以下是具体流程:

  1. 用户输入任务

    • 用户输入任务描述:“创建一个 TODO 应用,支持添加、删除和查看任务。”
  2. 构建 Prompt

    • CodeActAgent 构建消息列表并发送给 LLM。
  3. 解析 LLM 响应

    • LLM 返回的响应被解析为一系列子任务:
      • 初始化任务列表。
      • 实现添加任务功能。
      • 实现删除任务功能。
      • 实现查看任务功能。

子任务分发

CodeActAgent 的子任务分发通过工具调用实现,以下是具体流程:

  1. 工具调用

    • 根据子任务调用相应的工具(如执行 Python 代码、编辑文件)。
    • 示例:
      action = CmdRunAction(command="tasks = []")
      result = action.execute()
      
  2. 结果存储

    • 工具返回的结果被存储为观察消息。
    • 示例:
      observation_message = Message(role="observation", content="Tool returned: \"Initialized an empty task list.\"")
      state.add_observation(observation_message)
      
  3. 反馈给用户

    • CodeActAgent 将工具调用结果反馈给用户。
    • 示例:
      final_message = Message(role="assistant", content="TODO 应用已创建,支持添加、删除和查看任务。")
      state.add_message(final_message)
      

完整实现示例

以下是一个完整的实现示例,展示如何使用 CodeActAgent 创建一个 TODO 应用:

from openhands.core.config import AgentConfig
from openhands.llm.llm import LLM
from openhands.agenthub.codeact_agent.codeact_agent import CodeActAgent
from openhands.controller.state.state import State
from openhands.core.message import Message

# 配置代理
config = AgentConfig(
    codeact_enable_browsing=False,
    codeact_enable_jupyter=True,
    codeact_enable_llm_editor=False,
    condenser={"type": "default", "max_events": 50},
    enable_prompt_extensions=True,
    disabled_microagents=[],
)

# 初始化 LLM 和代理
llm = LLM(model_name="gpt-4")
agent = CodeActAgent(llm=llm, config=config)

# 创建初始状态
state = State()

# 用户输入任务
user_message = Message(role="user", content="创建一个 TODO 应用,支持添加、删除和查看任务。")
state.add_message(user_message)

# 构建 Prompt 并与 LLM 交互
messages = agent._get_messages(state)
response = llm.completion(messages=agent.llm.format_messages_for_llm(messages))

# 解析 LLM 响应
actions = agent.tools.response_to_actions(response)

# 执行子任务
for action in actions:
    result = action.execute()
    observation_message = Message(role="observation", content=f"Tool returned: {result}")
    state.add_observation(observation_message)

# 反馈结果
final_message = Message(role="assistant", content="TODO 应用已创建,支持添加、删除和查看任务。")
state.add_message(final_message)

# 输出对话历史
for msg in state.history:
    print(f"{msg.role}: {msg.content}")

总结

通过上述分析和实现示例,我们可以清晰地了解 CodeActAgent 的 Prompt 和 Profile,以及它们在任务分解和子任务分发中的作用。您可以根据这些信息手动实现一个类似的代理,并根据任务需求动态生成 Prompt 和配置 Profile。

留言与讨论