OpenManus BaseFlow 深度解析:用 Flow 编排多步骤多智能体任务(PlanningFlow 实战)

OpenManus BaseFlow 深度解析:用 Flow 编排多步骤多智能体任务(PlanningFlow 实战)

【免费下载链接】Tutorial-Codebase-Knowledge Pocket Flow: Codebase to Tutorial 【免费下载链接】Tutorial-Codebase-Knowledge 项目地址: https://gitcode.com/gh_mirrors/tu/Tutorial-Codebase-Knowledge

在 OpenManus 中,BaseAgent 解决的是"单个智能体如何思考与行动",Tool / ToolCollection 解决的是"智能体如何获得具体技能"。而本文要讲的 Flow(流程) 则位于更高一层:当一个任务需要分阶段、跨技能、甚至跨多个智能体协同完成时,Flow 就是那个统筹全局的"项目经理"。读完本文,你将掌握 OpenManus 中 BaseFlow 的设计思想、FlowFactory 的创建方式,以及 PlanningFlow 从"制定计划"到"逐条执行"再到"汇总结果"的完整运行链路,能够直接用代码搭建自己的多步骤任务编排流程。

单步任务的局限:为什么需要 Flow?

先看一个单步任务:给一个配备了 WebSearch 工具的智能体提问"法国的首都是哪里?"——它调用工具、拿到答案、返回"巴黎",一次交互即可完成。

但如果是这样一个任务:"调研电动车的优缺点,然后写一篇简短的博客文章总结它们"?它显然不是一次行动,而是一连串相互依赖的步骤:

  1. 规划:拆解步骤(搜索优点 → 搜索缺点 → 搭建文章结构 → 撰写草稿 → 审阅草稿)。
  2. 执行第 1 步:用搜索工具找优点。
  3. 执行第 2 步:用搜索工具找缺点。
  4. 执行第 3 步:调用 LLM 的"大脑"列出博客大纲。
  5. 执行第 4 步:基于调研结果和大纲撰写全文。
  6. 执行第 5 步:最终审阅。

一个非常复杂的 BaseAgent 理论上或许能独自完成这一切,但当流程变长、步骤变多时,由专门的编排器(orchestrator)来统管整个过程会清晰得多、也更容易维护。这正是 Flow 的职责:BaseFlow 是这些编排器的蓝图,它定义了一种能够管理多个智能体、并按照特定策略(例如遵循预先制定的计划)协调它们去完成更大目标的结构。

一个典型的用例:还是"先研究后写作"的任务,我们需要一个东西来管理整体进程。PlanningFlow(基于 BaseFlow 构建的一种具体 Flow)正合适——它先创建计划(如上面列出的步骤),然后逐步执行,必要时还能把不同的步骤分派给不同的专业智能体。

核心概念:Flow、Agents 与执行策略

OpenManus 的 Flow 体系由三个层次的概念组成:

  1. BaseFlow(app/flow/base.py):所有 Flow 的抽象蓝图。可以把它理解为一份"项目经理岗位说明书"——它规定了一名经理需要知道自己的团队(agents)、需要有一个运行项目的方式(execute 方法),但不规定经理具体"怎么管"。它最主要的职责是持有一个 agents 字典,存放可供 Flow 调用的智能体。你通常不会直接使用 BaseFlow,而是使用它的具体实现。

  2. 具体 Flow(如 PlanningFlow,位于 app/flow/planning.py):这些是具体的项目管理策略,它们继承自 BaseFlow。PlanningFlow 的策略是:

    • 接收总目标;
    • 使用 LLM 与一个专门的 PlanningTool 把目标拆解为一串步骤(即"计划");
    • 逐条执行计划中的步骤,通常通过调用某个合适的 BaseAgent 的 run() 方法来完成;
    • 跟踪每个步骤的状态(未开始、进行中、已完成)。
  3. Flow 内的 Agents:被 Flow 管理的"工人"或"专家"。一个 Flow 持有一个或多个 BaseAgent 实例。在 PlanningFlow 中,可能指定一个主智能体(primary agent,常负责协助生成计划),其余(或同一个)智能体则作为计划步骤的"执行者"。Flow 负责决定每一步该交给哪个智能体。

