Day 48 / 共 60 天 · 阶段7 Flow 事件驱动(收官)

Flow vs Crew:什么时候用哪个,怎么混用

阶段 7 最后一天,不引入新源码机制,而是把 Crew(阶段 4)Flow(阶段 7)放在一起,从两者的源码定位差异出发,给出一张可操作的选型决策表。核心结论早在 D41 埋下:Flow 编排、Crew 干活。今天用 process.pycrew.kickoff 和 Flow 的事件模型对照,说清"为什么 Crew 只有两种固定流程、Flow 却能任意控制流",再落到三个真实场景手把手选型,并混合它们。

📍 你在 60 天里的位置(阶段7 Flow 事件驱动 · 共 8 天 · 收官)
D41 Flow 总览 D42 装饰器 D43 定义契约 D44 路由跳转 D45 状态持久化 D46 表达式 D47 对话式 D48 选型取舍 阶段8 LLM 与集成
💡 先用一个类比兜住今天 Crew 是"外包一支专家团队":你把目标交给他们,他们内部怎么协作、谁先谁后,很大程度由 LLM 自己商量着来,你控制不了每一步细节,但省心。Flow 是"你自己当项目总监画甘特图":每一步什么条件触发、走哪条分支、循环几次,你用代码写死,精确可控,但要自己操心流程。真实的大项目往往是"总监(Flow)画好流程图,在某些节点把活外包给专家团队(Crew)"。选型的本质是:这段逻辑,你要"可控"还是要"省心"?
L01

痛点:学完两套,反而不知道该用哪个了

🤔 痛点Crew 能跑多 Agent 协作,Flow 能做事件流程——功能上好像有重叠。新项目一上来到底建 Crew 还是建 Flow?有了 Flow 是不是就不用 Crew 了?两个混着用会不会打架?选错了要重构,代价不小。
💡 一句话本质 它们不在同一层,不是竞品:Crew 是"被编排的执行单元"(一支 Agent 团队完成一个目标),Flow 是"编排者"(决定整个流程怎么走)。判断标准就一句:你的逻辑需要"分支/循环/精确控制/确定性"吗?需要 → Flow;只是"让一队 Agent 把一件事做完" → Crew。需要智能协作的那一步,就在 Flow 里 kickoff 一个 Crew。绝大多数复杂项目是"Flow 套 Crew"。
L02

从源码看:Crew 的流程是"枚举写死"的

Crew 支持的流程,源码里就是一个只有两个值的枚举(process.py:4):

# process.py:4
class Process(str, Enum):
    sequential = "sequential"
    hierarchical = "hierarchical"
    # TODO: consensual = 'consensual'      ← 第三种还只是 TODO

Crew.kickoff 里,"怎么跑"就是对这个枚举的 if/elif(crew.py:1023):

# crew.py:1023
if self.process == Process.sequential:
    result = self._run_sequential_process()      # 任务列表顺序跑
elif self.process == Process.hierarchical:
    result = self._run_hierarchical_process()     # 加一个 manager agent 派活
else:
    raise NotImplementedError(f"The process '{self.process}' is not implemented yet.")
只有两种 Process★Crew 的"控制流"就是枚举里这两个值。你没法在 Crew 层表达"if 通过 else 打回"——枚举里没有这个选项。
if/elif 写死kickoff 里对 process 的分发是框架写死的,跑 sequential 还是 hierarchical,之后的路径就固定了。
NotImplementedError连 consensual 都还是 TODO——说明 Crew 的设计初衷就不是做通用控制流,而是"少数几种固定协作模式"。

对照 Flow:控制流是你用 @listen/@router 自由画的图,引擎按事件驱动(D44 runtime/__init__.py:2727),没有"只能两种"的限制。

💡 一句话看穿差异Crew 的流程是"从菜单里选一个"(sequential / hierarchical),Flow 的流程是"自己下厨随便做"。菜单省事但不自由,下厨自由但费心。这就是源码层面 Crew 与 Flow 最根本的分野。
L03

Crew:LLM 驱动、把"怎么协作"交给模型

Crew 是一个 Pydantic 模型,核心就是 agents + tasks + process(crew.py:159crew.py:225):

# crew.py:159
class Crew(FlowTrackable, BaseModel):
    ...
    # crew.py:225
    process: Process = Field(default=Process.sequential)   # 默认顺序
    # agents: list[BaseAgent] / tasks: list[Task] 等字段(阶段4 讲过)
默认 sequential不指定就是"任务列表从上到下顺次跑"——最简单的协作。
hierarchical 靠 manager LLM层级模式里,一个 manager agent(本身是 LLM)动态决定"下一个派给谁"——协作顺序由 LLM 现场决策,不是你写死的。
FlowTrackable★Crew 继承 FlowTrackable——它天生就"可被 Flow 追踪/嵌入"。这从类型层面印证了"Crew 是给 Flow 用的执行单元"。
💡 Crew 的取舍:省心 vs 可控 Crew 把"任务之间怎么衔接、hierarchical 下谁先干"很大程度交给 LLM。好处是你写得少、模型灵活应变——需求模糊、步骤松散时特别爽。坏处是不确定、不好调试:同样的输入,LLM 可能这次这么协作、下次那么协作。当"每一步都必须精确、可复现、可审计"时,Crew 的这种灵活反而成了负担——这正是该切到 Flow 的信号。
L04

Flow:代码驱动、你精确掌控每一步

回顾 Flow 的执行本质(D41/D44)——一切由你写的图 + 你的 Python 决定:

# 你在 Flow 里拥有的控制力(综合前七天)
class ResearchFlow(Flow[MyState]):
    @start()
    def research(self): ...                    # 起点,可能 kickoff 一个 Crew
    @router(research)
    def gate(self) -> Literal["ok","redo"]:    # ★你的 Python 决定走哪条(D44)
        return "ok" if self.state.score >= 80 else "redo"
    @listen("ok")
    def publish(self): ...
    @start("redo")                              # ★循环:打回重来(D44)
    def _retry(self): ...
@router 里是你的代码★走哪条分支由 if self.state.score >= 80 这种确定性 Python决定,不是 LLM 猜。同样输入,永远同样路径。
循环/汇合任你画D44 的循环、and_ 汇合,都是 Crew 表达不了的控制流。
state 显式可持久化D45 让 Flow 能崩溃恢复、人在环暂停;Crew 没有这层跨进程的状态机。
LLM 按需介入★Flow 方法里可以完全不碰 LLM(纯数据处理),也可以 kickoff 一个 Crew 让 LLM 上场。智能是"你在需要时调用的",不是默认弥漫的。
数据结构:谁在"层级"的哪一层 Flow(编排层) 分支 / 循环 / 汇合 / 持久化 / 你的 Python 控制流 节点=纯 Python 节点=kickoff 一个 Crew 节点=调单个 Agent/LLM Crew:sequential/hierarchical
图注:Flow 在上层编排,它的节点可以是纯 Python、也可以下钻去 kickoff 一个 Crew 或调一个 Agent。Crew 永远在被调用的下层。
L05

混合:Flow 编排 + Crew 干活(最常见的生产结构)

Flow 方法里 kickoff 一个 Crew,就是把两者拼起来——Crew 继承 FlowTrackable 就是为此(crew.py:159):

# 典型混合结构
class ContentPipeline(Flow[PipelineState]):
    @start()
    def plan(self):
        return PlanningCrew().kickoff(inputs={"topic": self.state.topic}).raw  # ← Flow 节点里跑 Crew

    @router(plan)
    def check(self) -> Literal["write", "replan"]:
        return "write" if self.state.plan_ok else "replan"      # ← 确定性分支

    @listen("write")
    def write(self):
        draft = WritingCrew().kickoff(inputs={"plan": self.state.plan})  # ← 另一个 Crew
        self.state.draft = draft.raw

    @start("replan")
    def _replan(self): ...                                       # ← 循环回去重新规划
plan 节点 kickoff PlanningCrew需要"多 Agent 智能规划"的一步 → 交给 Crew。Flow 只管"什么时候调它、结果存哪"。
check 是纯 Python router"计划过不过关"是确定性判断 → Flow 的 router 精确控制,不浪费一次 LLM 调用。
write 节点又一个 Crew不同阶段用不同 Crew,各司其职。Flow 把它们按业务流程串起来。
replan 循环Crew 给不出好计划就打回——这种"重试直到达标"只有 Flow 能编排。
大白话Flow 是"总导演",负责剧本流程(先规划、审一下、不行重来、再写作);每一场需要"演员现场发挥"的戏,就叫一个 Crew(一支 Agent 团队)上台演。导演不亲自演,演员不管剧本走向。
L06

决策表:三秒选型

你的需求里有…理由(对应源码)
就是"一队 Agent 把一件事做完",步骤松散Crewsequential 足够,写得少 process.py:9
需要一个"管理者"动态派活Crew(hierarchical)_run_hierarchical_process crew.py:1025
if/else 分支(通过就发布,否则打回)Flow@router 返回事件名 flow/dsl/_router.py:142
循环重试直到达标Flow@start(条件) 被路由重触发 runtime:2846
等多路并行汇合再继续Flowand_ + _pending_events runtime:2860
要能崩溃恢复 / 人在环暂停Flow@persist + load_state runtime:2668
要多轮对话、记住上下文Flow(对话式)ChatState + 每轮 kickoff conversation.py:51
某些步骤必须确定性、不许 LLM 乱来Flow节点=纯 Python,LLM 按需调
复杂业务:流程要控制、局部要智能Flow 套 CrewCrew 继承 FlowTrackable crew.py:159
控制流:三步选型决策 ① 需要 分支/循环/汇合/恢复/多轮? 否 → 只是让团队做完 是 → 需要控制流 用 Crew 用 Flow 局部要智能→套 Crew
图注:先问"要不要控制流",不要→Crew,要→Flow;Flow 里需要智能协作的节点再下钻套 Crew。
💡 一句话记忆看到 "如果 / 否则 / 循环 / 等到 / 恢复 / 多轮" 这些词 → Flow;只是 "让他们把 X 做出来"Crew;两者都有 → Flow 编排、Crew 干活
L07

三个真实场景,手把手选

📝 场景一:写一篇调研报告(研究员+写手+审校) 步骤清晰但松散、不需要分支/循环——用 Crew(sequential)。三个 Agent 三个 Task 顺次跑完即可。硬上 Flow 是过度设计,白写一堆装饰器。
📝 场景二:内容审核流水线(生成→审核→通过发布/不通过打回重写,最多重写 3 次)分支 + 循环 + 上限——用 Flow。router 判审核结果,@start("rewrite") 实现打回,max_method_calls 或 state 里的计数兜住"最多 3 次"。生成和重写这两步内部可以各 kickoff 一个 Crew。
📝 场景三:客服机器人(多轮对话,按意图分流到"查询/退货/转人工") 多轮 + 意图路由 + 可能人在环(转人工)——用对话式 Flow(D47)。ChatState 存历史,意图分类接 router 分流,转人工用 @persist + 人在环暂停等客服接入。查询这类子任务可交给 Crew/Agent。

👶 小白:我拿不准,先用 Crew 以后不够再换 Flow,行不行?

👨‍🏫 老师:可以,而且是合理的渐进策略。因为 Crew 天生能被 Flow 嵌入(继承 FlowTrackable),你先写好的 Crew 日后能原样塞进 Flow 的某个节点,几乎不用改。所以"先 Crew、需要控制流时再用 Flow 把它包起来"迁移成本很低。反过来把 Flow 拆成 Crew 就难了——所以别一开始就为可能用不上的控制流强行上 Flow。

L08

阶段 7 收官 + 自测

⚠️ 反模式:用 Flow 的 router 去做"LLM 才擅长的判断",或用 Crew 硬凑控制流 两个方向都别走偏:① 别在 Flow 的 @router 里写"靠规则模拟语义理解"——判断"用户这句是不是投诉"这种模糊语义,该用 D47 的意图分类(调 LLM)再把结果给 router,而不是在 router 里堆一堆 if "投诉" in text 的关键词匹配,那样又脆又不准。② 别用 Crew 硬凑分支——比如想靠"给 Agent 写一堆 if 提示词"让它自己决定跳过某任务,这既不可靠也不可复现,那正是 Flow router 存在的意义。让确定性的归 Flow、语义的归 LLM、协作的归 Crew,各用其长。

🧠 阶段 7(D41-48)你应该能回答

  • Flow 的三层(dsl/definition/runtime)各管什么?契约为什么可序列化?(D41/D43)
  • @start/@listen/@router 分别往方法上挂了什么?描述符 __get__ 干嘛?(D42)
  • 一次 kickoff 怎么"事件驱动"扩散?分支/循环/汇合各怎么实现?(D41/D44)
  • @persist 在什么时机落盘?怎么用 id 恢复?(D45)
  • CEL 表达式相对 eval 的安全边界在哪?(D46)
  • 对话式 = 哪三块旧积木的组合?(D47)
  • 从源码看 Crew 与 Flow 最根本的差异是什么?什么时候混用?(D48)

✋ 10 分钟动手:把 Crew 塞进 Flow

P=lib/crewai/src/crewai
sed -n '4,11p'      $P/process.py         # Crew 只有两种 Process
sed -n '1023,1030p' $P/crew.py            # kickoff 对 process 的 if/elif
grep -n "FlowTrackable" $P/crew.py        # Crew 继承 FlowTrackable,可被 Flow 嵌入
# 混合:Flow 里 kickoff 一个 Crew
python -c "
from typing import Literal
from crewai import Agent, Task, Crew
from crewai.flow.flow import Flow, start, router, listen
def make_crew():
    a=Agent(role='算术员', goal='算数', backstory='会算')
    t=Task(description='算 6*7', expected_output='数字', agent=a)
    return Crew(agents=[a], tasks=[t])
class Pipe(Flow):
    @start()
    def compute(self): return make_crew().kickoff().raw   # Flow 节点跑 Crew
    @router(compute)
    def gate(self) -> Literal['done']: return 'done'
    @listen('done')
    def report(self): return '完成'
print(Pipe().kickoff())
"
明日预告 · Day 49(阶段8 LLM 与集成):Flow/Crew/Agent 最终都要落到"调一次大模型"。阶段 8 转向 LLM 层:明天读 llm.py 的 LLM 抽象——CrewAI 怎么用 litellm 统一几十种 provider、一次 call() 内部怎么走。
← Day 47 对话式 Day 49 · LLM 抽象 →