OpenManus LLM 工具调用机制详解

作者:唯氪

原文地址: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 收到用户请求和工具定义后,执行以下步骤:

  1. 理解用户意图:分析用户请求,理解任务需求

  2. 匹配合适工具:从提供的工具列表中选择最合适的工具

  3. 构造参数:为选定的工具构造必要的参数

  4. 生成工具调用:生成包含工具名称和参数的工具调用

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 工具执行流程

工具执行通常包括以下步骤:

  1. 获取工具实例:根据工具名称获取对应的工具实例

  2. 解析参数:将 JSON 格式的参数解析为 Python 对象

  3. 执行工具:调用工具的 execute 方法,传入解析后的参数

  4. 处理结果:获取工具执行结果,并根据需要更新系统状态

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)

六、工具调用的优势

基于工具调用的设计有以下优势:

  1. 结构化输出:LLM 生成的是结构化的工具调用,而不是自由文本,减少了解析错误

  2. 参数验证:工具定义中包含参数验证规则,确保 LLM 提供有效参数

  3. 功能扩展:可以轻松添加新工具,扩展系统功能

  4. 错误处理:工具执行失败时,可以返回详细的错误信息,帮助 LLM 调整策略

七、总结

OpenManus 通过精心设计的工具调用机制,实现了 LLM 与各种工具的无缝集成,使 LLM 能够执行复杂的任务规划和执行流程。这种设计不仅提高了系统的灵活性和可扩展性,还充分利用了 LLM 的智能决策能力,为自动化任务执行提供了强大的支持。

### 如何在代码中实现LLM函数调用 LLM函数调用的核心在于使模型能够理解并执行特定的功能,这些功能通常由外部服务或工具提供。以下是关于如何实现这一过程的具体方法。 #### 使用LangChain框架进行异步调用 为了简化复杂的LLM应用程序开发流程,`LangChain`框架提供了高级抽象支持,使得开发者可以通过简单的接口轻松管理多个LLM调用以及与其他工具的集成[^3]。下面是一个具体的Python代码示例: ```python from langchain.llms import OpenAI from langchain.agents import initialize_agent, Tool from langchain.tools import BaseTool class DataExtractor(BaseTool): name = "data_extractor" description = "Useful for extracting data from structured inputs" def _run(self, query: str): # 假设这里有一个数据提取逻辑 return f"Extracted {query}" llm = OpenAI(temperature=0) tools = [DataExtractor()] agent = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True) response = agent.run("Please extract the user's information.") print(response) ``` 这段代码展示了如何创建自定义工具类 `DataExtractor` 并将其与 LangChain 集成在一起,从而实现了对结构化输入的数据提取操作。 #### 定义和调用具体函数 除了利用现有的库之外,还可以手动定义适合业务需求的函数,并通过适当的方式传递给LLM模型以便于其识别和调用。例如,在某些情况下可能需要定义像 `extract_data(name: string, birthday: string)` 这样的简单函数[^4]。以下是如何在一个假设场景下实现此类函数的一个例子: ```python def extract_data(name: str, birthday: str) -> dict: """A function to simulate extraction of personal details.""" return {"name": name, "birthday": birthday} # Assuming we have an LLM instance that supports function calls. functions = [ { "name": "extract_data", "description": "Extracts and returns a dictionary containing 'name' and 'birthday'.", "parameters": { "type": "object", "properties": { "name": {"type": "string"}, "birthday": {"type": "string"} }, "required": ["name", "birthday"] } } ] prompt = """ You are now able to call functions based on your understanding of the input text. Input Text: The person is named John Doe born on January 1st, 2000. Action: Call the appropriate function with parameters derived from the Input Text. """ function_call_result = some_llm_instance(prompt=prompt, functions=functions).execute() if isinstance(function_call_result, dict): print(f"The extracted data is as follows:\n{function_call_result}") else: raise ValueError("Unexpected result type received!") ``` 此部分演示了如何构建一个JSON Schema来描述参数期望值,并将之嵌入到提示语境当中去引导LLMs正确地解析信息并作出相应的动作响应。 #### Prompt格式优化技巧 最后值得注意的是,良好的Prompt设计对于提升LLM函数调用的成功率至关重要。研究表明精心调整后的Prompt样式可以帮助提高系统的整体表现效果[^5]。因此,在实际应用过程中应当重视这方面的工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值