用盖房子来类比最直观:

  • BaseFlow 是"总承包商"这个概念本身;
  • PlanningFlow 是某种具体的总承包商——他总是先绘制详细的建筑图纸(计划),再为每个阶段聘请专家;
  • agents 是各工种专家:水管工、电工、木匠……;
  • 总目标("盖一栋房子")交给 PlanningFlow(承包商);
  • PlanningFlow 制定计划(地基、框架、水电……),然后针对计划的每一步调用合适的 agent(专家)。

实战:用 FlowFactory 创建 PlanningFlow

在 OpenManus 中,创建 Flow 的标准方式是使用 FlowFactory,并把 Flow 所需的智能体交给它。下面的示例创建一个带有一个名为 "Manus"(OpenManus 的通用型智能体)的 PlanningFlow:

# 导入必要的类
from app.agent.manus import Manus  # 一个能力全面的智能体
from app.flow.flow_factory import FlowFactory, FlowType
import asyncio  # 异步执行所需

# 1. 创建我们想让 Flow 管理的智能体
#    可以给智能体在 Flow 内分配特定的键(名称)
agents_for_flow = {
    "research_writer": Manus()  # 用 Manus 智能体处理所有任务
}

# 2. 使用工厂创建 Flow
#    指定类型(PLANNING)并提供智能体
planning_flow_instance = FlowFactory.create_flow(
    flow_type=FlowType.PLANNING,
    agents=agents_for_flow,
    # 可选:指定哪个智能体是主智能体(如果不使用第一个)
    # primary_agent_key="research_writer"
)

print(f"Created a {type(planning_flow_instance).__name__}")
print(f"Primary agent: {planning_flow_instance.primary_agent.name}")

# 3. 定义 Flow 的总体目标
overall_goal = "Research the main benefits of solar power and write a short summary."

# 定义运行 Flow 的异步函数
async def run_the_flow():
    print(f"\nExecuting flow with goal: '{overall_goal}'")
    # 4. 用目标执行 Flow
    final_result = await planning_flow_instance.execute(overall_goal)
    print("\n--- Flow Execution Finished ---")
    print(f"Final Result:\n{final_result}")

# 运行异步函数
# asyncio.run(run_the_flow())  # 取消注释即可运行

逐步拆解这段代码:

  1. 导入要用的智能体(Manus)以及 FlowFactory、FlowType;
  2. 创建字典 agents_for_flow,把键 "research_writer" 映射到一个 Manus 实例——这告诉 Flow 有哪些"工人"可用;
  3. 调用 FlowFactory.create_flow(),指定 FlowType.PLANNING 并传入 agents_for_flow,工厂负责正确构造 PlanningFlow 对象;
  4. 定义高层任务 overall_goal;
  5. await planning_flow_instance.execute(overall_goal)——这是魔法发生的地方,PlanningFlow 接管后续一切。

预期输出(高层视角):运行后你不会立刻得到答案,而是会看到类似这样的过程:

  • 正在创建计划(例如:Step 1: 搜索优点,Step 2: 综合发现,Step 3: 撰写摘要);
  • "research_writer" 智能体开始执行 Step 1,可能伴随它调用网络搜索工具的输出;
  • 智能体继续 Step 2、Step 3,期间可能展示 LLM 的思考或写作输出;
  • 最终,execute 调用返回一个字符串,其中包含各步骤的结果以及(可能的)最终总结。

整个多步骤过程由 PlanningFlow 基于初始目标自动管理——这正是它区别于单步 Agent 的核心价值。

关于 FlowFactory 与 FlowType

