基于仓库
crewAIInc/crewAI主干源码(版本1.14.3a3,位于 lib/crewai/src/crewai/),从最底层的 Agent 执行循环、事件总线与 LLM 抽象,一路向上拆到 Crew 编排、Flow 声明式工作流、内存/知识系统,并附 10+ 个端到端生产落地场景。CrewAI 是当前社区里最"Pythonic"的多 Agent 编排框架:零 LangChain 依赖(1.0 版本彻底解耦)、Pydantic-first、自带 CLI 脚手架、同时提供角色驱动的 Crew 与DAG 式的 Flow 两种互补范式。本文会把这套设计的每一层拆开讲清楚。
目录
- 项目全景与设计哲学
- 核心抽象关系图
- Agent 源码剖析
- Task 源码剖析
- Crew 编排源码剖析
- CrewAgentExecutor:ReAct 与原生 ToolCall 双循环
- LLM 层与 LiteLLM 集成
- Tools 工具系统
- Flow 声明式工作流框架
- Memory 统一记忆系统
- Knowledge 知识库(RAG)
- Events 事件总线与可观测性
- Checkpoint 持久化与断点续跑
- CLI 脚手架与项目结构
- 生产应用教程(10 个端到端场景)
- 生产落地清单(性能、成本、安全、可观测)
- 关键源码文件速查表
一、项目全景与设计哲学
1.1 仓库结构
crewAI/
├── docs/ # MkDocs 官方文档
├── pyproject.toml # 顶层工作空间
└── lib/
├── crewai/ # 主框架包
│ └── src/crewai/
│ ├── agent/ # Agent 核心类
│ ├── agents/ # Executor、Parser、ToolsHandler
│ ├── crew.py # Crew 编排核心(2298 行)
│ ├── task.py # Task 定义(2000+ 行)
│ ├── process.py # Process 枚举(sequential / hierarchical)
│ ├── flow/ # Flow 工作流框架
│ ├── llm.py / llms/ # LLM 抽象 + LiteLLM 适配
│ ├── memory/ # 统一记忆系统
│ ├── knowledge/ # RAG 知识库
│ ├── tools/ # Tool 基类与注册表
│ ├── events/ # 事件总线
│ ├── telemetry/ # 遥测
│ ├── cli/ # CLI 脚手架
│ ├── state/ # Checkpoint 持久化
│ ├── security/ # Fingerprint、权限
│ ├── rag/ # RAG 抽象层
│ ├── mcp/ # MCP 协议集成
│ ├── a2a/ # Agent-to-Agent 协议
│ ├── experimental/ # 新版 AgentExecutor
│ ├── hooks/ # LLM call hooks
│ └── project/ # @agent / @task / @crew 装饰器
├── crewai-tools/ # 第一方工具库(SerperDev、Firecrawl、…)
├── crewai-files/ # 文件处理工具
└── devtools/ # 开发者工具
1.2 两种互补范式:Crew vs Flow
CrewAI 最关键的设计决策是同时提供两套正交范式,两者可以互相嵌套使用:
| 维度 | Crew(角色驱动) | Flow(事件驱动) |
|---|---|---|
| 适用场景 | 角色明确、需要自主协作的问题(调研、写作、代码审查) | 确定性流程、需要精确控制状态流转的场景(数据管道、审核流) |
| 编程模型 | 声明 Agent/Task,框架自主调度 | @start / @listen / @router 装饰器手动拼接 DAG |
| 控制流 | Process.sequential 顺序 / Process.hierarchical Manager 分派 | 显式状态机,支持 and_() / or_() 合流 |
| 并发 | Task.async_execution=True | 按事件触发自然并发 |
| 状态 | TaskOutput 链式传递 | Pydantic State[T] 对象 + 持久化 |
| 代表类 | Agent、Task、Crew | Flow[T]、@start、@listen、@router |
两者的关系:Flow 里可以把一个 Crew.kickoff() 当成一个节点调用,Crew 里也可以触发 Flow;Flow 负责"大粒度的宏观流程",Crew 负责"小粒度的智能协作"。
1.3 核心设计哲学
- Pydantic-first:
Agent、Task、Crew、Flow全是BaseModel,天然可序列化、可校验、可检查点。 - Event-driven:运行时的每一步(Agent 启动、LLM 调用、Tool 调用、Task 完成)都通过单例
crewai_event_bus广播,为监控、日志、重放提供统一埋点。 - Provider-agnostic LLM:
llm.py是对litellm的包装,同一套 API 覆盖 20+ 供应商(OpenAI / Anthropic / Gemini / Bedrock / Azure / Ollama / DeepSeek / Groq…)。 - Tool = Pydantic schema + callable:工具自动生成
args_schema,既能喂给原生 ToolCall 也能喂给 ReAct 文本解析。 - 零 LangChain 依赖:1.x 之后彻底移除了 LangChain,所有抽象自己实现,减少版本冲突面。
二、核心抽象关系图
┌──────────────────────────────────────────────────────────────┐
│ Crew (crew.py) │
│ agents=[…] tasks=[…] process=sequential|hierarchical │
│ memory / knowledge / manager_agent / checkpoint │
└────────────────┬───────────────────────┬─────────────────────┘
│ 分派 │ 广播事件
▼ ▼
┌──────────────────────┐ ┌────────────────────────────┐
│ Task (task.py) │ │ crewai_event_bus │
│ description │ │ (events/event_bus.py) │
│ expected_output │ │ sync + async handlers │
│ agent / context │ └────────────────────────────┘
│ guardrail │ ▲
│ output_pydantic │ │emit
└─────────┬────────────┘ │
│ execute_sync() │
▼ │
┌───────────────────────┐ │
│ Agent (agent/core.py) │ │
│ role / goal / │ │
│ backstory / tools │─────────────┘
│ llm / memory │
└────────┬──────────────┘
│ invoke()
▼
┌────────────────────────────────────────────────┐
│ CrewAgentExecutor │
│ (agents/crew_agent_executor.py, 1617 行) │
│ ├── _invoke_loop_native_tools() ← 原生 ToolCall│
│ └── _invoke_loop_react() ← ReAct 文本 │
└────────┬───────────────────────────────────────┘
│
├──► LLM (llm.py) ── litellm ─► 20+ 供应商
├──► BaseTool ── Pydantic schema
├──► Memory (UnifiedMemory + LanceDB)
└──► Knowledge (向量检索)
关键类 & 文件索引:
| 抽象 | 文件 | 行数 | 职责 |
|---|---|---|---|
Agent | agent/core.py | 1884 | 自主 Agent:role/goal/backstory、工具调用、委托 |
Task | task.py | 2000+ | 可执行单元:描述、预期输出、guardrail、同步/异步 |
Crew | crew.py | 2298 | 多 Agent 编排:sequential / hierarchical |
Process | process.py | 12 | 枚举:sequential / hierarchical |
Flow | flow/flow.py | 3465 | 声明式 DAG + 状态机 |
LLM | llm.py | 2558 | LiteLLM 包装器 |
CrewAgentExecutor | agents/crew_agent_executor.py | 1617 | ReAct / 原生 ToolCall 双循环 |
Memory | memory/unified_memory.py | 400+ | LLM 驱动的统一记忆 |
Knowledge | knowledge/knowledge.py | 119 | RAG 知识源 |
BaseTool | tools/base_tool.py | 400+ | 工具基类 + 注册表 |
CrewAIEventsBus | events/event_bus.py | 800+ | 单例事件总线 |
三、Agent 源码剖析
3.1 类定义全貌
agent/core.py:156-300 定义了 Agent(BaseAgent),继承自 BaseAgent(位于 agents/agent_builder/base_agent.py)。最常用的字段:
class Agent(BaseAgent):
# ── 角色三件套(提示词核心)──
role: str # 角色定位:"资深 Python 工程师"
goal: str # 目标:"写出可维护的后端代码"
backstory: str # 背景故事:让 LLM 进入角色
# ── LLM 绑定 ──
llm: str | BaseLLM | None # 主 LLM,可传字符串 "gpt-4o"
function_calling_llm: BaseLLM | None # 仅用于 ToolCall 的 LLM
# (常用廉价模型调工具、贵模型做推理)
# ── 工具 & 协作 ──
tools: list[BaseTool] # 可用工具列表
allow_delegation: bool = False # 是否允许把任务委托给同 Crew 其他 Agent
knowledge: Knowledge | None # Agent 级知识库
memory: Memory | None # Agent 级记忆
# ── 执行控制 ──
max_iter: int = 25 # 最大推理步数(防死循环)
max_execution_time: int | None # 秒级超时
max_retry_limit: int = 2 # 解析失败重试次数
step_callback: Callable | None # 每步回调(流式观察)
verbose: bool = False
# ── 规划 & 推理 ──
planning: bool = False # 执行前先生成计划
reasoning: bool = False # 每步显式推理(增强 CoT)
planning_config: PlanningConfig | None
# ── 结构化输出 & 校验 ──
guardrail: GuardrailType | None # 函数式/LLM 式输出校验
3.2 执行入口:execute_task()
agent/core.py:714 定义 execute_task(task, context, tools) -> str,整体流程:
execute_task()
│
├─ 1. _prepare_task_execution(task, context)
│ 拼接 system_prompt(role+goal+backstory) + task.description + context
│
├─ 2. handle_knowledge_retrieval()
│ 如果绑了 knowledge,先做一轮 RAG,检索结果注入到 prompt
│
├─ 3. _finalize_task_prompt()
│ 附加工具说明(tools_description + tool_names)
│
├─ 4. _execute_without_timeout() / _execute_with_timeout()
│ 调用 self.agent_executor.invoke({
│ "input": task_prompt,
│ "tool_names": ",".join([t.name for t in tools]),
│ "tools_description": "...",
│ "ask_for_human_input": task.human_input
│ })
│
├─ 5. _finalize_task_execution()
│ guardrail 校验、structured output 解析、写入 memory
│
└─ 返回 str(原始字符串,由 Task 再包成 TaskOutput)
3.3 超时控制
agent/core.py:785-818 用 concurrent.futures.ThreadPoolExecutor 包了一层:
def _execute_with_timeout(self, prompt, timeout):
with ThreadPoolExecutor(max_workers=1) as pool:
future = pool.submit(self.agent_executor.invoke, prompt)
try:
return future.result(timeout=timeout)
except FuturesTimeoutError:
raise AgentExecutionTimeoutError(...)
注意:由于 Python GIL 与 ThreadPool 的特性,真正超时后底层的 HTTP 请求仍在后台跑完才释放连接,这只在 LLM 调用已返回、但 ReAct 循环陷死时有效。生产环境建议同时在 LLM 层设 timeout= 参数。
3.4 委托(Delegation)
当 allow_delegation=True 时,Agent 会自动获得两个内置工具:
Delegate work to coworker:把子任务交给同 Crew 的另一 AgentAsk question to coworker:向另一 Agent 发问
这两个工具由 tools/agent_tools/agent_tools.py 通过 AgentTools(agents=crew.agents).tools() 注入。在 hierarchical 流程里,manager agent 的 allow_delegation 自动置 True。
3.5 异步变体:aexecute_task
agent/core.py:846 提供 async def aexecute_task(...),内部走 CrewAgentExecutor.ainvoke()。CrewAI 从 1.0 起大部分调用链已打通 asyncio,配合 Task.async_execution=True 可以真正并发跑多个 Task。
3.6 轻量变体:LiteAgent
lite_agent.py 提供 LiteAgent —— 当你只需要"一个绑了工具的 LLM"、不需要 Crew/Task 编排时使用。它剥离了所有的 role/goal/backstory/planning/reasoning,只保留 LLM + tools + 一轮 prompt,适合做轻量级代理或嵌入到其他系统中。
四、Task 源码剖析
4.1 核心字段
task.py:106-280 定义 Task(BaseModel):
class Task(BaseModel):
description: str # 任务描述(会进入 prompt)
expected_output: str # 期望输出描述(也进入 prompt)
agent: BaseAgent | None # 指派给哪个 Agent(可在 Crew 里动态绑定)
# ── 上下文链式传递 ──
context: list[Task] | None # 把前置 Task 的 output 作为上下文
tools: list[BaseTool] | None # Task 专属工具(覆盖 Agent 级)
# ── 输出结构化(三选一)──
output_json: type[BaseModel] | None # JSON 模式
output_pydantic: type[BaseModel] | None # Pydantic 模式
response_model: type[BaseModel] | None # LLM 厂商原生 structured output
output_file: str | None # 写文件
# ── 并发与重试 ──
async_execution: bool = False
callback: Callable | None # 完成后回调
guardrail: GuardrailType | None # 输出校验(函数或 LLM)
guardrail_max_retries: int = 3
# ── 人工介入 ──
human_input: bool = False # 是否在完成后请求人工确认
markdown: bool = False # 强制 Markdown 输出
4.2 执行方法
task.execute_sync(agent, context, tools) -> TaskOutput # 同步
task.execute_async(agent, context, tools) -> Future[TaskOutput] # 异步
内部实际上调用 agent.execute_task(task, context, tools) 拿到字符串,再包装成 TaskOutput:
class TaskOutput(BaseModel):
description: str
raw: str # LLM 原始输出
pydantic: BaseModel | None # 已验证的结构化对象
json_dict: dict[str, Any] | None # JSON 字典
output_format: OutputFormat # raw / json / pydantic
summary: str # 自动截断描述做摘要
messages: list[LLMMessage] # 完整对话历史
4.3 Guardrails(输出校验)
两种 guardrail:
1. 函数式 guardrail
def validate_word_count(output: TaskOutput) -> tuple[bool, Any]:
if len(output.raw.split()) < 100:
return (False, "Output too short, please expand to at least 100 words")
return (True, output)
task = Task(
description="Write an article...",
expected_output="A 500+ word article",
guardrail=validate_word_count,
guardrail_max_retries=3,
)
校验失败时,(False, feedback) 里的 feedback 会作为 new user message 被喂回 LLM,让它重试。
2. LLM-as-judge guardrail
tasks/llm_guardrail.py 提供 LLMGuardrail:
from crewai import LLMGuardrail
task = Task(
description="...",
expected_output="...",
guardrail=LLMGuardrail(
description="输出必须是有效 JSON,且包含 title、body、tags 三个字段",
llm="gpt-4o-mini", # 用廉价模型做裁判
)
)
4.4 ConditionalTask
tasks/conditional_task.py 定义 ConditionalTask,多了一个 condition: Callable[[TaskOutput], bool] 字段。在 _execute_tasks 循环里(crew.py:1472-1478),Crew 会先执行 condition 函数,返回 False 则跳过这个 Task。适合"质检失败才走修复分支"这类场景。
五、Crew 编排源码剖析
5.1 关键字段
class Crew(BaseModel):
agents: list[BaseAgent]
tasks: list[Task]
process: Process = Process.sequential
# ── Hierarchical 模式专属 ──
manager_llm: str | BaseLLM | None
manager_agent: BaseAgent | None # 自定义 Manager
# ── 共享资源 ──
memory: bool | Memory = False # 启用后 Crew 级记忆
knowledge: Knowledge | None
cache: bool = True # 工具结果缓存
# ── 运行时控制 ──
verbose: bool = False
stream: bool = False # 流式输出
max_rpm: int | None # 全局 RPM 限流
planning: bool = False # 执行前用 Planner 生成计划
planning_llm: str | BaseLLM | None
function_calling_llm: LLM | None # 覆盖所有 Agent 的 tool LLM
# ── 持久化 & 安全 ──
checkpoint: CheckpointConfig | bool | None
security_config: SecurityConfig # 指纹、权限
# ── 回调 ──
before_kickoff_callbacks: list[Callable]
after_kickoff_callbacks: list[Callable]
task_callback: Callable | None # 每个 Task 完成后触发
step_callback: Callable | None # 每步(Agent 推理)触发
5.2 kickoff() 完整流程
def kickoff(self, inputs: dict | None = None) -> CrewOutput:
# 0. 如果有 checkpoint,先恢复状态
self.apply_checkpoint()
# 1. 如果启用流式,返回 CrewStreamingOutput 生成器
if self.stream:
return self._kickoff_streaming(inputs)
# 2. before_kickoff_callbacks
for cb in self.before_kickoff_callbacks:
inputs = cb(inputs) or inputs
# 3. prepare_kickoff(): 模板变量替换(role/goal/backstory/description/expected_output 中 {var} 占位符)
self.prepare_kickoff(inputs)
# 4. 路由到具体流程
if self.process == Process.sequential:
result = self._run_sequential_process()
elif self.process == Process.hierarchical:
result = self._run_hierarchical_process()
# 5. after_kickoff_callbacks
for cb in self.after_kickoff_callbacks:
result = cb(result) or result
# 6. 聚合 usage_metrics(token、成本)
self.usage_metrics = self.calculate_usage_metrics()
# 7. 等待所有异步 memory 写入完成
self._memory.drain_writes()
return result
5.3 Sequential 流程
def _run_sequential_process(self) -> CrewOutput:
return self._execute_tasks(self.tasks)
极简!真正的逻辑在 _execute_tasks(crew.py:1441+):
def _execute_tasks(self, tasks):
task_outputs = []
futures = [] # 待收集的异步 Future
for task_index, task in enumerate(tasks):
exec_data = self._prepare_task_execution(task, inputs)
# 1. ConditionalTask 先判条件
if isinstance(task, ConditionalTask):
skipped = self._handle_conditional_task(task, task_outputs, ...)
if skipped:
task_outputs.append(skipped)
continue
# 2. 异步任务 → 丢进池子,不阻塞
if task.async_execution:
future = task.execute_async(
agent=exec_data.agent,
context=self._get_context(task, task_outputs),
tools=exec_data.tools,
)
futures.append((task, future, task_index))
else:
# 3. 遇到同步任务前,先 await 之前所有异步任务
if futures:
task_outputs = self._process_async_tasks(futures)
futures.clear()
# 4. 同步执行
context = self._get_context(task, task_outputs)
out = task.execute_sync(
agent=exec_data.agent, context=context, tools=exec_data.tools
)
task_outputs.append(out)
# 5. 每个 Task 完成后,触发 task_callback
if self.task_callback:
self.task_callback(out)
# 6. 收尾:未消费的 Futures
if futures:
task_outputs.extend(self._process_async_tasks(futures))
return CrewOutput(...)
并发语义:连续的异步 Task 会并发跑,直到遇到第一个同步 Task 为止——相当于"数据并行 + 同步屏障",既保留了声明简洁,又避免了乱序依赖。
5.4 Hierarchical 流程
def _run_hierarchical_process(self) -> CrewOutput:
self._create_manager_agent()
return self._execute_tasks(self.tasks)
def _create_manager_agent(self):
self.manager_llm = create_llm(self.manager_llm)
manager = Agent(
role=i18n.retrieve("hierarchical_manager_agent", "role"),
goal=i18n.retrieve("hierarchical_manager_agent", "goal"),
backstory=i18n.retrieve("hierarchical_manager_agent", "backstory"),
tools=AgentTools(agents=self.agents).tools(), # ← 核心:委托工具
allow_delegation=True,
llm=self.manager_llm,
verbose=self.verbose,
)
self.manager_agent = manager
工作机制:
- Manager 不在
self.agents里,是单独创建的"元 Agent"。 - 所有 Task 的
agent字段都会被动态替换成 manager。 - Manager 自己不干活,通过
Delegate work to coworker工具把任务拆给真正的执行者。 - Manager 的
backstory里明确告诉它:"你不要自己回答,要委托给最合适的同事"。
5.5 Planning(可选前置规划)
当 Crew(planning=True) 时,prepare_kickoff 里会先用 CrewPlanner(utilities/planning_handler.py)跑一次 LLM 生成"每个 Task 的详细步骤",并把计划注入到 Task.description 前部。适合任务描述不够具体、需要 LLM 再做一轮分解的场景。
5.6 回调钩子
Crew 暴露了五级回调,按粒度由粗到细:
| 回调 | 触发时机 |
|---|---|
before_kickoff_callbacks | 启动前,可改写 inputs |
after_kickoff_callbacks | 结束后,可改写最终 CrewOutput |
task_callback | 每个 Task 完成 |
step_callback(Agent 级) | 每步 Agent 推理(每次 LLM 调用后) |
| Event Bus | 细到 Tool 调用、LLM chunk、memory 保存 |
六、CrewAgentExecutor:ReAct 与原生 ToolCall 双循环
这是 CrewAI 最复杂也是最关键的一块代码,位于 agents/crew_agent_executor.py(1617 行)。
6.1 分发策略
invoke() 方法(line 192-228):
def invoke(self, inputs: dict) -> dict:
self._setup_messages(inputs) # 组装 system + user messages
self._inject_multimodal_files(inputs) # 附件注入
self._show_start_logs()
# 双循环分发
use_native = (
hasattr(self.llm, "supports_function_calling")
and self.llm.supports_function_calling()
and self.original_tools # 有工具才走原生
)
if use_native:
formatted_answer = self._invoke_loop_native_tools()
else:
formatted_answer = self._invoke_loop_react()
return {"output": formatted_answer.output}
6.2 ReAct 循环
agents/crew_agent_executor.py:311-451:
ReAct 模式下 LLM 输出必须遵循:
Thought: 我需要先查一下今天的天气
Action: weather_tool
Action Input: {"city": "Shanghai"}
Observation: 25°C, 晴
Thought: 现在可以回答了
Final Answer: 上海今天 25 度晴
Executor 通过正则解析这段文本:
def _invoke_loop_react(self):
formatted_answer = None
while not isinstance(formatted_answer, AgentFinish):
# 1. 超最大轮次保护
if has_reached_max_iterations(self.iterations, self.max_iter):
formatted_answer = handle_max_iterations_exceeded(...)
break
# 2. RPM 限流
enforce_rpm_limit(self.request_within_rpm_limit)
# 3. 调 LLM
answer = get_llm_response(
llm=self.llm,
messages=self.messages,
callbacks=self.callbacks,
response_model=self.response_model,
)
# 4. 解析
formatted_answer = process_llm_response(answer, self.use_stop_words)
# parser.py 里区分 AgentAction / AgentFinish
# 5. Tool 调用
if isinstance(formatted_answer, AgentAction):
tool_result = self.tools_handler.use_tool(formatted_answer)
# 拼接 Observation 回到 messages
self.messages.append({"role": "user",
"content": f"Observation: {tool_result}"})
self.iterations += 1
elif isinstance(formatted_answer, AgentFinish):
break
return formatted_answer
Parser 细节(agents/parser.py):
class AgentAction:
tool: str
tool_input: str
log: str # 原始文本
class AgentFinish:
output: str
log: str
text: str
正则同时兼容:
Final Answer:/final answer:- JSON / 纯文本的
Action Input - 代码块包裹的输入
json ...
6.3 原生 ToolCall 循环
对于 GPT-4o、Claude-3.5、Gemini-2.0 等支持 function calling 的模型,CrewAI 会走更高效的 _invoke_loop_native_tools:
- 把每个
BaseTool转成 OpenAI function schema(tools/agent_tools/tool_calling.py)。 - LLM 返回结构化的
tool_calls数组,无需正则解析。 - 每个 tool_call 独立执行,结果通过
role: tool消息回灌。 - 如果 LLM 返回
finish_reason == "stop"且没有 tool_calls,视为 AgentFinish。
优势:节省 30–60% 的输出 token(省掉 Thought/Action/Observation 模板)、延迟更低、解析失败率接近 0。
6.4 错误恢复
| 情形 | 处理 |
|---|---|
达到 max_iter | 调用 handle_max_iterations_exceeded:让 LLM 用当前已掌握的信息给出 Final Answer |
| 上下文超长 | _handle_context_length() 触发 SummaryCondensator,压缩历史 messages |
| Tool 抛异常 | 把错误信息当 Observation 喂回去,由 LLM 决定重试/换策略 |
| 解析失败 | 添加 "I couldn't parse your output..." 提示,重试最多 max_retry_limit 次 |
| Guardrail 失败 | 把 feedback 当 user message,重试最多 guardrail_max_retries 次 |
七、LLM 层与 LiteLLM 集成
7.1 设计:LiteLLM Proxy-in-Process
llm.py 2558 行,核心就是把 litellm.completion() 包装成一个有状态的 LLM 类。为什么不直接用 litellm?——因为 CrewAI 需要做这些额外的事:
- 统一的上下文窗口管理(不同模型不同大小)
- 自动 fallback 模型
- 事件发射(
LLMCallStartedEvent/LLMCallCompletedEvent) - 结构化输出适配(OpenAI json_mode / Anthropic tool / Gemini responseSchema 的差异)
- 钩子注入点(hooks/llm_hooks.py)
7.2 核心配置
from crewai import LLM
llm = LLM(
model="gpt-4o",
temperature=0.7,
max_tokens=4096,
timeout=60,
base_url="https://my-proxy.example.com/v1", # 支持 OpenAI-compatible 代理
api_key=os.environ["OPENAI_API_KEY"],
top_p=1.0,
frequency_penalty=0.0,
seed=42, # 可复现
stop=["\n\nObservation:"], # ReAct 停止词
reasoning_effort="medium", # o1/o3 reasoning
logprobs=False,
response_format=MyPydanticModel, # 结构化输出
)
7.3 上下文窗口字典
llm.py:112-200 维护了 LLM_CONTEXT_WINDOW_SIZES 常量:
LLM_CONTEXT_WINDOW_SIZES = {
"gpt-4": 8192,
"gpt-4o": 128_000,
"gpt-4o-mini": 200_000,
"gpt-4.1": 1_047_576,
"o1-preview": 128_000,
"o3-mini": 200_000,
"claude-3-5-sonnet-20241022": 200_000,
"claude-3-opus-20240229": 200_000,
"gemini-1.5-pro": 2_097_152,
"gemini-2.0-flash": 1_048_576,
"gemini-2.5-pro": 2_097_152,
"deepseek-chat": 64_000,
# ...
}
这个字典驱动了 _handle_context_length 的触发时机。
7.4 结构化输出
from pydantic import BaseModel
class Article(BaseModel):
title: str
body: str
tags: list[str]
task = Task(
description="Write a blog post about AI",
expected_output="Article with title, body, and tags",
output_pydantic=Article, # Pydantic 校验
# 或
response_model=Article, # 走 provider 原生 structured output
)
output_pydantic:让 LLM 返回 JSON,Python 侧用 Pydantic 校验+重试。response_model:直接把 schema 传给 OpenAIresponse_format/ Anthropic tools / GeminiresponseSchema,由厂商保证语法合法性,通常更可靠。
八、Tools 工具系统
8.1 BaseTool
class BaseTool(BaseModel, ABC):
name: str # 对 LLM 可见的名字
description: str # 对 LLM 可见的用法说明(非常重要)
args_schema: type[PydanticBaseModel] # 参数 schema
env_vars: list[EnvVar] = [] # 需要的环境变量
cache_function: Callable | None = None # 缓存命中判断
@abstractmethod
def _run(self, *args, **kwargs) -> Any: ...
子类自动注册(line 108-111 __init_subclass__):每个 BaseTool 子类在定义时会被写入 _TOOL_TYPE_REGISTRY,用于 checkpoint 反序列化(知道该用哪个类复活)。
8.2 三种定义工具的方式
方式 1:继承 BaseTool(最灵活)
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class WeatherInput(BaseModel):
city: str = Field(description="城市名称,如 Shanghai")
class WeatherTool(BaseTool):
name: str = "get_weather"
description: str = "查询指定城市的实时天气"
args_schema: type[BaseModel] = WeatherInput
def _run(self, city: str) -> str:
resp = requests.get(f"https://api.weather.com/{city}")
return resp.json()["summary"]
方式 2:@tool 装饰器(最简)
from crewai.tools import tool
@tool("Calculator")
def calculator(expression: str) -> str:
"""Safely evaluate a math expression."""
return str(eval(expression, {"__builtins__": {}}))
内部会生成一个 CrewStructuredTool 实例,自动从 type hints + docstring 推导 args_schema 和 description。
方式 3:MCP 工具
CrewAI 内置 MCP 客户端(mcp/),可以直接把 MCP server 暴露的工具注册进 Agent:
from crewai.mcp import MCPServerAdapter
mcp = MCPServerAdapter(url="http://localhost:3000/mcp")
agent = Agent(role="...", tools=mcp.tools())
8.3 工具结果缓存
class ExpensiveTool(BaseTool):
name = "fetch_report"
cache_function = lambda args, result: args.get("report_id") is not None
# 只要 report_id 相同,就不重复调用
缓存由 agents/cache/cache_handler.py 实现,Crew 级开关 cache=True。
8.4 第一方工具库
crewai-tools 是独立的 pip 包,提供 40+ 开箱即用工具:
- Web:
SerperDevTool、WebsiteSearchTool、FirecrawlScrapeWebsiteTool - 文件:
FileReadTool、DirectoryReadTool、PDFSearchTool、DOCXSearchTool - RAG:
RagTool、CSVSearchTool、JSONSearchTool、XMLSearchTool - 代码:
CodeInterpreterTool(Docker 沙箱)、GithubSearchTool - 数据库:
MySQLSearchTool、PGSearchTool
使用:
from crewai_tools import SerperDevTool, FileReadTool
agent = Agent(tools=[SerperDevTool(), FileReadTool("data.csv")])
九、Flow 声明式工作流框架
Flow 是 CrewAI 在 0.80+ 版本加入的第二种编程模型,位于 flow/flow.py(3465 行)。
9.1 心智模型
想象一下 Python 版的 AWS Step Functions:
- 用
@start声明起点 - 用
@listen("prev_method")声明"当 prev_method 完成时执行我" - 用
@router声明条件分支 - 用
and_() / or_()声明多输入合流 - 每个方法的返回值会被持久化到
state
9.2 最小示例
from crewai.flow.flow import Flow, start, listen, router
from pydantic import BaseModel
class MyState(BaseModel):
topic: str = ""
research: str = ""
article: str = ""
score: int = 0
class ContentFlow(Flow[MyState]):
@start()
def kickoff(self):
self.state.topic = "Quantum Computing"
return self.state.topic
@listen(kickoff)
def do_research(self, topic):
# 可以直接调 Crew
crew = research_crew()
result = crew.kickoff(inputs={"topic": topic})
self.state.research = result.raw
return result.raw
@listen(do_research)
def write_article(self, research):
# ...
self.state.article = "..."
return self.state.article
@router(write_article)
def quality_gate(self):
self.state.score = evaluate(self.state.article)
if self.state.score > 8:
return "ship"
return "revise"
@listen("ship")
def publish(self):
publish_to_cms(self.state.article)
@listen("revise")
def revise(self):
return self.write_article(self.state.research) # 回到写作
flow = ContentFlow()
flow.kickoff()
9.3 装饰器实现
flow/flow_wrappers.py:22-168 定义了装饰器的内部表达:
FlowCondition = TypedDict(
"type": Literal["OR", "AND"],
"conditions": Sequence[FlowMethodName | FlowCondition], # 可嵌套
"methods": list[FlowMethodName],
)
每个装饰器把普通方法包装成 FlowMethod 子类(StartMethod / ListenMethod / RouterMethod),并挂上:
__trigger_methods__: list[str]__condition_type__: "OR" | "AND"__trigger_condition__: FlowCondition(复杂条件)
FlowMeta 元类在类创建时扫描所有方法,构建 _start_methods、_listeners、_routers 三张表。
9.4 运行时引擎
flow/flow.py:892+ 的执行引擎本质是一个 work-queue:
初始化 → 队列 = [所有 @start 方法]
while 队列非空:
m = 队列.pop()
output = 执行 m(上游输出)
_completed_methods.add(m)
_method_outputs.append(output)
for 监听者 l in _listeners:
if l 已经在队列/已完成: 跳过
if l.condition 是 OR: 只要有一个 trigger 在 _completed 里就入队
if l.condition 是 AND: 所有 trigger 都在 _completed 才入队
AND 条件处理(_pending_and_listeners):当部分前置完成时,会累积计数器,最后一个前置完成时才触发。
9.5 状态管理
Flow[T] 的 T 可以是:
dict[str, Any]:默认BaseModel子类:类型安全,推荐
self.state 是 _state: Any = PrivateAttr() 的门面。在方法里修改 state 会自动被持久化(如果配了 persistence)。
9.6 持久化与 fork
flow/persistence/ 提供了持久化后端(默认 SQLite):
flow = ContentFlow(
persistence=SQLiteFlowPersistence(".flows.db"),
checkpoint=True,
)
flow.kickoff(id="run-123")
# 崩溃后可以从 checkpoint 恢复
flow2 = ContentFlow.from_checkpoint(config)
flow2.kickoff()
# 也可以 fork 一个分支做 A/B
flow3 = ContentFlow.fork(config, branch="experiment-v2")
9.7 可视化
flow/visualization/ 基于 pyvis + networkx 把 Flow 结构画成 HTML:
crewai plot flow
# 输出 crewai_flow.html
十、Memory 统一记忆系统
10.1 设计:LLM-driven Unified Memory
CrewAI 1.x 抛弃了旧的"short_term / long_term / entity"三分法,引入 UnifiedMemory(memory/unified_memory.py)。核心思想:让 LLM 自己决定记忆的 scope、重要性、类别。
class Memory(BaseModel):
llm: str | BaseLLM # 分析用 LLM(默认 gpt-4o-mini 够了)
storage: StorageBackend = "lancedb" # 向量库,默认本地 LanceDB
embedder: EmbedderConfig
# 检索打分权重
recency_weight: float = 0.3
semantic_weight: float = 0.5
importance_weight: float = 0.2
recency_half_life_days: int = 30
# 合并阈值:相似度 > 0.85 自动合并
consolidation_threshold: float = 0.85
# 默认重要性
default_importance: float = 0.5
confidence_threshold_high: float = 0.8
confidence_threshold_low: float = 0.5
# 探索预算:检索时允许偏离当前 scope 多少
exploration_budget: int = 1
# 根 scope 前缀(多租户隔离)
root_scope: str | None = None
10.2 写入:remember()
memory = Memory(root_scope="tenant_42")
await memory.aremember("用户喜欢 Python 超过 Go,尤其是异步场景")
内部:
- 调用 LLM 分析这段文本 → 推断 scope("user.preferences.languages")、category、importance、tags
- 生成 embedding
- 查询相似已有记忆,若相似度 > 0.85 则合并
- 写入 LanceDB(向量 + metadata)
10.3 读取:recall()
results = memory.recall("用户偏好什么编程语言?", limit=5)
打分公式(memory/recall_flow.py):
score = semantic_weight * cosine_similarity
+ recency_weight * exp(-age_days / half_life)
+ importance_weight * importance
RecallFlow 还支持自适应深度:初步检索结果不够就扩大 scope 再检索,由 exploration_budget 控制。
10.4 在 Crew 里启用
crew = Crew(
agents=[...],
tasks=[...],
memory=True, # 用默认配置
# 或
memory=Memory(
llm="gpt-4o-mini",
root_scope=f"project_{project_id}",
),
)
启用后,每个 Task 完成时会自动 remember(task_output),每个 Task 开始时会 recall(task.description) 把相关记忆注入到 prompt。
10.5 手动管理
crewai memory # 启动 TUI 查看/编辑记忆
crewai reset-memories # 清空
十一、Knowledge 知识库(RAG)
Memory 是"动态的、会自增长的",Knowledge 是"静态的、预先准备好的"。二者独立。
11.1 基础用法
from crewai.knowledge.source.pdf_knowledge_source import PDFKnowledgeSource
from crewai import Agent, Knowledge
pdf_source = PDFKnowledgeSource(file_paths=["manual.pdf"])
knowledge = Knowledge(
collection_name="product_docs",
sources=[pdf_source],
embedder={"provider": "openai", "config": {"model": "text-embedding-3-small"}},
)
agent = Agent(
role="Support Engineer",
goal="Answer customer questions from the manual",
backstory="...",
knowledge=knowledge,
)
11.2 源类型
TextFileKnowledgeSourcePDFKnowledgeSourceCSVKnowledgeSourceJSONKnowledgeSourceExcelKnowledgeSourceCrewDoclingSource(自动识别格式)StringKnowledgeSource(直接传字符串)
所有源都继承 BaseKnowledgeSource,需实现 load_content()。
11.3 执行流程
当 Task 执行时,agent.handle_knowledge_retrieval() 会:
- 把
task.description作为 query - 调
knowledge.query(queries=[query], results_limit=3, score_threshold=0.35) - 拿到相关 chunk 后拼到 prompt 前面作为上下文
十二、Events 事件总线与可观测性
12.1 单例设计
class CrewAIEventsBus:
_instance: CrewAIEventsBus | None = None
_sync_handlers: dict[type[BaseEvent], list[Callable]]
_async_handlers: dict[type[BaseEvent], list[Callable]]
_executor: ThreadPoolExecutor # 同步 handler 池
_loop: asyncio.AbstractEventLoop # 异步 handler 池
crewai_event_bus = CrewAIEventsBus()
12.2 事件类型
agent_events.py-AgentExecutionStartedEvent、AgentExecutionCompletedEvent、AgentExecutionErrorEventcrew_events.py-CrewKickoffStartedEvent、CrewKickoffCompletedEventtask_events.py-TaskStartedEvent、TaskCompletedEvent、TaskFailedEventllm_events.py-LLMCallStartedEvent、LLMCallCompletedEvent、LLMStreamChunkEventtool_usage_events.py-ToolUsageStartedEvent、ToolUsageFinishedEvent、ToolUsageErrorEventmemory_events.py、knowledge_events.py、flow_events.py、skill_events.py
12.3 订阅示例
from crewai.events import crewai_event_bus
from crewai.events.types.llm_events import LLMCallCompletedEvent
@crewai_event_bus.on(LLMCallCompletedEvent)
def log_llm_cost(source, event: LLMCallCompletedEvent):
print(f"[{event.call_type}] tokens={event.usage.total_tokens} "
f"cost=${event.cost:.4f} latency={event.latency_ms}ms")
# 或者异步 handler
@crewai_event_bus.on_async(ToolUsageFinishedEvent)
async def push_to_elk(source, event):
await elk_client.send({...})
12.4 与 OpenTelemetry 集成
CrewAI 自带的 telemetry/ 模块可以在不改代码的情况下把 Events 转成 OTLP span。在 .env 里:
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com
CREWAI_TELEMETRY_OPT_IN=true
十三、Checkpoint 持久化与断点续跑
13.1 动机
Crew/Flow 跑到一半崩了怎么办?重跑浪费 LLM 费用。CrewAI 在 1.x 引入了 Checkpoint:
from crewai import Crew
from crewai.state.checkpoint_config import CheckpointConfig
crew = Crew(
agents=[...],
tasks=[...],
checkpoint=CheckpointConfig(
folder=".checkpoints/",
run_id="article-2024-01-15",
),
)
result = crew.kickoff(inputs={"topic": "AI"})
# 中途崩了...
恢复:
crew = Crew.from_checkpoint(CheckpointConfig(
folder=".checkpoints/",
restore_from="latest",
))
result = crew.kickoff() # 从最后一个成功完成的 Task 之后继续
13.2 实现
每个 Task 完成后,state/ 会把:
- 已完成的
TaskOutput列表 - 当前迭代计数
- Agent 的 messages buffer
- Flow 的
_completed_methods、_method_outputs、_state
序列化成 JSON 写进 checkpoint 文件。利用 Pydantic model_dump() 天然支持。
13.3 CLI 支持
crewai run crew --checkpoint .checkpoints/
crewai replay crew --task-id <task_uuid> # 从指定 Task 开始重放
十四、CLI 脚手架与项目结构
十四.1 命令列表
| 命令 | 作用 |
|---|---|
crewai create crew <name> | 创建 Crew 项目 |
crewai create flow <name> | 创建 Flow 项目 |
crewai install | 用 uv 装依赖 |
crewai run | 执行主 crew/flow |
crewai train | 训练模式(收集反馈) |
crewai test | 基于评测数据集打分 |
crewai evaluate | 评估 crew 表现 |
crewai replay <task-id> | 重放某任务 |
crewai chat | 与 crew 交互式对话 |
crewai memory | 记忆 TUI |
crewai reset-memories | 清空记忆 |
crewai tools | 管理/发布工具 |
crewai plot flow | 画 Flow 图 |
crewai deploy | 部署到 CrewAI Enterprise |
crewai triggers | Webhook 触发器管理 |
14.2 crewai create crew 生成的目录
my_project/
├── pyproject.toml
├── README.md
├── .env
├── knowledge/
│ └── user_preference.txt
└── src/my_project/
├── __init__.py
├── main.py # run_crew(), train(), test(), replay()
├── crew.py # 用 @CrewBase / @agent / @task / @crew 装饰器
├── tools/
│ └── custom_tool.py
└── config/
├── agents.yaml # ← 角色定义
└── tasks.yaml # ← 任务定义
crew.py 模板:
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
@CrewBase
class MyProject:
agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
@agent
def researcher(self) -> Agent:
return Agent(config=self.agents_config["researcher"])
@task
def research_task(self) -> Task:
return Task(config=self.tasks_config["research_task"])
@crew
def crew(self) -> Crew:
return Crew(
agents=self.agents, # 自动从 @agent 装饰器收集
tasks=self.tasks,
process=Process.sequential,
)
agents.yaml:
researcher:
role: Senior Research Analyst
goal: Uncover cutting-edge developments in {topic}
backstory: >
You're a seasoned researcher with a knack for uncovering
the latest developments in {topic}.
llm: gpt-4o
{topic} 是运行时 inputs 里的变量。
14.3 装饰器系统
project/ 的 @CrewBase / @agent / @task / @crew / @before_kickoff / @after_kickoff 通过元类扫描把 YAML 和 Python 方法关联起来,让 CLI 能标准化地 kickoff。
十五、生产应用教程(10 个端到端场景)
15.1 场景一:金融舆情日报 Crew
需求:每天早 8 点自动抓取隔夜海外财经新闻,按板块分类、打分、生成中文摘要、发企业微信。
# crew.py
from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool, WebsiteSearchTool
from pydantic import BaseModel
class NewsItem(BaseModel):
title: str
url: str
sector: str
sentiment: int # -5 ~ 5
summary_cn: str
class DailyReport(BaseModel):
date: str
items: list[NewsItem]
top_movers: list[str]
crawler = Agent(
role="海外财经爬虫",
goal="从 Bloomberg、Reuters、FT 抓取过去 12h 的新闻标题和链接",
backstory="你是一位 20 年经验的财经信息工程师。",
tools=[SerperDevTool(), WebsiteSearchTool()],
llm="gpt-4o-mini",
max_iter=10,
)
analyst = Agent(
role="宏观分析师",
goal="对每条新闻进行板块分类(科技/能源/金融/地缘/其他)和情绪打分",
backstory="CFA 持证人,精通各板块逻辑。",
llm="gpt-4o",
)
editor = Agent(
role="中文财经编辑",
goal="用 200 字以内中文摘要每条新闻,并选出 Top 5 影响最大的",
backstory="《财新》资深编辑。",
llm="gpt-4o",
)
crawl_task = Task(
description="抓取过去 12 小时的 Bloomberg/Reuters/FT 头条,至少 20 条",
expected_output="JSON 列表,含 title 和 url",
agent=crawler,
)
analyze_task = Task(
description="对每条新闻分类并打分",
expected_output="带 sector 和 sentiment 字段的列表",
agent=analyst,
context=[crawl_task],
)
report_task = Task(
description="生成中文日报",
expected_output="完整的 DailyReport",
agent=editor,
context=[analyze_task],
output_pydantic=DailyReport,
)
daily_crew = Crew(
agents=[crawler, analyst, editor],
tasks=[crawl_task, analyze_task, report_task],
process=Process.sequential,
memory=True,
verbose=True,
)
if __name__ == "__main__":
import datetime
report = daily_crew.kickoff(inputs={"date": datetime.date.today().isoformat()})
# report.pydantic 是 DailyReport 实例
push_to_wechat(report.pydantic)
用 cron 或 k8s CronJob 定时调度即可。用 memory=True 让模型记住昨天的大新闻,避免重复分析。
15.2 场景二:客诉工单自动分诊(Hierarchical)
triage_manager = Agent(
role="Support Triage Manager",
goal="Classify tickets and delegate to the right specialist",
backstory="...",
llm="gpt-4o",
)
billing_expert = Agent(role="Billing Expert", ..., tools=[sql_tool])
technical_expert = Agent(role="Technical Expert", ..., tools=[kb_search])
churn_saver = Agent(role="Retention Specialist", ...)
classify = Task(
description="Read ticket #{ticket_id} and decide: billing / technical / churn_risk",
expected_output="A final answer from the appropriate specialist.",
)
crew = Crew(
agents=[billing_expert, technical_expert, churn_saver],
tasks=[classify],
process=Process.hierarchical,
manager_agent=triage_manager, # 自定义 Manager
verbose=True,
)
Manager 自动拿到 Delegate work to coworker 工具,可以根据内容委派给专家,专家干完再回执给 Manager 汇总。
15.3 场景三:多模态 PDF 合同审查
from crewai_tools import PDFSearchTool
pdf_tool = PDFSearchTool(pdf="contract.pdf")
reviewer = Agent(
role="法务合同审查员",
goal="找出所有对我方不利的条款、罚则、自动续约",
backstory="8 年公司法经验。",
tools=[pdf_tool],
llm=LLM(model="claude-3-5-sonnet-20241022", temperature=0),
)
class Risk(BaseModel):
clause: str # 原文
page: int
risk_level: Literal["low", "medium", "high"]
suggestion_cn: str
class Review(BaseModel):
risks: list[Risk]
overall_score: int # 1-10
review_task = Task(
description="审查整份合同,输出至少 5 个风险点",
expected_output="Review 对象",
agent=reviewer,
output_pydantic=Review,
guardrail=lambda out: (len(out.pydantic.risks) >= 3,
"至少输出 3 条风险") if out.pydantic else (False, "解析失败"),
guardrail_max_retries=3,
)
15.4 场景四:研发团队自动 Code Review Crew
from crewai_tools import FileReadTool, DirectoryReadTool, CodeInterpreterTool
style_checker = Agent(
role="Style Reviewer",
goal="Flag PEP8 / black / ruff violations",
tools=[FileReadTool()],
llm="gpt-4o-mini",
)
security_reviewer = Agent(
role="Security Reviewer",
goal="Flag OWASP top 10 issues",
tools=[FileReadTool(), CodeInterpreterTool()], # 可以跑 bandit
llm="gpt-4o",
)
design_reviewer = Agent(
role="Software Architect",
goal="Review design, suggest refactors",
tools=[DirectoryReadTool(directory="./src")],
llm="gpt-4o",
)
# 并发跑三个 Task
tasks = [
Task(description=f"Review {file} for style issues",
agent=style_checker, async_execution=True,
expected_output="List of style violations")
for file in changed_files
] + [
Task(description=f"Review {file} for security",
agent=security_reviewer, async_execution=True,
expected_output="List of security issues")
for file in changed_files
]
# 最后一个同步任务做汇总
summary_task = Task(
description="Combine all reviews into a GitHub PR comment",
agent=design_reviewer,
context=tasks, # 同步等所有异步 Task
expected_output="Markdown formatted PR review",
)
crew = Crew(agents=[style_checker, security_reviewer, design_reviewer],
tasks=tasks + [summary_task],
process=Process.sequential)
15.5 场景五:Flow 驱动的多阶段数据管道
from crewai.flow.flow import Flow, start, listen, router, and_
class PipelineState(BaseModel):
raw_data: list[dict] = []
cleaned: list[dict] = []
validated: bool = False
enriched: list[dict] = []
uploaded: bool = False
class ETLFlow(Flow[PipelineState]):
@start()
def extract(self):
self.state.raw_data = load_from_s3(...)
return len(self.state.raw_data)
@listen(extract)
def clean(self, n):
self.state.cleaned = [clean_row(r) for r in self.state.raw_data]
@listen(clean)
def validate(self):
self.state.validated = all(is_valid(r) for r in self.state.cleaned)
return self.state.validated
@router(validate)
def route(self):
return "enrich" if self.state.validated else "abort"
@listen("enrich")
def enrich(self):
# 这里调用一个 Crew 做 AI 标注
annotation_crew = build_annotation_crew()
result = annotation_crew.kickoff(inputs={"data": self.state.cleaned})
self.state.enriched = result.json_dict["rows"]
@listen("enrich")
def audit_log(self):
# 与 enrich 并行
write_audit(self.state.cleaned)
@listen(and_(enrich, audit_log)) # 两者都完成才继续
def upload(self):
upload_to_bq(self.state.enriched)
self.state.uploaded = True
@listen("abort")
def alert(self):
send_slack("ETL aborted due to validation failure")
flow = ETLFlow(persistence=SQLiteFlowPersistence(".etl.db"))
flow.kickoff()
15.6 场景六:HITL(Human-in-the-loop)审核链
from crewai.flow.human_feedback import HumanFeedback
class ApprovalFlow(Flow):
@start()
def draft(self):
# 用 Crew 生成草稿
return writing_crew.kickoff(inputs={"topic": self.state.topic}).raw
@listen(draft)
async def request_approval(self, draft_text):
feedback = await self.ask_human(
question=f"请审核以下草稿:\n{draft_text}\n\n批准吗?",
choices=["approve", "revise", "reject"],
timeout_minutes=60,
)
return feedback
@router(request_approval)
def decide(self):
match self._method_outputs[-1]:
case "approve": return "publish"
case "revise": return "revise"
case "reject": return "abort"
@listen("publish")
def publish(self): ...
@listen("revise")
def revise(self):
return self.draft() # 回到草稿
15.7 场景七:FastAPI 包装成 REST API
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
app = FastAPI()
class KickoffRequest(BaseModel):
topic: str
max_words: int = 1000
@app.post("/research")
async def research(req: KickoffRequest, bg: BackgroundTasks):
# 用 uuid 做 run_id,支持后续查询
run_id = str(uuid.uuid4())
crew = build_research_crew()
crew.checkpoint = CheckpointConfig(folder=f".runs/{run_id}")
# 异步跑,立即返回
bg.add_task(run_crew, crew, req.model_dump(), run_id)
return {"run_id": run_id, "status": "running"}
@app.get("/research/{run_id}")
def get_status(run_id: str):
return read_checkpoint(run_id)
# 流式版本
@app.post("/research/stream")
async def research_stream(req: KickoffRequest):
crew = build_research_crew()
crew.stream = True
async def gen():
output = crew.kickoff(inputs=req.model_dump())
async for chunk in output:
yield f"data: {json.dumps(chunk.model_dump())}\n\n"
return StreamingResponse(gen(), media_type="text/event-stream")
15.8 场景八:多租户 Memory 隔离
def build_crew_for_tenant(tenant_id: str) -> Crew:
return Crew(
agents=[...],
tasks=[...],
memory=Memory(
llm="gpt-4o-mini",
root_scope=f"tenant_{tenant_id}", # ← 关键
storage="lancedb",
),
knowledge=Knowledge(
collection_name=f"kb_{tenant_id}", # 每租户独立知识库
sources=[...],
),
)
LanceDB 底下会自动按 root_scope 前缀检索,租户间零泄漏。
15.9 场景九:接入自建 Ollama + 国产模型
from crewai import Agent, LLM
# 本地 Ollama
qwen = LLM(
model="ollama/qwen2.5:72b",
base_url="http://192.168.1.100:11434",
temperature=0.6,
num_ctx=32768,
)
# DeepSeek(OpenAI 协议)
deepseek = LLM(
model="deepseek/deepseek-chat",
api_key=os.environ["DEEPSEEK_KEY"],
temperature=0.3,
)
# 混合使用:主推理用 DeepSeek,工具调用用 Qwen 本地
agent = Agent(
role="...",
llm=deepseek,
function_calling_llm=qwen,
)
15.10 场景十:A/B 测试不同 Agent 配置
from crewai.flow.flow import Flow, start, listen
class ABFlow(Flow):
@start()
def experiment(self):
self.state.control_crew = build_crew(variant="A")
self.state.treatment_crew = build_crew(variant="B")
@listen(experiment)
def run_a(self):
return self.state.control_crew.kickoff(inputs=self.state.inputs)
@listen(experiment)
def run_b(self):
return self.state.treatment_crew.kickoff(inputs=self.state.inputs)
@listen(and_(run_a, run_b))
def compare(self):
a = self._method_outputs[-2]
b = self._method_outputs[-1]
# 用 LLMGuardrail 做裁判
judge = LLMGuardrail(
description="Which output is more complete and accurate? Return 'A' or 'B'",
llm="gpt-4o",
)
winner = judge(TaskOutput(raw=f"A:\n{a.raw}\n\nB:\n{b.raw}", ...))
log_experiment_result(winner)
十六、生产落地清单
16.1 性能
| 优化点 | 做法 |
|---|---|
| 降延迟 | 同类 Task 开 async_execution=True 并发;Manager 模型用 Opus,执行用 Haiku |
| 降成本 | function_calling_llm 用廉价模型;开 cache=True 缓存工具结果;用 Prompt Caching(Claude/OpenAI) |
| 并发 | Sequential 模式下,连续异步 Task 会同时 fire;Flow 天然按事件并发 |
| 限流 | Crew(max_rpm=60) 全局 RPM;LLM 层每个 provider 可单独设 rpm |
| 上下文 | 大任务切成小 Task,让 context 链式传递而不是一个 Task 吃进全部历史 |
| Streaming | Crew(stream=True) 返回 CrewStreamingOutput,可边跑边展示 |
16.2 成本
- 混合模型:
manager_llm=gpt-4o、agent.llm=gpt-4o-mini、function_calling_llm=gpt-4o-mini。 - Prompt Caching:Anthropic 的 cache_control / OpenAI 的 prompt_cache_key 都在 LiteLLM 里透明支持。对于固定的 backstory 尤其有效。
- Token 监控:监听
LLMCallCompletedEvent,把usage.total_tokens * price入库。 - Guardrails 短路:用
max_iter严格限制 ReAct 轮数,避免死循环烧钱。
16.3 安全
| 风险 | 缓解 |
|---|---|
| Prompt injection | 工具输出做 sanitize;把用户数据用明显边界(<user_input>...</user_input>)包起来 |
| 任意代码执行 | 用 CodeInterpreterTool 的 Docker 模式(unsafe_mode=False) |
| 凭据泄漏 | 工具 env_vars 字段声明需要的变量,框架会校验;绝不把 key 放进 prompt |
| 越权委托 | 不需要委托的 Agent 一律 allow_delegation=False |
| 多租户 | Memory.root_scope 强制隔离;Knowledge 用独立 collection |
| 指纹 | SecurityConfig 里的 Fingerprint 每次 kickoff 生成唯一 ID,便于审计 |
16.4 可观测
from crewai.events import crewai_event_bus
from crewai.events.types.llm_events import LLMCallCompletedEvent
from crewai.events.types.tool_usage_events import ToolUsageFinishedEvent, ToolUsageErrorEvent
import logging, json
logger = logging.getLogger("crewai")
@crewai_event_bus.on(LLMCallCompletedEvent)
def _log_llm(src, ev):
logger.info(json.dumps({
"type": "llm",
"model": ev.model,
"prompt_tokens": ev.usage.prompt_tokens,
"completion_tokens": ev.usage.completion_tokens,
"cost_usd": ev.cost,
"latency_ms": ev.latency_ms,
}))
@crewai_event_bus.on(ToolUsageFinishedEvent)
def _log_tool(src, ev):
logger.info(json.dumps({
"type": "tool", "name": ev.tool_name, "latency_ms": ev.latency_ms
}))
@crewai_event_bus.on(ToolUsageErrorEvent)
def _alert_tool_err(src, ev):
alert_slack(f"Tool {ev.tool_name} errored: {ev.error}")
推荐把以上日志推到:
- 本地开发:stdout + rich 彩色打印
- 预发:文件 + ELK
- 生产:OTLP → Jaeger/Tempo 做 trace,Prometheus 做 metric,Grafana 统一看板
CrewAI 还原生支持 AgentOps / Langfuse / Langtrace / OpenLLMetry,只要装对应 SDK 并在启动时 init() 即可自动接入。
16.5 部署
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN pip install uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen
COPY . .
CMD ["uv", "run", "crewai", "run"]
Kubernetes:
- 每个 Crew run 做成
Job,挂.checkpoints/PVC,失败重启会从断点续跑。 - Flow 做成
Deployment+Service,用 FastAPI 包装 kickoff。 - 用
HorizontalPodAutoscaler按 CPU/自定义指标(LLM 请求队列长度)扩容。
CrewAI Enterprise:crewai deploy 一键部署到官方托管环境,自带监控、限流、Secret 管理。
十七、关键源码文件速查表
结语
CrewAI 用非常"Python 原生"的方式同时解决了多 Agent 框架最头疼的三件事:
- 表达力:Crew(自主) + Flow(显式 DAG),上限高下限低。
- 工程化:Pydantic-first → 可序列化 / 可 checkpoint / 可 schema-validate;CLI + YAML 让脚手架和运维都有标准答案。
- 可扩展:Event Bus + LiteLLM + MCP + A2A 几条主干让第三方集成几乎零摩擦。
如果你现在正在为"用 LangGraph 还是 AutoGen 还是 CrewAI"纠结,这里有一条简单的决策树:
- 要角色扮演 + 自主协作,写起来像写小说 → CrewAI
- 要精确控制每一步 + 复杂状态机 → CrewAI Flow 或 LangGraph
- 要分布式、多进程、跨语言 Actor 模型 → AutoGen
- 要单个 ReAct Agent 就够,轻量 → CrewAI LiteAgent / Pydantic AI
本文涉及的所有源码路径都可直接点击跳转到本地 clone 的 crewAI 仓库,推荐配合 VS Code + Python Language Server 边读边跳转类型定义。Happy crewing!