作者:唯氪
原文地址:https://zhuanlan.zhihu.com/p/1886362220297967012
OpenManus 是一个基于 LLM 的任务规划和执行框架,其核心功能之一是通过 LLM 智能选择并调用各种工具来完成复杂任务。本文详细解析 OpenManus 中 LLM 工具调用的完整机制。
一、工具定义与结构
1.1 工具基类
所有工具都继承自 BaseTool 基类,该类定义了工具的基本接口:
class BaseTool(ABC, BaseModel):
name: str
description: str
parameters: Optional[dict] = None
async def __call__(self, **kwargs) -> Any:
"""Execute the tool with given parameters."""
return await self.execute(**kwargs)
@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,
},
}
1.2 工具参数定义
每个工具都通过 parameters 字典定义其接受的参数,遵循 JSON Schema 格式:
parameters: dict = {
"type": "object",
"properties": {
"command": {
"description": "The command to execute...",
"enum": ["create", "update", "list", "get", "set_active", "mark_step", "delete"],
"type": "string",
},
# 其他参数...
},
"required": ["command"],
"additionalProperties": False,
}
1.3 OpenManus 中的核心工具
OpenManus 定义了多种工具,包括:
-
PlanningTool:任务规划工具,用于创建和管理计划
-
BrowserUseTool:浏览器自动化工具,用于网页交互
-
WebSearch:网络搜索工具,支持多种搜索引擎
-
Bash/Terminal:命令行执行工具
-
FileSaver:文件保存工具
-
PythonExecute:Python 代码执行工具
-
等等
二、工具调用流程
2.1 工具定义传递给 LLM
系统将工具定义传递给 LLM,这通常在 ask_tool 方法中完成:
response = await self.llm.ask_tool(
messages=messages,
system_msgs=[Message.system_message(self.system_prompt)],
tools=self.available_tools.to_params(), # 将所有可用工具传递给 LLM
tool_choice=ToolChoice.AUTO, # 让 LLM 自动选择合适的工具
)
to_params() 方法将工具转换为 OpenAI API 可接受的格式:
def to_params(self) -> List[Dict[str, Any]]:
"""Convert all tools to function call format."""
return [tool.to_param() for tool in self.tools]
2.2 LLM 分析请求并选择工具
LLM 收到用户请求和工具定义后,执行以下步骤:
-
理解用户意图:分析用户请求,理解任务需求
-
匹配合适工具:从提供的工具列表中选择最合适的工具
-
构造参数:为选定的工具构造必要的参数
-
生成工具调用:生成包含工具名称和参数的工具调用
2.3 工具调用返回格式
LLM 生成的工具调用会在 response.tool_calls 中返回,格式如下:
[
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "planning",
"arguments": "{\"command\":\"create\",\"plan_id\":\"plan_1234\",\"title\":\"比特币价值分析\",\"steps\":[\"收集市场数据\",\"分析历史趋势\",\"评估当前价值\"]}"
}
}
]
2.4 解析和执行工具调用
系统解析 LLM 返回的工具调用,并执行相应操作:
if response.tool_calls:
for tool_call in response.tool_calls:
if tool_call.function.name == "planning":
# 解析参数
args = json.loads(tool_call.function.arguments)
# 执行工具
await self.planning_tool.execute(**args)
在 PlanningAgent 中,系统使用 Message.from_tool_calls 将工具调用转换为消息格式:
assistant_msg = Message.from_tool_calls(
content=response.content,
tool_calls=response.tool_calls
)
self.memory.add_message(assistant_msg)
三、工具执行机制
3.1 工具执行流程
工具执行通常包括以下步骤:
-
获取工具实例:根据工具名称获取对应的工具实例
-
解析参数:将 JSON 格式的参数解析为 Python 对象
-
执行工具:调用工具的 execute 方法,传入解析后的参数
-
处理结果:获取工具执行结果,并根据需要更新系统状态
3.2 执行结果处理
工具执行结果通常包装在 ToolResult 对象中:
class ToolResult(BaseModel):
"""Represents the result of a tool execution."""
output: Any = Field(default=None)
error: Optional[str] = Field(default=None)
base64_image: Optional[str] = Field(default=None)
system: Optional[str] = Field(default=None)
系统会根据执行结果更新状态,并可能将结果反馈给 LLM 进行下一步决策。
四、具体示例:创建计划
以创建比特币价值分析报告为例,完整流程如下:
4.1 用户请求
用户输入:"请做一份btc价值分析报告"
4.2 系统处理
# 1. 创建消息
messages = [Message.user_message("请做一份btc价值分析报告")]
# 2. 调用 LLM
response = await self.llm.ask(
messages=messages,
system_msgs=[Message.system_message(self.system_prompt)],
tools=self.available_tools.to_params(),
tool_choice=ToolChoice.AUTO,
)
4.3 LLM 响应
LLM 分析请求,选择 planning 工具,并生成工具调用:
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "planning",
"arguments": "{\"command\":\"create\",\"plan_id\":\"plan_1234\",\"title\":\"比特币价值分析报告\",\"steps\":[\"收集当前市场数据\",\"分析历史价格趋势\",\"评估基本面因素\",\"考虑宏观经济影响\",\"总结价值评估\"]}"
}
}
4.4 系统解析
系统解析工具调用,获取参数:
args = {
"command": "create",
"plan_id": "plan_1234",
"title": "比特币价值分析报告",
"steps": ["收集当前市场数据", "分析历史价格趋势", "评估基本面因素", "考虑宏观经济影响", "总结价值评估"]
}
4.5 执行工具
系统调用 planning_tool.execute 方法,创建计划:
result = await planning_tool.execute(**args)
4.6 更新状态
系统更新计划状态,准备执行计划中的步骤。
五、多工具调用处理
LLM 可能会在一次响应中生成多个工具调用,系统会依次处理每个调用:
if response.tool_calls:
for tool_call in response.tool_calls:
tool_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
# 获取工具实例
tool = self.available_tools.get_tool(tool_name)
if tool:
# 执行工具
result = await tool.execute(**args)
六、工具调用的优势
基于工具调用的设计有以下优势:
-
结构化输出:LLM 生成的是结构化的工具调用,而不是自由文本,减少了解析错误
-
参数验证:工具定义中包含参数验证规则,确保 LLM 提供有效参数
-
功能扩展:可以轻松添加新工具,扩展系统功能
-
错误处理:工具执行失败时,可以返回详细的错误信息,帮助 LLM 调整策略
七、总结
OpenManus 通过精心设计的工具调用机制,实现了 LLM 与各种工具的无缝集成,使 LLM 能够执行复杂的任务规划和执行流程。这种设计不仅提高了系统的灵活性和可扩展性,还充分利用了 LLM 的智能决策能力,为自动化任务执行提供了强大的支持。