ByteFisher AI 工程化深度实战(八):Agent 框架源码剖析——CrewAI

第 07 篇拆了 LangGraph,理解了一个”图状态机”框架。本篇拆它的反面——CrewAI:一个”角色协作”框架。两者都做 Agent 编排,但哲学完全不同。本篇目标是看懂 CrewAI 的四大核心类(Agent / Tool / Task / Crew)的源码结构与协作机制,并用第 01 篇承诺过的”钓鱼日记生成”场景,把 tools/douyin-blog/generate-blog.py 的串行流程改写为 Crew 协作。

一、CrewAI 的核心抽象

1.1 四个类,四个角色

CrewAI 把所有 AI 工作流建模成”一个团队在干活”:

1
2
3
4
5
6
7
8
Crew(团队)
├── Agent(成员:有角色、目标、个性、工具)
├── Agent(成员:...)
└── Process(协作模式:顺序 / 层级)
└── 每个 Agent 执行各自的 Task

Task(任务:描述 + 期望输出 + 归因到某个 Agent)
Tool(工具:Agent 能调用的函数)
  • Agent:不是通用的”执行者”,而是有”人格”的参与者——role(岗位)、goal(目标)、backstory(人设背景)、tools(工具)、llm(可每个角色配不同模型)。
  • Task:description(要做什么)+ expected_output(期望的产出格式)+ agent(谁来做)。任务之间可以传递上下文(context)。
  • Crew:编排器,tasks 列表 + process(sequential 顺序 / hierarchical 层级)。kickoff() 启动整个流程。
  • Process:sequential 是”任务 A → B → C”;hierarchical 引入一个 Manager Agent 动态分派任务。

1.2 与 LangGraph 的哲学对比

维度 LangGraph CrewAI
心智模型 图 / 状态机 / 显式控制流 团队 / 角色 / 任务分工
控制流 你在代码里画边,完全显式 sequential 自动串,hierarchical 交给 Manager
状态管理 一等公民(State+reducer+检查点) 隐式,靠任务上下文传递
学习成本 高(Pregel/检查点/流式) 低(四个类即全貌)
复杂分支 强(条件边、回退、并行) 弱(hierarchical 依赖 LLM 判断)
可恢复性 强(检查点持久化) 弱(无内建检查点)
适合场景 复杂状态流、需断点续跑 多角色分工明确、流程相对线性

一句话总结:LangGraph 强在”流程的确定性控制”,CrewAI 强在”角色的声明式分工”。你的流程是”图”就用 LangGraph,你的流程是”流水线分工”就用 CrewAI。

二、源码结构拆解

2.1 Agent 的本质

CrewAI 的 Agent 并不神秘——它内部就是把”role/goal/backstory + tools 描述 + 任务描述”拼成一个 system prompt,然后调 LLM:

1
2
3
4
5
6
7
8
9
10
# crewai.agent.Agent.agent_executor 的关键逻辑(伪码还原)
system_prompt = f"""
你是 {self.role}。
目标:{self.goal}
背景:{self.backstory}
你有以下工具可用:{self.tools_schema()}
当前任务是:{task.description}
期望输出:{task.expected_output}
"""
result = llm.chat(system_prompt + task_context)

关键工程点:tools 的描述(JSON Schema)会注入 prompt,模型根据描述决定”调哪个工具、传什么参数”,然后 CrewAI 帮你执行函数调用(function calling),把结果再喂回模型。这就是第 03 篇”函数调用”的 Agent 化封装。

2.2 Task 的上下文传递

Task 的 context 字段决定前置任务:Crew 按顺序执行时,会把前面任务的输出注入当前任务的上下文。这是隐式状态传递——与 LangGraph 的显式 reducer 形成对比。好处是写起来简单,坏处是”状态在哪个 Agent 手里”不透明。

2.3 Crew 的 kickoff

Crew.kickoff() 内部维护一个任务队列,sequential 模式下逐个执行;hierarchical 模式下先让 Manager 决定每个任务分配给谁。内部还有一个核心组件 TaskOutput / AgentOutput 数据结构,把每个任务的输出和元数据(token 用量、耗时)结构化——这正是第 11 篇可观测性要采集的数据源。

三、实战:钓鱼日记生成流水线改写

tools/douyin-blog/generate-blog.py 现在是一个 22KB 的串行 Python 脚本:读取 videos.json → 按日期找相册图片 → 调 DeepSeek 生成日记 → 写草稿。流程是”一步到底”,没有分工,也没有中间产物。

3.1 改写为 Crew

1
2
3
4
5
6
7
8
9
Crew: 钓鱼日记编辑部
├── 素材Agent(Role: 素材整理员)
│ Task1: 从 videos.json 和相册目录整理出素材清单
├── 策划Agent(Role: 内容策划)
│ Task2: 基于素材生成写作大纲(结构、节奏、亮点)
├── 写作Agent(Role: 技术宅日记作家)
│ Task3: 按大纲写完整日记,程序员比喻风格
└── 校对Agent(Role: SEO 编辑)
Task4: 校验 front matter、补 tags/categories、检查违禁词
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from crewai import Agent, Task, Crew, Process