FlowFactory 是一个静态工厂:它接收 FlowType 枚举值与 agents 字典,通过内部映射找到对应的 Flow 类并实例化。从教程文档所描述的 OpenManus 教程 可以看出,FlowType 目前至少包含 PLANNING = "planning" 一项,且该映射是可扩展的——新增 Flow 类型时只需在 flows 映射表中登记新类即可。如果传入未注册的 flow_type,工厂会抛出 ValueError(f"Unknown flow type: {flow_type}")。

源码走查:PlanningFlow.execute 的执行链路

调用 execute(input_text) 后到底发生了什么?以下是高层完整走查:

  1. 接收目标:execute 方法拿到 input_text(即总体目标)。
  2. 创建计划(_create_initial_plan):
    • 为 LLM 构造消息,包括要求它扮演"规划师"角色的系统消息;
    • 告诉 LLM 存在 PlanningTool(一个专门用于创建和管理计划的 Tool);
    • 调用 LLM 的 ask_tool 方法,实质上是在问:"请使用 PlanningTool 为这个目标创建计划:{input_text}";
    • PlanningTool(在被 LLM 调用时)把生成的步骤(如 ["Search benefits", "Write summary"])与一个唯一的 plan_id 关联并存储起来。
  3. 执行循环:Flow 进入循环逐条执行计划步骤:
    • 取下一步(_get_current_step_info):用 PlanningTool 检查已存计划,找到第一个未标记为"completed"的步骤,取得其文本与索引;
    • 检查是否完成:若找不到未完成步骤,说明计划已执行完毕,跳出循环;
    • 选择执行者(get_executor):决定当前步骤由哪个智能体执行。在简单示例里它总是选择 "research_writer";更复杂的 Flow 可以根据步骤类型选择(例如 "[CODE]" 步骤交给编码智能体);
    • 执行步骤(_execute_step):为选中的执行者智能体准备提示词(包含当前计划状态与当前步骤的具体指令,如 "You are working on step 0: 'Search benefits'. Please execute this step."),然后调用 await executor.run(step_prompt)——智能体随之展开工作(可能使用它自己的工具、记忆与 LLM);拿到 run() 返回的结果;
    • 标记完成(_mark_step_completed):让 PlanningTool 把当前步骤状态更新为 "completed";
    • 循环:回到"取下一步",继续下一轮。
  4. 收尾(_finalize_plan):循环结束后,可能生成已完成计划的最终总结(可能再次借助 LLM)。
  5. 返回结果:把所有步骤累计得到的结果作为字符串返回。

时序图

mermaid

核心代码片段解析

以下代码片段是教程文档基于 OpenManus 源码给出的简化版本,用于说明三个文件各自承担的角色。

app/flow/base.py:蓝图的骨架,只持有 agents

# 简化片段(源自 app/flow/base.py)
from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Union
from pydantic import BaseModel
from app.agent.base import BaseAgent

class BaseFlow(BaseModel, ABC):
    """Base class for execution flows supporting multiple agents"""
    agents: Dict[str, BaseAgent]  # 持有所有可用智能体
    primary_agent_key: Optional[str] = None  # 主智能体的键

    # ... __init__ 负责构建 agents 字典 ...

    @property
    def primary_agent(self) -> Optional[BaseAgent]:
        """获取 Flow 的主智能体"""
        return self.agents.get(self.primary_agent_key)

    @abstractmethod  # 子类必须实现 execute
    async def execute(self, input_text: str) -> str:
        """以给定输入执行 Flow"""
        pass

关键点:BaseFlow 同时继承 Pydantic 的 BaseModel 与 Python 的 ABC,因此字段具备数据校验能力,而 execute 被声明为抽象方法——子类必须给出自己的编排逻辑。primary_agent 是一个 property,按 primary_agent_key 从 agents 字典中取出主智能体;若未指定 primary_agent_key,通常会默认取字典的第一个键(这也是上面示例代码中注释"if not first"的原因)。

app/flow/flow_factory.py:工厂,创建具体 Flow

# 简化片段(源自 app/flow/flow_factory.py)
from enum import Enum
from app.agent.base import BaseAgent
from app.flow.base import BaseFlow
from app.flow.planning import PlanningFlow  # 导入具体 Flow

class FlowType(str, Enum):
    PLANNING = "planning"  # 在此扩展其他 Flow 类型

class FlowFactory:
    @staticmethod
    def create_flow(flow_type: FlowType, agents, **kwargs) -> BaseFlow:
        flows = {  # 类型枚举到具体类的映射
            FlowType.PLANNING: PlanningFlow,
        }
        flow_class = flows.get(flow_type)
        if not flow_class:
            raise ValueError(f"Unknown flow type: {flow_type}")
        # 创建 PlanningFlow(agents, **kwargs) 实例
        return flow_class(agents, **kwargs)

关键点:FlowType 是继承自 str 的枚举,因此 FlowType.PLANNING 本身就可以直接当作字符串 "planning" 使用。工厂的核心是"枚举 → 类"的映射字典,kwargs 会把 primary_agent_key 等额外配置透传给具体 Flow 的构造函数。

app/flow/planning.py:计划与执行的核心逻辑

# 简化片段(源自 app/flow/planning.py)
from app.flow.base import BaseFlow
from app.tool import PlanningTool
from app.agent.base import BaseAgent
from app.schema import Message

class PlanningFlow(BaseFlow):
    planning_tool: PlanningTool = Field(default_factory=PlanningTool)
    # ... 其他字段,如 llm、active_plan_id ...

    async def execute(self, input_text: str) -> str:
        """使用智能体执行计划流程。"""
        # 1. 提供输入时创建计划
        if input_text:
            await self._create_initial_plan(input_text)

        result_accumulator = ""
        while True:
            # 2. 获取要执行的下一步
            step_index, step_info = await self._get_current_step_info()

            # 3. 没有更多步骤则退出
            if step_index is None:
                result_accumulator += await self._finalize_plan()
                break

            # 4. 获取执行该步骤的智能体
            executor_agent = self.get_executor(step_info.get("type"))

            # 5. 让智能体执行步骤
            step_result = await self._execute_step(executor_agent, step_info)
            result_accumulator += step_result + "\n"

        return result_accumulator

    async def _create_initial_plan(self, request: str):
        """使用 LLM 和 PlanningTool 创建计划。"""
        logger.info(f"Creating plan for: {request}")
        system_msg = Message.system_message("You are a planner...")
        user_msg = Message.user_message(f"Create a plan for: {request}")

        # 请 LLM 使用规划工具
        response = await self.llm.ask_tool(
            messages=[user_msg],
            system_msgs=[system_msg],
            tools=[self.planning_tool.to_param()],  # 提供工具规格
            # tool_choice=ToolChoice.AUTO  # 或指定规划工具名称
        )
        logger.info("Plan created.")

    async def _execute_step(self, executor: BaseAgent, step_info: dict) -> str:
        """让执行者智能体执行单个步骤。"""
        step_text = step_info.get("text", "Current step")
        plan_status = await self._get_plan_text()  # 获取当前计划状态

        # 为智能体构造提示词
        step_prompt = f"Current Plan:\n{plan_status}\n\nYour Task:\nExecute step: {step_text}"

        # 调用智能体的 run 方法!
        step_result = await executor.run(step_prompt)

        # 执行后标记步骤完成
        await self._mark_step_completed()

        return step_result

    async def _mark_step_completed(self):
        """更新当前步骤的规划工具状态。"""
        if self.current_step_index is not None:
            await self.planning_tool.execute(
                command="mark_step",
                plan_id=self.active_plan_id,
                step_index=self.current_step_index,
                step_status="completed"
            )
            logger.info(f"Step {self.current_step_index} marked complete.")

