项目全景:CrewAI 这套代码长什么样
欢迎来到 CrewAI 60 天源码深度之旅。第一天不写代码、不背 API,只做一件事:把整个仓库在脑子里画出一张地图。CrewAI 是一个"多智能体协作"框架——你给它几个有角色的 AI(Agent),派给它们几件活(Task),把它们编成一个团队(Crew),然后一声 kickoff() 让团队跑起来。今天我们就从最外层的目录结构,一路看到 __init__.py 到底把哪些类摆在了门口。
痛点:为什么需要"多智能体"这样一个框架
# README.md:63(原文节选)
CrewAI is an open-source Python framework with high-level abstractions
and low-level APIs for building production-ready multi-agent workflows.
It gives developers autonomous agent collaboration through Crews and
precise, event-driven control through Flows.
high-level abstractions高层抽象:Agent / Task / Crew 这些"一句话能说清"的概念,让你几行代码搭出团队。low-level APIs低层 API:需要精细控制时(自定义执行循环、工具、LLM 适配)也能钻下去。这正是 60 天要挖的部分。Crews vs Flows★两条路线:Crew 强调"智能体自主协作",Flow 强调"事件驱动的确定性编排"。阶段7 会专门对比选型。打开仓库:这是一个 monorepo(多包同仓)
克隆下来第一眼你会发现:核心代码不在根目录,而在 lib/ 下面分成了好几个独立的包。这叫 monorepo——多个能各自发布的 Python 包住在同一个 git 仓库里,用 uv workspace 统一管理。根 pyproject.toml 第一行就点明它自己只是个"工作区壳子"(pyproject.toml:1):
# pyproject.toml:1
name = "crewai-workspace"
# ...
requires-python = ">=3.10,<3.14"
# pyproject.toml:231
[tool.uv.workspace]
# members 指向 lib/ 下的各个子包
name = "crewai-workspace"根包不是你 pip install crewai 装到的那个,它只是把 lib/ 里的子包"聚在一起开发"的容器。requires-python >=3.10,<3.14支持的 Python 版本区间。60 天里你只要有 3.10~3.13 任一个就行。[tool.uv.workspace]★用 uv(新一代 Python 包管理器)的 workspace 机制:一次 uv sync 就把 lib 下所有子包按依赖装好、互相以本地源码引用。crewai 包里。CrewAI 选择拆成 crewai / crewai-core / crewai-tools / crewai-files / crewai-cli / crewai-devtools 多个包。好处:① 按需安装——只想用核心就装 crewai,要一堆现成工具才装 crewai-tools,不背无关依赖;② 解耦发布——工具库更新频繁,核心库要稳,分开就能各自迭代。代价:目录变深、新手要先建立"哪个东西在哪个包"的地图(就是今天要干的事)。这是"用一点结构复杂度,换依赖清爽和迭代自由"的典型权衡。lib/ 下六个包,各管一摊
用 ls lib/ 能看到六个子包。每个子包里都有自己的 pyproject.toml,第一行的 name 就是它发布到 PyPI 的名字:
# 在仓库根执行,逐个看子包名
$ for d in lib/*/; do grep -m1 '^name' "$d/pyproject.toml"; done
name = "crewai-cli" # lib/cli
name = "crewai-core" # lib/crewai-core
name = "crewai-files" # lib/crewai-files
name = "crewai-tools" # lib/crewai-tools
name = "crewai" # lib/crewai ← ★ 60 天主战场
name = "crewai-devtools" # lib/devtools
| 包(目录) | 负责什么 | 60 天里的分量 |
|---|---|---|
crewai(lib/crewai) | 框架本体:Agent / Task / Crew / Flow / LLM / 记忆 / 工具接口 | ★★★ 绝大部分时间在这 |
crewai-core | 更底层的公共基座(版本、通用工具),被 crewai 依赖 | ★ 偶尔溯源 |
crewai-tools | 大量现成工具(搜索、文件、爬虫等),可选安装 | ★★ 工具系统那周 |
crewai-files | 文件输入/多模态文件处理 | ★ 用到再看 |
crewai-cli(lib/cli) | 命令行脚手架 crewai create/run 等 | ★ 收官那周 |
crewai-devtools | 开发者工具/调试辅助 | · 了解即可 |
lib/crewai/src/crewai/,要现成工具看 lib/crewai-tools/。其它四个是配角,用到再翻。走进 src/crewai:顶层目录导览
主战场是 lib/crewai/src/crewai/。ls 一下,把关键文件/目录对号入座(这就是 60 天大纲的物理地址):
$ ls lib/crewai/src/crewai/
__init__.py # ★ 门面:决定 import crewai 能拿到什么(L05 细看)
agent/ # Agent 本体(core.py 是主类)—— 阶段1/2
agents/ # 执行器/解析器:crew_agent_executor.py parser.py step_executor.py
task.py # ★ Task 模型(单文件,1400+ 行)—— D04/阶段3
tasks/ # Task 的周边:task_output.py conditional_task.py guardrail...
crew.py # ★ Crew 模型(2400 行,最大的文件)—— D05/阶段4
crews/ # crew_output.py 等结果对象
process.py # ★ Process 枚举(就 11 行!sequential / hierarchical)
flow/ # Flow 事件驱动编排 —— 阶段7
llm.py llms/ # LLM 抽象与各 provider 适配 —— 阶段8
memory/ rag/ knowledge/ # 记忆与知识 —— 阶段6
tools/ mcp/ # 工具系统与 MCP —— 阶段5
events/ # 事件总线/监听器(贯穿始终)
cli/ project/ security/ a2a/ telemetry/ # 生态/进阶 —— 阶段9/10
__init__.py:import crewai 到底给了你什么
当你写 from crewai import Agent, Crew, Task,这些名字从哪来?答案是包门面 __init__.py。它开头就把主角们从各自的模块里"请到门口"(__init__.py:8):
# __init__.py:8
from crewai.agent.core import Agent # ← 注意:Agent 在 agent/core.py,不是 agent/agent.py
from crewai.context import ExecutionContext
from crewai.crew import Crew
from crewai.crews.crew_output import CrewOutput
from crewai.flow.flow import Flow
from crewai.knowledge.knowledge import Knowledge
from crewai.llm import LLM
from crewai.llms.base_llm import BaseLLM
from crewai.process import Process
from crewai.task import Task
from crewai.tasks.task_output import TaskOutput
Agent ← agent.core★新手最常踩:源码里 没有 agent/agent.py,Agent 类真正住在 agent/core.py。大纲里写"agent/agent.py"是习惯叫法,实际以 core.py 为准(D03 会打开它)。Crew ← crewCrew 在单文件 crew.py(2400 行,全仓最大)。Task ← taskTask 在单文件 task.py。Flow / Process / LLM ...另一条主线 Flow、流程枚举 Process、模型抽象 LLM/BaseLLM 也都在门口就位。接着有一个很有意思的惰性导入机制——Memory 不在开头 import,而是等你第一次访问 crewai.Memory 才加载(__init__.py:58):
# __init__.py:53
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
"Memory": ("crewai.memory.unified_memory", "Memory"),
}
# __init__.py:58
def __getattr__(name: str) -> Any:
"""Lazily import heavy modules (e.g. Memory → lancedb) on first access."""
if name in _LAZY_IMPORTS:
module_path, attr = _LAZY_IMPORTS[name]
mod = importlib.import_module(module_path) # 用到时才真正 import
val = getattr(mod, attr)
globals()[name] = val # 缓存到模块全局,下次直接拿
return val
raise AttributeError(f"module 'crewai' has no attribute {name!r}")
Memory → lancedb。记忆功能背后拖着 lancedb 这类重量级向量库,加载慢、依赖多。如果在 import crewai 时就 import Memory,那么每个人——哪怕根本不用记忆功能——都要为这份重依赖付启动时间。源码用模块级 __getattr__(Python 的 PEP 562)实现"按名字延迟加载":只有真的用到 crewai.Memory 时才 import,且用 globals()[name]=val 缓存避免重复。取舍点:牺牲一点"门面代码的直观性",换来所有轻量用户的秒级启动。这是库设计里非常常见的"冷启动优化"。最后 __all__ 明确列出对外公开的名字(__init__.py:187):LLM, Agent, BaseLLM, Crew, CrewOutput, Entity, ExecutionContext, Flow, Knowledge, LLMGuardrail, Memory, PlanningConfig, Process, RuntimeState, Task, TaskOutput, __version__。这一串就是 CrewAI 官方认可的"公共 API 表面"。
五大主角与它们的关系
把 L05 请出来的名字连成关系,就是整个框架的心智模型。当前版本号也在门面里写着:__version__ = "1.15.2"(__init__.py:51)。
from crewai import Agent, Task, Crew, Process
analyst = Agent(
role="Senior Market Analyst",
goal="Conduct deep market analysis with expert insight",
backstory="You're a veteran analyst known for identifying subtle market patterns")
analysis_task = Task(
description="Analyze {sector} sector data for the past {timeframe}",
expected_output="Detailed market analysis with confidence score",
agent=analyst)
crew = Crew(agents=[analyst], tasks=[analysis_task],
process=Process.sequential, verbose=True)
result = crew.kickoff(inputs={"sector": "tech", "timeframe": "1W"}) # → CrewOutput
这段代码就用到了今天认识的全部主角。明天(D02)我们真的把它跑起来。边界 + 今日小结
try: model_rebuild() except?
翻到 __init__.py:152 附近,你会看到一长串 Task.model_rebuild(...) / Crew.model_rebuild(...) 被包在一个大 try/except (ImportError, PydanticUserError) 里。这是因为这些类是 Pydantic 模型且彼此循环引用(Crew 里有 Agent、Agent 里有 crew、Task 里有 agent……)。Pydantic 需要在所有类都定义完之后,回头"重建"每个模型来解析这些前向引用(forward ref)。__init__.py 里手工触发 model_rebuild 并吞掉可能的异常(只打 warning,见 __init__.py:178),是为了"即使某个可选依赖缺失也别让 import crewai 直接崩"。坑点:这也意味着如果你遇到"forward ref unresolved"类报错,根源往往在导入顺序,而不是你的业务代码——记住有这么个机制,排查时就不会慌。👶 小白:我 pip install crewai 装的,跟这个 lib/crewai 是一个东西吗?
👨🏫 老师:是同一份代码。你 pip 装的 crewai 包,源码就是 lib/crewai/src/crewai/。root 的 crewai-workspace 只是开发时用来同时管理多个子包的"工作区",不会被发布。所以 60 天读的都是你实际会 import 的真代码。
🧠 今天你应该能回答
- CrewAI 解决什么问题?(把一个 LLM 干所有事 → 变成有角色分工的团队协作)
- 它的两条主线是什么?(Crew 自主协作 / Flow 事件驱动可控)
- 为什么是 monorepo?拆了哪几个包?(按需安装+解耦发布;crewai/core/tools/files/cli/devtools)
- 60 天主战场目录在哪?(
lib/crewai/src/crewai/) Agent类的真实文件是?(agent/core.py,不是 agent.py)- Memory 为什么惰性导入?(它拖着 lancedb 重依赖,不用就别拖慢启动)
- 五大主角关系?(Crew 装 agents+tasks+process,kickoff 后产出 CrewOutput)
✋ 10 分钟动手
# 1. 看清 monorepo 分了几个包
ls lib/ && for d in lib/*/; do grep -m1 '^name' "$d/pyproject.toml"; done
# 2. 数一数三大主角文件多大(感受复杂度)
wc -l lib/crewai/src/crewai/agent/core.py \
lib/crewai/src/crewai/task.py \
lib/crewai/src/crewai/crew.py \
lib/crewai/src/crewai/process.py
# 3. 读门面导出的公共 API
sed -n '8,21p;187,205p' lib/crewai/src/crewai/__init__.py
uv 把环境装好,配一个 LLM key,然后亲手 kickoff 今天那段最小 crew,看着它真的跑出一份分析——顺便认识 inputs 插值和 CrewOutput 的结构。