手把手帶你理解OpenManus
我之前演示過幾個OpenManus的demo,其實也就是demo,包括manus,現(xiàn)在也就是demo階段,復雜的plan和flow,現(xiàn)在的代碼支撐和LLM的能力都有待改善,但是我們這期不是吐槽文章,是來把OpenManus給打開看看它的實現(xiàn)是怎么樣的,其實Manus也都差不多,甚至OWL也都差不多,我們看一個就夠了。
其他的幾個目錄也沒啥特別需要看的,就看app。
app里面有這么幾個結(jié)構(gòu):
1- agent 沒啥好解釋的
2- flow 就是來做multiagent的planning和管理任務框架的。
3- prompt
都是這種形式,來定義系統(tǒng)提示詞和agent的role。
4- tool
純純干活的了。
主要看最重要的目錄agent。
大概總體分這么幾個agent。
先看base:base.py 模塊定義了抽象基類 BaseAgent,用于管理代理的狀態(tài)、內(nèi)存、執(zhí)行循環(huán)(包括運行、步驟執(zhí)行、卡住處理)和消息,并提供初始化和配置功能,為構(gòu)建具有特定行為的代理提供基礎框架。
在看planning:planning.py模塊定義了PlanningAgent類,該代理通過PlanningTool和Terminate等工具創(chuàng)建、管理和執(zhí)行任務計劃。它具有初始化、計劃創(chuàng)建(create_initial_plan)、思考(think)、行動(act)、計劃狀態(tài)更新(update_plan_status)、步驟跟蹤(step_execution_tracker)等功能,并能根據(jù)工具執(zhí)行結(jié)果動態(tài)調(diào)整計劃,處理初始請求(run)并檢索當前計劃狀態(tài)(get_plan)。
然后是 react.py:react.py模塊定義了繼承自BaseAgent的抽象類ReActAgent,它通過think(思考,決定下一步行動) 和act(執(zhí)行行動) 兩個抽象方法(需子類實現(xiàn))以及step方法(整合think和act)來處理和執(zhí)行任務,并提供基礎的任務處理框架。
swe和tool就是 指從code和tool了
manus.py:manus.py模塊定義了Manus類,一個繼承自ToolCallAgent(實際上你前面提到了繼承自 PlanningAgent, 請確認是哪個) 的通用智能代理。Manus具有預定義的名稱、描述、系統(tǒng)提示、步驟限制(max_observe,max_steps),并利用包含PythonExecute、WebSearch、BrowserUseTool、FileSaver和Terminate等工具的available_tools集合來執(zhí)行各種任務,并通過_handle_special_tool方法處理(如清理)BrowserUseTool的結(jié)果。
好的,我們來梳理一下這些代碼的調(diào)用邏輯。這里涉及到多個Agent類(BaseAgent,ReActAgent,ToolCallAgent,PlanningAgent,SWEAgent,Manus),以及它們之間的繼承關系和方法調(diào)用。我用文字描述,并結(jié)合偽代碼(因為好解釋)來表示調(diào)用流程。
1. 總體繼承關系:
BaseAgent (抽象基類)
└─ ReActAgent (抽象基類)
└─ ToolCallAgent
├─ PlanningAgent
├─ SWEAgent
└─ Manus
2.BaseAgent(核心基類):
- 核心方法:
__init__: 初始化Agent的基本屬性(name, description, llm, memory, state, max_steps等)。
initialize_agent: 使用model_validator在對象創(chuàng)建后進行進一步初始化(設置默認的LLM和Memory)。
state_context: 一個異步上下文管理器,用于安全地更改Agent的狀態(tài)(IDLE, RUNNING, ERROR等)。
update_memory: 將消息添加到Agent的內(nèi)存中。
run: Agent的主執(zhí)行循環(huán)。它會循環(huán)調(diào)用step()方法,直到達到max_steps或狀態(tài)變?yōu)镕INISHED。
step:抽象方法,必須由子類實現(xiàn)。執(zhí)行Agent的單個步驟。
is_stuck: 檢查智能體是否卡住。
handle_stuck_state: 處理卡住的狀態(tài)。
messages: 屬性,用于獲取和設置消息。
- 調(diào)用流程 (以run方法為例):
async run(request):
if state != IDLE:
raise RuntimeError
if request:
update_memory(USER, request)
async with state_context(RUNNING):
while current_step < max_steps and state != FINISHED:
current_step += 1
step_result = await step() # 調(diào)用子類的 step() 方法
if is_stuck():
handle_stuck_state()
results.append(step_result)
if current_step >= max_steps:
state = IDLE
results.append("Terminated: Reached max steps")
return "\n".join(results)
3.ReActAgent(ReAct模式的基類):
- 繼承自:?BaseAgent
- 核心方法:
think:抽象方法,由子類實現(xiàn)。思考下一步行動。
act:抽象方法,由子類實現(xiàn)。執(zhí)行行動。
step: 實現(xiàn)了BaseAgent的step方法。依次調(diào)用think()和act()。
- 調(diào)用流程 (以step方法為例):
4.ToolCallAgent(支持工具調(diào)用的Agent):
async step():
should_act = await think() # 調(diào)用子類的 think()
if should_act:
return await act() # 調(diào)用子類的 act()
else:
return "Thinking complete - no action needed"
- 繼承自:?ReActAgent
- 核心方法:
__init__: 初始化工具相關的屬性 (available_tools, tool_choices, special_tool_names, tool_calls等)。
think: 根據(jù)當前狀態(tài)和可用工具,決定是否調(diào)用工具。使用LLM進行決策。
act: 執(zhí)行工具調(diào)用,并處理工具的輸出。
execute_tool: 執(zhí)行具體的工具調(diào)用。
_handle_special_tool: 處理特殊工具。
- 調(diào)用流程 (以think和act為例):
async think():
# ... (準備消息和工具信息) ...
response = await llm.ask_tool(...) # 讓LLM決定是否調(diào)用工具,以及調(diào)用哪個工具
if response.tool_calls:
self.tool_calls = response.tool_calls
return True # 需要執(zhí)行工具
else:
return False
async act():
if self.tool_calls:
tool_call = self.tool_calls.pop(0) # 取出第一個工具調(diào)用
result = await execute_tool(tool_call)
await _handle_special_tool(tool_call.function.name,result) # 如果是特殊工具,則處理。
update_memory(TOOL, result, tool_call_id=tool_call.id)
return result
else:
return "No tool to execute"
5.PlanningAgent(規(guī)劃型Agent):
- 繼承自:?ToolCallAgent
- 核心方法:
- initialize_plan_and_verify_tools: 初始化計劃ID并驗證是否有PlanningTool。
- think: 獲取當前計劃狀態(tài),并讓LLM決定下一步行動(包括是否調(diào)用工具)。
- act: 執(zhí)行工具調(diào)用,并更新計劃狀態(tài)。
- get_plan: 獲取當前計劃。
- create_initial_plan: 根據(jù)用戶請求創(chuàng)建初始計劃。
- update_plan_status: 根據(jù)工具執(zhí)行結(jié)果更新計劃步驟的狀態(tài)。
- _get_current_step_index: 獲取當前計劃中第一個未完成步驟的索引。
- 調(diào)用流程 (更詳細,以run->think->act為例):
async run(request):
if request:
await create_initial_plan(request)
return await super().run() # 調(diào)用 ToolCallAgent.run() -> ReActAgent.run() -> BaseAgent.run()
async create_initial_plan(request):
# 1. 構(gòu)造消息,讓 LLM 創(chuàng)建計劃
messages = [...]
response = await llm.ask_tool(..., tool_choice=ToolChoice.AUTO)
# 2. 處理 LLM 的響應,提取工具調(diào)用(應該是 planning 工具的調(diào)用)
for tool_call in response.tool_calls:
if tool_call.function.name == "planning":
result = await execute_tool(tool_call) # 執(zhí)行 planning 工具
# 3. 將工具執(zhí)行結(jié)果(計劃)存入內(nèi)存
update_memory(TOOL, result, tool_call_id=tool_call.id)
async think():
prompt = f"CURRENT PLAN STATUS:\n{await self.get_plan()}\n\n{self.next_step_prompt}"
self.messages.append(Message.user_message(prompt))
self.current_step_index = await self._get_current_step_index()
result = await super().think() # 調(diào)用 ToolCallAgent 的 think()
if result and self.tool_calls:
# 記錄工具和步驟的關聯(lián)
latest_tool_call = self.tool_calls[0]
if latest_tool_call 不是 planning tool 且 不是 special tool:
self.step_execution_tracker[latest_tool_call.id] = {
"step_index": self.current_step_index,
"tool_name": latest_tool_call.function.name,
"status": "pending"
}
return result
async act():
result = await super().act() # 調(diào)用 ToolCallAgent 的 act()
if self.tool_calls:
latest_tool_call = self.tool_calls[0]
if latest_tool_call.id in self.step_execution_tracker:
self.step_execution_tracker[latest_tool_call.id]["status"] = "completed"
self.step_execution_tracker[latest_tool_call.id]["result"] = result
if latest_tool_call 不是 planning tool 且 不是 special tool:
await self.update_plan_status(latest_tool_call.id)
return result
async update_plan_status(tool_call_id):
# 1. 檢查 tool_call_id 是否在 tracker 中,以及狀態(tài)是否為 completed
# 2. 調(diào)用 planning 工具的 mark_step 命令,將對應步驟標記為 completed
async _get_current_step_index():
# 1. 獲取當前計劃 (文本)
# 2. 解析計劃文本,找到第一個 [ ] 或 [→] 的步驟
# 3. 調(diào)用 planning 工具的 mark_step 命令,將當前步驟設置為 in_progress
# 4. 返回步驟索引
6.SWEAgent(軟件工程師Agent):
- 繼承自:?ToolCallAgent
- 核心方法:
think: 更新工作目錄,并使用格式化的next_step_prompt。然后調(diào)用父類的think。
- 調(diào)用流程 (以think為例):
async think():
self.working_dir = await self.bash.execute("pwd") # 獲取當前工作目錄
self.next_step_prompt = self.next_step_prompt.format(current_dir=self.working_dir) # 更新提示
return await super().think() # 調(diào)用 ToolCallAgent 的 think()
7.Manus(通用Agent):
- 繼承自:?ToolCallAgent
- 特點:擁有更廣泛的工具集 (PythonExecute, WebSearch, BrowserUseTool, FileSaver, Terminate)。
- _handle_special_tool: 重寫此方法,處理BrowserUseTool的清理工作。
那么好,我們總結(jié)一下:
- BaseAgent提供了Agent的基本框架和執(zhí)行循環(huán)。
- ReActAgent引入了 "思考-行動" 模式。
- ToolCallAgent增加了工具調(diào)用的能力。
- PlanningAgent、SWEAgent和Manus是ToolCallAgent的具體實現(xiàn),分別針對不同的任務類型。
- 調(diào)用邏輯主要通過繼承和重寫think、act和step方法來實現(xiàn)。
- PlanningAgent具有更復雜的內(nèi)部狀態(tài)管理,用于跟蹤計劃的執(zhí)行。
畫出來就是下圖這樣:
總體調(diào)用的抽象感覺就是下圖這樣:
好了今天這節(jié)課就解釋到這,大家可以結(jié)合我的解釋自己去run一下代碼,甚至自己按著這個邏輯來新寫一套multi-agents的框架也不是特別難的事。
本文轉(zhuǎn)載自??熵減AI??,作者:周博洋