material_agent = Agent(
role="素材整理员",
goal="从 videos.json 和相册目录整理本次出钓的素材清单",
backstory="严谨的钓鱼数据档案员,擅长从文件名和元数据中提取日期、地点、鱼获信息",
tools=[read_videos_json, list_album_photos],
llm="deepseek/deepseek-chat",
)

planner_agent = Agent(
role="内容策划",
goal="把素材组织成有起承转合的钓鱼日记大纲",
backstory="懂叙事节奏的策划,知道怎么把一次普通出钓写成有趣的故事",
)

writer_agent = Agent(
role="技术宅日记作家",
goal="按大纲写出有程序员味道的钓鱼日记",
backstory="既是程序员又是钓友,善用代码、Bug、调参比喻写钓鱼",
)

editor_agent = Agent(
role="SEO 编辑",
goal="校验并补全 front matter,优化标题与关键词",
backstory="熟悉 Hexo 博客规范和 SEO,能发现违禁词和格式问题",
)

task_material = Task(description="整理素材清单", expected_output="包含日期/地点/图片/鱼获的结构化清单", agent=material_agent)
task_outline = Task(description="生成写作大纲", expected_output="分节大纲与亮点", agent=planner_agent, context=[task_material])
task_draft = Task(description="写日记正文", expected_output="完整 Markdown 日记", agent=writer_agent, context=[task_outline])
task_seo = Task(description="SEO 校验", expected_output="front matter + 标签 + 关键词", agent=editor_agent, context=[task_draft])

crew = Crew(agents=[material_agent, planner_agent, writer_agent, editor_agent],
tasks=[task_material, task_outline, task_draft, task_seo],
process=Process.sequential)
result = crew.kickoff()

3.2 对比:CrewAI 版 vs 原串行版

维度 原 generate-blog.py CrewAI 版
代码结构 一个函数串到底 四角色职责分离
中间产物 无(一次生成) 素材清单→大纲→草稿→SEO,每步可审查
改一处逻辑 改主函数,牵一发动全身 只改对应 Agent 的 role/goal
每角色模型 单一模型 可给校对 Agent 配更强/更弱模型
可控性 完全确定 依赖 prompt 质量,需人工审查
部署 纯脚本 依赖 crewai 包,运行成本更高

结论:对于”生成一篇日记”这种角色分工明确的线性任务,CrewAI 的声明式写法更清晰、更易维护、可插拔(想给校对加工具,给 Agent 加一行就行)。但注意——改写不等于必然更好,原脚本一次调用 1 次 DeepSeek,Crew 版要 4 次调用(4 个 Agent),成本翻 4 倍。这是第 10 篇要深入讨论的成本问题。

四、LangGraph vs CrewAI:同场景对比复盘

用第 07 篇的”鱼获数据提取 Agent”在同一场景下对比:

场景需求 LangGraph CrewAI
提取失败自动重试(条件边) 原生支持,显式 无原生,需 Manager 或外部循环
提取到一半崩溃恢复 检查点 + thread_id 无内建,需自建重入
拆成”提取员/校验员/写入员” 也能做,但要自己画图 声明式,更自然
需要并行提取多篇 超步天然并行 需 await_fork 或自定义

选型最终建议(回扣第 01 篇决策树):

  • 有重试/分支/持久化需求 → LangGraph
  • 多角色线性分工、重视可读性 → CrewAI
  • 两者可以混用:CrewAI 管”角色分工”,外层用 LangGraph 画”流程控制图”,把每个 Crew 当一个节点——这是大型系统常见做法(第 10 篇实践)。

五、落地清单与小结

  • CrewAI 四类核心:Agent(角色人设 + 工具)、Task(描述 + 期望输出 + 上下文)、Crew(编排器)、Process(sequential/hierarchical)。
  • Agent 本质 = 动态 system prompt + function calling 封装,工具描述注入 prompt 让模型决定调用。
  • 与 LangGraph 对比:确定性控制选 LangGraph,角色分工选 CrewAI;CrewAI 无内建检查点,可恢复性弱。
  • 钓鱼日记生成流水线改写为 4 角色 Crew:素材→策划→写作→SEO,职责清晰、可审查,但成本从 1 次调用变 4 次——工程决策要算账。
  • 大型系统可混用:LangGraph 画流程图,CrewAI 做分工节点。

下一篇进入工具层:自研 MCP Server 实战。把博客的数据(TiDB 鱼获、文章检索、Hexo 草稿)封装成标准 MCP Server,接进 Claude/Cursor,实现”AI 助手直接操作博客工具”。


本系列持续更新中,每周一篇。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: