Flow vs Crew:什么时候用哪个,怎么混用
阶段 7 最后一天,不引入新源码机制,而是把 Crew(阶段 4)和 Flow(阶段 7)放在一起,从两者的源码定位差异出发,给出一张可操作的选型决策表。核心结论早在 D41 埋下:Flow 编排、Crew 干活。今天用 process.py、crew.kickoff 和 Flow 的事件模型对照,说清"为什么 Crew 只有两种固定流程、Flow 却能任意控制流",再落到三个真实场景手把手选型,并混合它们。
痛点:学完两套,反而不知道该用哪个了
从源码看: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:LLM 驱动、把"怎么协作"交给模型
Crew 是一个 Pydantic 模型,核心就是 agents + tasks + process(crew.py:159、crew.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 用的执行单元"。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 编排 + 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 能编排。决策表:三秒选型
| 你的需求里有… | 选 | 理由(对应源码) |
|---|---|---|
| 就是"一队 Agent 把一件事做完",步骤松散 | Crew | sequential 足够,写得少 process.py:9 |
| 需要一个"管理者"动态派活 | Crew(hierarchical) | _run_hierarchical_process crew.py:1025 |
| if/else 分支(通过就发布,否则打回) | Flow | @router 返回事件名 flow/dsl/_router.py:142 |
| 循环重试直到达标 | Flow | @start(条件) 被路由重触发 runtime:2846 |
| 等多路并行汇合再继续 | Flow | and_ + _pending_events runtime:2860 |
| 要能崩溃恢复 / 人在环暂停 | Flow | @persist + load_state runtime:2668 |
| 要多轮对话、记住上下文 | Flow(对话式) | ChatState + 每轮 kickoff conversation.py:51 |
| 某些步骤必须确定性、不许 LLM 乱来 | Flow | 节点=纯 Python,LLM 按需调 |
| 复杂业务:流程要控制、局部要智能 | Flow 套 Crew | Crew 继承 FlowTrackable crew.py:159 |
三个真实场景,手把手选
@start("rewrite") 实现打回,max_method_calls 或 state 里的计数兜住"最多 3 次"。生成和重写这两步内部可以各 kickoff 一个 Crew。👶 小白:我拿不准,先用 Crew 以后不够再换 Flow,行不行?
👨🏫 老师:可以,而且是合理的渐进策略。因为 Crew 天生能被 Flow 嵌入(继承 FlowTrackable),你先写好的 Crew 日后能原样塞进 Flow 的某个节点,几乎不用改。所以"先 Crew、需要控制流时再用 Flow 把它包起来"迁移成本很低。反过来把 Flow 拆成 Crew 就难了——所以别一开始就为可能用不上的控制流强行上 Flow。
阶段 7 收官 + 自测
@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())
"
llm.py 的 LLM 抽象——CrewAI 怎么用 litellm 统一几十种 provider、一次 call() 内部怎么走。