对这段核心逻辑的逐点说明:

  • execute 是主控循环:先创建计划,然后进入 while True 循环——取下一步、判断是否结束、选执行者、执行步骤并累计结果,直到没有剩余步骤时调用 _finalize_plan 收尾。result_accumulator 把所有步骤结果拼成一个字符串返回,这正是我们在实战示例中打印的 final_result。
  • _create_initial_plan 打通了 LLM 与工具:它把"规划师"系统提示、用户目标封装成 Message,再通过 self.llm.ask_tool(..., tools=[self.planning_tool.to_param()]) 让 LLM 以工具调用(而不是纯文本回复)的方式生成计划——to_param() 把 PlanningTool 的描述与参数规格转成 LLM API 需要的格式,这与 ToolCollection 的 to_params() 是同一套机制。
  • _execute_step 是"委托"的关键:它把当前计划全文 + 本步骤指令拼成 step_prompt,然后 await executor.run(step_prompt)。这里的 run() 正是 BaseAgent 提供的标准执行循环——智能体内部会自行经历"检查状态 → 置为 RUNNING → 循环调用 step() → 归位 IDLE"的完整过程。也就是说,Flow 负责"安排做什么",Agent 负责"具体怎么做",两层职责被干净地切分。
  • _mark_step_completed 维护计划状态:通过 planning_tool.execute(command="mark_step", ...) 把当前步骤标记为 completed,配合 _get_current_step_info 读取"下一个未完成步骤",形成"取一步 → 执行 → 标记 → 再取一步"的状态推进闭环。

与前面章节的串联:Flow 如何站在 Agent 与 Tool 之上

把第 1~5 章的知识串起来,就能看到 OpenManus 的分层协作全景:

  1. LLM 的 ask 提供纯文本"思考",ask_tool 则允许 LLM 返回工具调用指令;
  2. Message / Memory 为这些思考与调用提供结构化的对话上下文;
  3. BaseAgent 的 run() 循环把"思考 + 工具调用"组装成一个可重复执行的智能体个体;
  4. Tool / ToolCollection 通过 to_params() 和 execute() 让智能体获得可被发现、可被执行的技能;
  5. 本文的 BaseFlow / PlanningFlow 站在最顶层:_create_initial_plan 用 LLM + PlanningTool 生成计划,执行循环则反复调用执行者智能体的 run()——而这些智能体内部又各自使用 LLM、Memory 与 Tools。

于是,OpenManus 的能力边界从"单个智能体的一次性问答"扩展到了"由计划驱动的、可跨智能体的多步骤项目"。这也解释了 OpenManus 教程首页 架构图中 BaseFlow -- Orchestrates Agents --> BaseAgent 与 BaseFlow -- Uses Tools --> Tool / ToolCollection 两条连线:Flow 既编排智能体,也间接消费工具能力。

总结与下一步

本文完整覆盖了 BaseFlow 的定位与使用方式:

  • BaseFlow 是所有 Flow 的抽象基类,本质是一个持有 agents 字典、声明抽象 execute 方法的编排蓝图;
  • PlanningFlow 是最核心的具体实现,采用"先生成计划、再逐条顺序执行"的策略,通过 PlanningTool 维护计划的创建、读取与状态标记;
  • FlowFactory + FlowType 是创建 Flow 的标准入口,新增流程类型只需扩展枚举与映射表;
  • 执行链路由 _create_initial_plan、_get_current_step_info、get_executor、_execute_step、_mark_step_completed、_finalize_plan 六个环节构成,最终把累计结果以字符串返回。

它让 OpenManus 能够处理比单个智能体独自作战更大的目标。而要让这些 Flow、Agent、Tool 之间顺畅地传递数据(例如工具参数的格式、智能体的状态定义),还需要一套"官方表格"来约束它们——这正是 Chapter 6: Schema 要解决的问题:了解 OpenManus 如何定义与校验这些数据结构。

【免费下载链接】Tutorial-Codebase-Knowledge Pocket Flow: Codebase to Tutorial 【免费下载链接】Tutorial-Codebase-Knowledge 项目地址: https://gitcode.com/gh_mirrors/tu/Tutorial-Codebase-Knowledge

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值