一、项目基本信息
1.1 背景信息
OpenManus 是基于大语言模型(LLM)构建的智能体框架,致力于打造灵活、可扩展且功能强大的系统,助力 AI 借助各类工具与外界交互,攻克复杂任务。它与传统聊天机器人大相径庭,不局限于文本理解与生成,还具备强大实操技能。既能快速精准搜索信息,又能流畅浏览网页,代码执行、文件保存等操作也不在话下,已然成为真正实用的 “智能助手”。其核心的 “思考 - 行动” 循环独具匠心。智能体接到任务,先审慎分析当下状态与需求,完成 “思考” 步骤;随即挑选并启用恰当工具付诸 “行动”;再依据行动结果展开新一轮思考。如此循环,OpenManus 逐步拆解难题,始终精准把握任务全貌,高效满足用户需求。
1.2 项目主要目录结构
OpenManus 项目的 app 目录下主要包含了与智能体、配置、工具、流程等相关的模块,以下是根据代码片段总结的主要结构及各部分功能:
1.2.1 核心功能模块
agent 目录:包含智能体相关的实现。
-
manus.py:定义了 Manus 类,这是一个通用的多功能智能体,集成了多种工具,如 PythonExecute、BrowserUseTool 等。
-
config.py:负责项目的配置管理。定义了各种配置类,如 LLMSettings(大语言模型设置)、SearchSettings(搜索设置)、BrowserSettings(浏览器设置)、SandboxSettings(沙盒设置)和 MCPSettings(MCP 配置)。
tool 目录:包含各种工具的实现。
-
base.py:定义了工具的基类 BaseTool,所有具体工具类都继承自该类。BaseTool 类包含工具的基本属性(如名称、描述、参数)和执行方法,同时提供了将工具转换为函数调用格式的方法 to_param。
-
planning.py:实现了 PlanningTool 类,用于创建和管理解决复杂任务的计划。该工具支持创建计划、更新计划步骤、跟踪进度等操作。
-
search 目录:包含搜索工具相关的实现。
-
base.py:定义了 WebSearchEngine 基类和 SearchItem 类,WebSearchEngine 类提供了执行搜索的抽象方法,SearchItem 类用于表示单个搜索结果。
1.2.2 辅助功能模块
prompt 目录:包含提示信息的定义。
- manus.py:定义了 Manus 智能体的系统提示信息 SYSTEM_PROMPT 和下一步提示信息 NEXT_STEP_PROMPT,用于指导智能体的行为。
- planning.py:定义了规划工具的系统提示信息 PLANNING_SYSTEM_PROMPT 和下一步提示信息 NEXT_STEP_PROMPT,帮助智能体更好地使用规划工具。
- schema.py:定义了项目中使用的各种数据模型和枚举类型。
- Role 枚举:定义了消息的角色选项,包括 SYSTEM、USER、ASSISTANT 和 TOOL。
- ToolChoice 枚举:定义了工具选择的选项,如 NONE、AUTO 和 REQUIRED。
- AgentState 枚举:定义了智能体的执行状态,如 IDLE、RUNNING、FINISHED 和 ERROR。
- Message 类:表示对话中的消息,包含角色、内容、工具调用等信息,并提供了消息的创建和转换方法。
- Memory 类:用于管理智能体的内存,支持添加消息、获取最近消息等操作。 flow 目录:包含执行流程相关的实现。
- base.py:定义了 BaseFlow 类,作为执行流程的基类,支持多个智能体的协作。该类提供了管理智能体的方法,如获取主智能体、添加智能体等,并定义了执行流程的抽象方法 execute。
1.2.3 其他模块
- init.py:对 Python 版本进行检查,确保使用的 Python 版本在 3.11 - 3.13 之间。如果版本不兼容,会输出警告信息。
1.3 核心组件介绍
1.3.1 智能体系统
智能体系统作为 OpenManus 的核心,采用层次化设计,使得代码模块化且可扩展性强,各层次专注自身职责:
- BaseAgent:提供基本的状态管理和执行循环功能。
- ReActAgent:实现思考-行动循环模式。
- ToolCallAgent:实现工具调用机制,确保智能体能够根据需求调用合适的工具。
- Manus:是集成多种工具的具体智能体实现。
1.3.2 工具系统
工具系统为智能体提供与外部世界交互的能力,每个工具都有明确的名称、描述和参数规范,便于大语言模型(LLM)正确选择和使用:
- BaseTool:所有工具的抽象基类,定义了工具的基本属性和方法。
- ToolCollection:工具的集合和管理器,负责管理和执行工具调用。
- 具体工具:如
PythonExecute、GoogleSearch、BrowserUseTool等。在代码中,BrowserUseTool在OpenManus/app/agent/manus.py中被使用,智能体在执行任务时会根据需要调用这些具体工具。
1.3.3 记忆系统
记忆系统使智能体能够在多个步骤中保持上下文连贯性,记录用户输入、LLM 响应和工具执行结果,帮助智能体基于历史信息做出决策:
- Memory:存储交互历史的容器。在
OpenManus/app/schema.py中,Memory类管理智能体的记忆,支持添加消息、获取最近消息和清空消息等操作。 - Message:表示不同类型消息的结构。
Message类定义了消息的角色(如SYSTEM、USER、ASSISTANT、TOOL)、内容、工具调用等信息,并提供了消息的创建和转换方法。
1.3.4 LLM 接口
LLM 接口负责与大语言模型(如 OpenAI 的 GPT 模型)通信,将智能体的记忆和工具信息传递给 LLM,并解析 LLM 的响应:
- LLM:封装了与 LLM API 的交互。代码中虽未详细展示其实现,但可推测它负责与外部大语言模型进行通信,发送请求并接收响应。
- ToolResponse:表示 LLM 响应的结构,不过在提供的代码片段中未直接体现其具体实现。
1.3.5 流程控制
流程控制组件管理不同类型的执行流程,使 OpenManus 能够支持不同的执行模式,如规划式执行:
- BaseFlow:所有流程的抽象基类,为具体流程类提供基础框架。在
OpenManus/app/flow/base.py中,BaseFlow类支持多个智能体的协作,定义了执行流程的抽象方法execute。 - PlanningFlow:实现规划和执行的流程。在
OpenManus/app/flow/planning.py中,PlanningFlow类根据输入创建初始计划,然后逐步执行计划中的步骤,直到计划完成或出现异常。 - FlowFactory:创建不同类型流程的工厂。
1.4 技术亮点
OpenManus 具备诸多极为显著的技术亮点:
其一,异步编程特性:它巧妙且广泛地运用 async/await 语法开展异步操作,这一举措极大地提升了输入/输出(I/O)的效率,使得系统在处理各类任务时能够更加流畅地与外部设备及资源交互,避免因等待数据传输而造成的时间浪费。
其二,模块化精巧设计:组件划分明确清晰,各组件间的接口定义精准规范。如此一来,无论是后续的维护工作,还是根据新需求进行功能扩展,开发人员都能依据既定的模块架构迅速定位问题、高效添加新功能,极大地增强了整个系统的可操作性与适应性。
其三,严谨的错误处理机制:构建起多层次的错误捕获及恢复体系,能够全方位、无死角地监测运行过程中可能出现的各类异常情况。一旦错误发生,系统能够迅速响应,有条不紊地按照预设的恢复流程进行处理,确保整体运行的稳定性,有效降低因局部错误导致系统崩溃的风险。
其四,工具抽象统一化:设计了统一的工具接口,这就像是为系统配备了一个万能插座,无论后续需要接入何种新型工具,只需遵循该接口规范,就能轻松实现对接,无缝融入 OpenManus 生态,为系统功能的多样化发展提供了无限可能。
其五,卓越的记忆管理系统:拥有一套完善的记忆机制,尤其在面对需要上下文连贯配合的多步骤复杂任务时,能够精准地存储、调用关键信息,让智能体如同拥有真正的人类记忆一般,时刻清楚任务的前因后果,有条不紊地推进各项任务,展现出极高的任务执行连贯性。 这些独树一帜的特点,合力塑造了 OpenManus 成为一个既强劲有力又灵活多变的智能体框架,使之有足够的实力从容应对各式各样错综复杂的任务场景。
在后续的章节里,下文将会深入到 OpenManus 的各个细微组件以及内部运行机制之中,将其神秘的面纱彻底揭开,全方位展现其背后的工作原理。
综上所述,OpenManus 项目的 app 目录结构清晰,各个模块分工明确,通过合理的设计和组织,实现了智能体、工具、配置和流程的有效管理和协作。
二、智能体的核心设计实现
2.1 BaseAgent:智能体的基座
BaseAgent 是所有智能体的基类,位于 app/agent/base.py中,它是智能体的基座实现。
在 BaseAgent 类中,核心属性和主要方法如下:
核心属性
-
name: 字符串类型,表示代理的唯一名称。
name: str = Field(..., description="Unique name of the agent") -
description: 可选的字符串类型,表示代理的描述。
description: Optional[str] = Field(None, description="Optional agent description") -
system_prompt: 可选的字符串类型,系统级别指令提示。
system_prompt: Optional[str] = Field(None, description="System-level instruction prompt") -
next_step_prompt: 可选的字符串类型,用于确定下一步操作的提示。
next_step_prompt: Optional[str] = Field(None, description="Prompt for determining next action") -
llm: LLM 类型的实例,默认值为通过
default_factory创建的LLM实例。llm: LLM = Field(default_factory=LLM, description="Language model instance") -
memory: Memory 类型的实例,默认值为通过
default_factory创建的Memory实例。memory: Memory = Field(default_factory=Memory, description="Agent's memory store") -
state: 表示代理当前状态的
AgentState类型,默认值为AgentState.IDLE。state: AgentState = Field(default=AgentState.IDLE, description="Current agent state") -
max_steps: 代理的最大步骤数,默认为10。
max_steps: int = Field(default=10, description="Maximum steps before termination") -
current_step: 代理当前执行的步骤数,默认为0。
current_step: int = Field(default=0, description="Current step in execution") -
duplicate_threshold: 检测重复状态的阈值,默认为2。
duplicate_threshold: int = 2
主要方法
-
initialize_agent: 模型验证器方法,在实例化后为未提供的凭据设置默认值。
@model_validator(mode="after") def initialize_agent(self) -> "BaseAgent": -
state_context: 状态上下文管理器,用于安全地在代理执行期间切换状态。
@asynccontextmanager async def state_context(self, new_state: AgentState): -
update_memory: 向代理的存储中添加一条消息。消息的内容可以是文本或图像。
def update_memory(self, role: ROLE_TYPE, content: str, base64_image: Optional[str] = None, **kwargs): -
run: 代理的主执行循环,异步执行代理逻辑直到达到最大步骤数或执行完成。
async def run(self, request: Optional[str] = None) -> str: -
step: 必须由子类实现的抽象方法,定义了单步代理行为的逻辑。
@abstractmethod async def step(self) -> str: -
handle_stuck_state: 处理代理由于重复响应陷入停滞状态的情况,通过修改
next_step_prompt来帮助代理改变策略。def handle_stuck_state(self): -
is_stuck: 用于检测代理是否处于停滞状态,判断依据是最近的消息内容中是否包含多次重复的代理响应。
def is_stuck(self) -> bool: -
messages:
- property 方法,返回代理存储中的消息列表。
@property def messages(self) -> List[Message]: - setter 方法,允许设置代理存储中的消息列表。
@messages.setter def messages(self, value: List[Message]):
- property 方法,返回代理存储中的消息列表。
这些属性和方法共同构成了 BaseAgent 类的基础功能,允许代理持续地管理其状态和执行循环。
BaseAgent的run方法是智能体执行的核心,它实现了一个循环,在循环中不断调用step方法,直到任务完成或达到最大步骤数:
async def run(self, request: Optional[str] = None) -> str:
"""Execute the agent's main loop asynchronously.
Args:
request: Optional initial user request to process.
Returns:
A string summarizing the execution results.
Raises:
RuntimeError: If the agent is not in IDLE state at start.
"""
if self.state != AgentState.IDLE:
raise RuntimeError(f"Cannot run agent from state: {self.state}")
if request:
self.update_memory("user", request)
results: List[str] = []
async with self.state_context(AgentState.RUNNING):
while (
self.current_step < self.max_steps and self.state != AgentState.FINISHED
):
self.current_step += 1
logger.info(f"Executing step {self.current_step}/{self.max_steps}")
step_result = await self.step()
# Check for stuck state
if self.is_stuck():
self.handle_stuck_state()
results.append(f"Step {self.current_step}: {step_result}")
if self.current_step >= self.max_steps:
self.current_step = 0
self.state = AgentState.IDLE
results.append(f"Terminated: Reached max steps ({self.max_steps})")
await SANDBOX_CLIENT.cleanup()
return "\n".join(results) if results else "No steps executed"
2.2 ReActAgent: 思考-行动循环模式的视线
ReActAgent继承自BaseAgent,位于app/agent/react.py中。它实现了思考-行动循环模式,这是一种强大的智能体决策框架。
核心方法
1. think 方法
@abstractmethod
async def think(self) -> bool:
"""Process current state and decide next action"""
- 功能:这是一个抽象方法,用于处理智能体的当前状态并决定下一步是否需要采取行动。由于它是抽象方法,所以具体的实现逻辑需要在子类中完成。
- 参数:无
- 返回值:返回一个布尔值。如果返回
True,表示需要采取行动;如果返回False,表示思考完成,无需采取行动。
2. act 方法
@abstractmethod
async def act(self) -> str:
"""Execute decided actions"""
- 功能:同样是一个抽象方法,用于执行智能体已经决定好的行动。具体的行动执行逻辑需要在子类中实现。
- 参数:无
- 返回值:返回一个字符串,表示行动执行后的结果。
3. step 方法
async def step(self) -> str:
"""Execute a single step: think and act."""
should_act = await self.think()
if not should_act:
return "Thinking complete - no action needed"
return await self.act()
- 功能:执行一个完整的步骤,该步骤包含思考和行动两个阶段。首先调用
think方法决定是否需要采取行动,如果需要则调用act方法执行行动。 - 参数:无
- 返回值:如果
think方法返回False,则返回字符串"Thinking complete - no action needed";如果think方法返回True,则返回act方法的执行结果。
这些方法共同构成了 ReActAgent 类的核心逻辑,通过 think 和 act 方法的组合,实现了智能体的思考和行动循环。子类需要实现 think 和 act 方法来具体定义智能体的行为。
2.3 ToolCallAgent: 工具调用机制剖析
ToolCallAgent继承自ReActAgent,位于app/agent/toolcall.py中。它实现了工具调用机制,使智能体能够使用各种工具来完成任务。
toolcall.py 文件定义了 ToolCallAgent 类,该类继承自 ReActAgent,用于处理工具调用。以下是该类的核心方法和属性介绍:
核心属性
name:代理的名称,默认为"toolcall"。description:代理的描述信息,表明该代理可以执行工具调用。system_prompt:系统提示信息,用于指导代理的行为,默认值从app.prompt.toolcall模块中获取。next_step_prompt:下一步提示信息,用于引导代理决定下一步的操作,默认值从app.prompt.toolcall模块中获取。available_tools:可用工具的集合,初始包含CreateChatCompletion和Terminate工具。tool_choices:工具选择模式,默认为ToolChoice.AUTO,表示自动选择工具。special_tool_names:特殊工具名称列表,默认包含Terminate工具的名称,执行这些工具可能会导致代理结束任务。tool_calls:待执行的工具调用列表,初始为空。_current_base64_image:当前工具调用返回的 base64 编码图像,初始为None。max_steps:代理执行的最大步数,默认为30。max_observe:观察结果的最大长度,可选类型为整数或布尔值,初始为None。
核心方法
think方法
async def think(self) -> bool:
...
- 功能:处理当前状态并决定下一步的操作,使用工具进行思考。
- 步骤:
- 添加下一步提示信息到消息列表。
- 调用语言模型的
ask_tool方法获取包含工具选项的响应。 - 处理可能的异常,如令牌限制错误。
- 记录响应信息,包括思考内容、选择的工具和工具参数。
- 根据工具选择模式处理响应,创建并添加助手消息到内存。
- 返回是否有工具调用需要执行。
act方法
async def act(self) -> str:
...
-
功能:执行工具调用并处理结果。
-
步骤:
- 检查是否有工具调用,如果没有且工具调用是必需的,则抛出异常;否则返回最后一条消息的内容。
- 遍历工具调用列表,执行每个工具调用并处理结果。
- 记录工具执行结果,并将工具响应添加到内存。
- 返回所有工具执行结果的组合字符串。
execute_tool方法
async def execute_tool(self, command: ToolCall) -> str:
...
-
功能:执行单个工具调用,并进行健壮的错误处理。
-
步骤:
- 检查命令格式是否有效,工具名称是否可用。
- 解析工具参数并执行工具。
- 处理特殊工具调用,可能会导致代理结束任务。
- 检查工具执行结果是否包含 base64 编码图像。
- 格式化工具执行结果并返回。
- 处理可能的异常,如 JSON 解析错误和其他异常。
_handle_special_tool方法
async def _handle_special_tool(self, name: str, result: Any, **kwargs):
...
-
功能:处理特殊工具调用并更新代理状态。
-
步骤:
- 检查工具名称是否为特殊工具。
- 如果是特殊工具且满足结束条件,则将代理状态设置为
FINISHED。
_should_finish_execution方法
@staticmethod
def _should_finish_execution(**kwargs) -> bool:
...
- 功能:判断工具执行是否应该结束代理任务,默认返回
True。
_is_special_tool方法
def _is_special_tool(self, name: str) -> bool:
...
- 功能:检查工具名称是否在特殊工具列表中。
cleanup方法
async def cleanup(self):
...
-
功能:清理代理使用的工具资源。
-
步骤:
- 遍历可用工具列表,检查工具是否有
cleanup方法。 - 如果有,则异步调用
cleanup方法并处理可能的异常。
- 遍历可用工具列表,检查工具是否有
run方法
async def run(self, request: Optional[str] = None) -> str:
...
-
功能:运行代理并在完成后进行清理。
-
步骤:
- 调用父类的
run方法执行代理任务。 - 无论任务是否成功,最后都调用
cleanup方法清理资源。
- 调用父类的
这些方法和属性共同构成了 ToolCallAgent 类的核心功能,使其能够根据系统提示和用户输入,思考并执行工具调用,同时处理异常和清理资源。
2.4 Manus: 智能体集成
manus.py 文件定义了 Manus 类,它继承自 ToolCallAgent,是一个通用的多功能代理,能够使用多种工具解决各种任务。以下是 Manus 类的核心属性和方法介绍:
核心属性
name- 类型:
str - 说明:代理的名称,设置为
"Manus"。
- 类型:
description- 类型:
str - 说明:对代理的描述,表明该代理是一个可以使用多种工具解决各种任务的多功能代理。
- 类型:
system_prompt- 类型:
str - 说明:系统提示信息,使用
SYSTEM_PROMPT格式化工作空间根目录后得到,用于指导代理的行为。
- 类型:
next_step_prompt- 类型:
str - 说明:下一步提示信息,用于引导代理决定下一步的操作,默认值从
app.prompt.manus模块中获取。
- 类型:
max_observe- 类型:
int - 说明:观察结果的最大长度,设置为
10000。
- 类型:
max_steps- 类型:
int - 说明:代理执行的最大步数,设置为
20。
- 类型:
available_tools- 类型:
ToolCollection - 说明:可用工具的集合,包含
PythonExecute、BrowserUseTool、StrReplaceEditor和Terminate工具。
- 类型:
special_tool_names- 类型:
list[str] - 说明:特殊工具名称列表,默认包含
Terminate工具的名称,执行这些工具可能会导致代理结束任务。
- 类型:
browser_context_helper- 类型:
Optional[BrowserContextHelper] - 说明:浏览器上下文助手,用于处理与浏览器相关的上下文信息,初始为
None。
- 类型:
核心方法
initialize_helper方法
@model_validator(mode="after")
def initialize_helper(self) -> "Manus":
self.browser_context_helper = BrowserContextHelper(self)
return self
- 功能:在模型验证后初始化
browser_context_helper属性,确保代理能够处理与浏览器相关的上下文信息。 - 返回值:返回
Manus实例本身。
think方法
async def think(self) -> bool:
original_prompt = self.next_step_prompt
recent_messages = self.memory.messages[-3:] if self.memory.messages else []
browser_in_use = any(
tc.function.name == BrowserUseTool().name
for msg in recent_messages
if msg.tool_calls
for tc in msg.tool_calls
)
if browser_in_use:
self.next_step_prompt = (
await self.browser_context_helper.format_next_step_prompt()
)
result = await super().think()
# Restore original prompt
self.next_step_prompt = original_prompt
return result
- 功能:处理当前状态并决定下一步的操作,根据最近的消息判断是否正在使用浏览器工具,如果是,则使用
browser_context_helper格式化下一步提示信息。 - 步骤:
- 保存原始的下一步提示信息。
- 获取最近的三条消息。
- 检查最近的消息中是否有使用
BrowserUseTool工具的调用。 - 如果正在使用浏览器工具,则使用
browser_context_helper格式化下一步提示信息。 - 调用父类的
think方法进行思考。 - 恢复原始的下一步提示信息。
- 返回思考结果。
cleanup方法
async def cleanup(self):
if self.browser_context_helper:
await self.browser_context_helper.cleanup_browser()
- 功能:清理
Manus代理使用的资源,主要是清理浏览器上下文。 - 步骤:
- 检查
browser_context_helper是否存在。 - 如果存在,则调用
browser_context_helper的cleanup_browser方法清理浏览器资源。
- 检查
这些属性和方法共同构成了 Manus 类的核心功能,使其能够根据系统提示和用户输入,灵活地使用多种工具解决各种任务,并在需要时处理浏览器相关的上下文信息。同时,代理在结束任务时会清理使用的资源,确保资源的有效利用。
2.5 小结
智能体执行流程
流程分析
流程图解释
-
开始:流程从创建
Manus实例开始。 -
初始化:初始化
browser_context_helper。 -
接收请求:用户输入请求,调用
run方法开始执行。 -
思考决策:在
think方法中,判断是否使用浏览器工具,若使用则更新提示信息,否则保持原提示信息,然后调用父类的think方法进行思考决策。 -
工具执行:根据思考结果调用
act方法执行工具。 -
循环执行:在
run方法中,循环执行思考决策和工具执行步骤,直到达到最大步数或任务完成。 -
资源清理:任务完成后,调用
cleanup方法清理资源。 -
结束:流程结束。
三、工具系统核心设计实现
OpenManus 项目的工具系统设计实现是其核心功能之一,它围绕工具的定义、管理、执行以及与代理的交互展开,旨在让代理能够高效地利用各种工具完成复杂任务。以下是对该工具系统设计实现的详细介绍:
3.1 工具定义与基类
BaseTool类:在app/tool/base.py文件中定义了BaseTool类,它是所有工具的基类。该类包含抽象方法execute,用于执行工具并接收参数。同时,提供了to_param方法,将工具转换为函数调用格式,方便与 LLM 交互。
@abstractmethod
async def execute(self, **kwargs) -> Any:
"""Execute the tool with given parameters."""
def to_param(self) -> Dict:
"""Convert tool to function call format."""
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": self.parameters,
},
}
- 工具结果类
ToolResult:同样在app/tool/base.py中定义了ToolResult类,用于表示工具执行的结果。它包含输出、错误信息、Base64 图像和系统信息等字段,并提供了合并结果和字符串表示的方法。
class ToolResult(BaseModel):
output: Any = Field(default=None)
error: Optional[str] = Field(default=None)
base64_image: Optional[str] = Field(default=None)
system: Optional[str] = Field(default=None)
def __bool__(self):
return any(getattr(self, field) for field in self.__fields__)
def __add__(self, other: "ToolResult"):
# 合并结果的逻辑
pass
def __str__(self):
return f"Error: {self.error}" if self.error else self.output
3.2 工具集合管理
ToolCollection类:在app/tool/tool_collection.py文件中定义了ToolCollection类,用于管理多个工具。它可以初始化工具列表,提供工具的执行、获取和添加等操作。
class ToolCollection:
def __init__(self, *tools: BaseTool):
self.tools = tools
self.tool_map = {tool.name: tool for tool in tools}
async def execute(self, *, name: str, tool_input: Dict[str, Any] = None) -> ToolResult:
tool = self.tool_map.get(name)
if not tool:
return ToolFailure(error=f"Tool {name} is invalid")
try:
result = await tool(**tool_input)
return result
except ToolError as e:
return ToolFailure(error=e.message)
def get_tool(self, name: str) -> BaseTool:
return self.tool_map.get(name)
def add_tool(self, tool: BaseTool):
self.tools += (tool,)
self.tool_map[tool.name] = tool
return self
3.3 工具与代理的交互
-
ToolCallAgent类:在app/agent/toolcall.py文件中定义了ToolCallAgent类,它是处理工具调用的基础代理类。该类包含思考和行动两个核心方法。- 思考方法
think:根据当前状态和可用工具,向 LLM 请求响应,并根据工具选择模式处理响应。
- 思考方法
async def think(self) -> bool:
response = await self.llm.ask_tool(
messages=self.messages,
system_msgs=(
[Message.system_message(self.system_prompt)]
if self.system_prompt
else None
),
tools=self.available_tools.to_params(),
tool_choice=self.tool_choices,
)
self.tool_calls = response.tool_calls if response and response.tool_calls else []
# 处理不同工具选择模式的逻辑
pass
- 行动方法
act:执行工具调用并处理结果,将工具响应添加到内存中。
async def act(self) -> str:
results = []
for command in self.tool_calls:
result = await self.execute_tool(command)
tool_msg = Message.tool_message(
content=result,
tool_call_id=command.id,
name=command.function.name,
base64_image=self._current_base64_image,
)
self.memory.add_message(tool_msg)
results.append(result)
return "\n\n".join(results)
- 执行工具方法
execute_tool:解析工具调用的参数,执行工具并处理特殊工具。
async def execute_tool(self, command: ToolCall) -> str:
name = command.function.name
args = json.loads(command.function.arguments or "{}")
result = await self.available_tools.execute(name=name, tool_input=args)
await self._handle_special_tool(name=name, result=result)
# 格式化结果的逻辑
pass
3.4 特殊工具处理
_handle_special_tool方法:在ToolCallAgent类中定义了_handle_special_tool方法,用于处理特殊工具的执行和状态变化。当执行特殊工具且满足结束条件时,将代理状态设置为已完成。
async def _handle_special_tool(self, name: str, result: Any, **kwargs):
if not self._is_special_tool(name):
return
if self._should_finish_execution(name=name, result=result, **kwargs):
self.state = AgentState.FINISHED
3.5 工具的清理
cleanup方法:在ToolCallAgent类中定义了cleanup方法,用于清理代理使用的工具资源。它会遍历所有可用工具,调用具有cleanup方法的工具进行清理。
async def cleanup(self):
for tool_name, tool_instance in self.available_tools.tool_map.items():
if hasattr(tool_instance, "cleanup") and asyncio.iscoroutinefunction(
tool_instance.cleanup
):
try:
await tool_instance.cleanup()
except Exception as e:
logger.error(
f"🚨 Error cleaning up tool '{tool_name}': {e}", exc_info=True
)
3.6 具体工具示例
Manus代理:在app/agent/manus.py文件中定义了Manus代理,它继承自ToolCallAgent类,并添加了通用工具,如PythonExecute、BrowserUseTool等。同时,根据浏览器工具的使用情况,动态调整下一步提示。
class Manus(ToolCallAgent):
available_tools: ToolCollection = Field(
default_factory=lambda: ToolCollection(
PythonExecute(), BrowserUseTool(), StrReplaceEditor(), Terminate()
)
)
async def think(self) -> bool:
original_prompt = self.next_step_prompt
browser_in_use = any(
tc.function.name == BrowserUseTool().name
for msg in recent_messages
if msg.tool_calls
for tc in msg.tool_calls
)
if browser_in_use:
self.next_step_prompt = (
await self.browser_context_helper.format_next_step_prompt()
)
result = await super().think()
self.next_step_prompt = original_prompt
return result
3.7 MCP 工具系统
MCPClients类:在app/tool/mcp.py文件中定义了MCPClients类,它继承自ToolCollection类,用于连接 MCP 服务器并管理可用工具。可以通过 SSE 或 stdio 传输方式连接服务器,初始化会话并列出工具。
class MCPClients(ToolCollection):
async def connect_sse(self, server_url: str) -> None:
streams_context = sse_client(url=server_url)
streams = await self.exit_stack.enter_async_context(streams_context)
self.session = await self.exit_stack.enter_async_context(
ClientSession(*streams)
)
await self._initialize_and_list_tools()
async def _initialize_and_list_tools(self) -> None:
await self.session.initialize()
response = await self.session.list_tools()
# 清理现有工具并创建新的工具对象
pass
3.8 总结
OpenManus 项目的工具系统设计实现了工具的定义、管理、执行和与代理的交互,通过工具集合和代理类的协作,使得代理能够根据任务需求选择合适的工具,并处理工具执行的结果。同时,提供了工具清理和特殊工具处理的功能,确保系统的稳定性和可靠性。此外,还支持与 MCP 服务器的连接,扩展了工具的来源和使用场景。
四、错误处理和容错机制
OpenManus 项目通过自定义异常类、超时处理和资源清理等机制,确保在面对各种错误情况时能够稳定运行,减少潜在的风险和损失。以下从异常类定义、超时处理、资源清理和测试用例等方面进行详细介绍:
4.1 自定义异常类
项目中定义了多个自定义异常类,用于处理不同类型的错误:
ToolError:当工具执行过程中遇到错误时抛出。例如,在_BashSession类的run方法中,如果会话未启动、bash 已退出或操作超时,会抛出该异常。
class ToolError(Exception):
def __init__(self, message):
self.message = message
OpenManusError:作为所有OpenManus错误的基类异常。
class OpenManusError(Exception):
"""Base exception for all OpenManus errors"""
TokenLimitExceeded:当请求超过令牌限制时抛出。
class TokenLimitExceeded(OpenManusError):
"""Exception raised when the token limit is exceeded"""
SandboxError:作为沙箱相关错误的基类异常。
class SandboxError(Exception):
"""Base exception for sandbox-related errors."""
SandboxTimeoutError:当沙箱操作超时时抛出。
class SandboxTimeoutError(SandboxError):
"""Exception raised when a sandbox operation times out."""
SandboxResourceError:当沙箱遇到资源相关错误时抛出。
class SandboxResourceError(SandboxError):
"""Exception raised for resource-related errors."""
4.2 超时处理
_BashSession类:在run方法中,使用asyncio.timeout为命令执行设置了超时时间(默认为 120 秒)。如果命令执行时间超过该限制,会抛出ToolError异常。
try:
async with asyncio.timeout(self._timeout):
while True:
await asyncio.sleep(self._output_delay)
output = self._process.stdout._buffer.decode()
if self._sentinel in output:
output = output[: output.index(self._sentinel)]
break
except asyncio.TimeoutError:
self._timed_out = True
raise ToolError(
f"timed out: bash has not returned in {self._timeout} seconds and must be restarted",
) from None
PythonExecute类:在execute方法中,使用multiprocessing.Process执行 Python 代码,并通过proc.join(timeout)设置了执行超时时间(默认为 5 秒)。如果超时,会终止进程并返回错误信息。
proc = multiprocessing.Process(
target=self._run_code, args=(code, result, safe_globals)
)
proc.start()
proc.join(timeout)
if proc.is_alive():
proc.terminate()
proc.join(1)
return {
"observation": f"Execution timeout after {timeout} seconds",
"success": False,
}
4.3 资源清理
DockerSandbox类:在create方法中,如果创建沙箱容器时出现异常,会调用cleanup方法确保资源被清理,避免资源泄漏。
try:
# 创建沙箱容器的相关操作
except Exception as e:
await self.cleanup() # Ensure resources are cleaned up
raise RuntimeError(f"Failed to create sandbox: {e}") from e
4.4 错误日志记录
项目使用 loguru 库进行日志记录,在关键操作中记录错误信息,方便后续排查问题。例如,在 PlanningFlow 类的 execute 和 _execute_step 方法中,使用 logger.error 记录执行过程中出现的错误。
try:
# 执行步骤的相关操作
except Exception as e:
logger.error(f"Error executing step {self.current_step_index}: {e}")
return f"Error executing step {self.current_step_index}: {str(e)}"
4.5 测试用例中的错误处理验证
在测试文件中,编写了多个测试用例来验证错误处理机制的有效性:
test_local_error_handling:测试本地沙箱的错误处理,尝试读取和复制不存在的文件,验证是否抛出相应的错误。
@pytest.mark.asyncio
async def test_local_error_handling(local_client: LocalSandboxClient):
await local_client.create()
with pytest.raises(Exception) as exc:
await local_client.read_file("/nonexistent.txt")
assert "not found" in str(exc.value).lower()
with pytest.raises(Exception) as exc:
await local_client.copy_from("/nonexistent.txt", "local.txt")
assert "not found" in str(exc.value).lower()
test_sandbox_error_handling:测试使用无效配置创建沙箱时的错误处理,验证是否抛出异常。
@pytest.mark.asyncio
async def test_sandbox_error_handling():
invalid_config = SandboxSettings(image="nonexistent:latest", work_dir="/invalid")
sandbox = DockerSandbox(invalid_config)
with pytest.raises(Exception):
await sandb
在 OpenManus 项目里,信息的保存、传递与使用机制是保障系统正常运行与功能实现的关键。下面将从信息保存、信息传递和信息使用三个方面详细阐述。
五、消息保存、传递和使用机制
5.1 信息保存机制
1. 内存保存
Message类:定义了聊天消息的结构,包含role(角色)、content(内容)、tool_calls(工具调用)等字段,可保存聊天过程中的各类消息。
class Message(BaseModel):
role: ROLE_TYPE = Field(...)
content: Optional[str] = Field(default=None)
tool_calls: Optional[List[ToolCall]] = Field(default=None)
# 其他字段...
Memory类:负责管理消息列表,具备添加消息、添加多条消息、清空消息、获取最近消息以及将消息转换为字典列表等功能。
class Memory(BaseModel):
messages: List[Message] = Field(default_factory=list)
max_messages: int = Field(default=100)
def add_message(self, message: Message) -> None:
self.messages.append(message)
# 可选:实现消息数量限制
if len(self.messages) > self.max_messages:
self.messages = self.messages[-self.max_messages:]
# 其他方法...
2. 文件保存
FileOperator协议:定义了文件操作的接口,包含读取文件、写入文件、检查路径是否为目录、检查路径是否存在以及运行 shell 命令等方法,可实现信息在文件中的保存。
@runtime_checkable
class FileOperator(Protocol):
async def read_file(self, path: PathLike) -> str:
...
async def write_file(self, path: PathLike, content: str) -> None:
...
# 其他方法...
5.2 信息传递机制
1. 消息传递
BaseAgent类:通过update_memory方法将消息添加到内存中,实现信息在代理内部的传递。
class BaseAgent(BaseModel, ABC):
def update_memory(self, role: ROLE_TYPE, content: str, base64_image: Optional[str] = None, **kwargs) -> None:
message_map = {
"user": Message.user_message,
"system": Message.system_message,
"assistant": Message.assistant_message,
"tool": lambda content, **kw: Message.tool_message(content, **kw),
}
if role not in message_map:
raise ValueError(f"Unsupported message role: {role}")
kwargs = {"base64_image": base64_image, **(kwargs if role == "tool" else {})}
self.memory.add_message(message_map[role](content, **kwargs))
2. 工具调用传递
MCPAgent类:连接到 MCP 服务器,将服务器的工具添加到代理的工具接口中,实现工具信息的传递。
class MCPAgent(ToolCallAgent):
async def initialize(self, connection_type: Optional[str] = None, server_url: Optional[str] = None, command: Optional[str] = None, args: Optional[List[str]] = None) -> None:
# 连接到 MCP 服务器
if self.connection_type == "sse":
await self.mcp_clients.connect_sse(server_url=server_url)
elif self.connection_type == "stdio":
await self.mcp_clients.connect_stdio(command=command, args=args or [])
self.available_tools = self.mcp_clients
# 存储初始工具模式
await self._refresh_tools()
# 添加系统消息和可用工具信息
tool_names = list(self.mcp_clients.tool_map.keys())
tools_info = ", ".join(tool_names)
self.memory.add_message(
Message.system_message(
f"{self.system_prompt}\n\nAvailable MCP tools: {tools_info}"
)
)
5.3 信息使用机制
1. 消息使用
LLM类:通过format_messages方法将消息转换为 OpenAI 消息格式,供大语言模型使用。
class LLM:
@staticmethod
def format_messages(messages: List[Union[dict, Message]], supports_images: bool = False) -> List[dict]:
formatted_messages = []
for message in messages:
if isinstance(message, Message):
message = message.to_dict()
# 处理消息
if isinstance(message, dict):
# 检查消息是否包含必要字段
if "role" not in message:
raise ValueError("Message dict must contain 'role' field")
# 处理 base64 图像
if supports_images and message.get("base64_image"):
# 初始化或转换内容为适当格式
if not message.get("content"):
message["content"] = []
elif isinstance(message["content"], str):
message["content"] = [
{"type": "text", "text": message["content"]}
]
elif isinstance(message["content"], list):
message["content"] = [
(
{"type": "text", "text": item}
if isinstance(item, str)
else item
)
for item in message["content"]
]
# 添加图像到内容
message["content"].append(
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{message['base64_image']}"
},
}
)
# 删除 base64_image 字段
del message["base64_image"]
elif not supports_images and message.get("base64_image"):
del message["base64_image"]
if "content" in message or "tool_calls" in message:
formatted_messages.append(message)
else:
raise TypeError(f"Unsupported message type: {type(message)}")
# 验证所有消息是否包含必要字段
for msg in formatted_messages:
if msg["role"] not in ROLE_VALUES:
raise ValueError(f"Invalid role: {msg['role']}")
return formatted_messages
2. 工具使用
PlanningFlow类:根据计划步骤选择合适的代理执行任务,调用工具完成相应操作。
class PlanningFlow(BaseFlow):
async def execute(self, input_text: str) -> str:
try:
if not self.primary_agent:
raise ValueError("No primary agent available")
# 创建初始计划
if input_text:
await self._create_initial_plan(input_text)
# 验证计划是否创建成功
if self.active_plan_id not in self.planning_tool.plans:
logger.error(
f"Plan creation failed. Plan ID {self.active_plan_id} not found in planning tool."
)
return f"Failed to create plan for: {input_text}"
result = ""
while True:
# 获取当前要执行的步骤
self.current_step_index, step_info = await self._get_current_step_info()
# 如果没有更多步骤或计划完成,则退出
if self.current_step_index is None:
result += await self._finalize_plan()
break
# 使用合适的代理执行当前步骤
step_type = step_info.get("type") if step_info else None
executor = self.get_executor(step_type)
step_result = await self._execute_step(executor, step_info)
result += step_result + "\n"
# 检查代理是否要终止
if hasattr(executor, "state") and executor.state == AgentState.FINISHED:
break
return result
except Exception as e:
logger.error(f"Error in PlanningFlow: {str(e)}")
return f"Execution failed: {str(e)}"
5.4 小结
综上所述,OpenManus 项目通过内存和文件保存信息,利用消息传递和工具调用传递信息,并在大语言模型调用和任务执行中使用信息,形成了一套完整的信息保存、传递与使用机制。