CrewAI 源码深度剖析与生产应用教程

2 阅读32分钟

基于仓库 crewAIInc/crewAI 主干源码(版本 1.14.3a3,位于 lib/crewai/src/crewai/),从最底层的 Agent 执行循环、事件总线与 LLM 抽象,一路向上拆到 Crew 编排、Flow 声明式工作流、内存/知识系统,并附 10+ 个端到端生产落地场景。

CrewAI 是当前社区里最"Pythonic"的多 Agent 编排框架:零 LangChain 依赖(1.0 版本彻底解耦)、Pydantic-first、自带 CLI 脚手架、同时提供角色驱动的 CrewDAG 式的 Flow 两种互补范式。本文会把这套设计的每一层拆开讲清楚。


目录

  1. 项目全景与设计哲学
  2. 核心抽象关系图
  3. Agent 源码剖析
  4. Task 源码剖析
  5. Crew 编排源码剖析
  6. CrewAgentExecutor:ReAct 与原生 ToolCall 双循环
  7. LLM 层与 LiteLLM 集成
  8. Tools 工具系统
  9. Flow 声明式工作流框架
  10. Memory 统一记忆系统
  11. Knowledge 知识库(RAG)
  12. Events 事件总线与可观测性
  13. Checkpoint 持久化与断点续跑
  14. CLI 脚手架与项目结构
  15. 生产应用教程(10 个端到端场景)
  16. 生产落地清单(性能、成本、安全、可观测)
  17. 关键源码文件速查表

一、项目全景与设计哲学

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] 对象 + 持久化
代表类AgentTaskCrewFlow[T]@start@listen@router

两者的关系:Flow 里可以把一个 Crew.kickoff() 当成一个节点调用,Crew 里也可以触发 Flow;Flow 负责"大粒度的宏观流程",Crew 负责"小粒度的智能协作"。

1.3 核心设计哲学

  1. Pydantic-firstAgentTaskCrewFlow 全是 BaseModel,天然可序列化、可校验、可检查点。
  2. Event-driven:运行时的每一步(Agent 启动、LLM 调用、Tool 调用、Task 完成)都通过单例 crewai_event_bus 广播,为监控、日志、重放提供统一埋点。
  3. Provider-agnostic LLMllm.py 是对 litellm 的包装,同一套 API 覆盖 20+ 供应商(OpenAI / Anthropic / Gemini / Bedrock / Azure / Ollama / DeepSeek / Groq…)。
  4. Tool = Pydantic schema + callable:工具自动生成 args_schema,既能喂给原生 ToolCall 也能喂给 ReAct 文本解析。
  5. 零 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 (向量检索)

关键类 & 文件索引

抽象文件行数职责
Agentagent/core.py1884自主 Agent:role/goal/backstory、工具调用、委托
Tasktask.py2000+可执行单元:描述、预期输出、guardrail、同步/异步
Crewcrew.py2298多 Agent 编排:sequential / hierarchical
Processprocess.py12枚举:sequential / hierarchical
Flowflow/flow.py3465声明式 DAG + 状态机
LLMllm.py2558LiteLLM 包装器
CrewAgentExecutoragents/crew_agent_executor.py1617ReAct / 原生 ToolCall 双循环
Memorymemory/unified_memory.py400+LLM 驱动的统一记忆
Knowledgeknowledge/knowledge.py119RAG 知识源
BaseTooltools/base_tool.py400+工具基类 + 注册表
CrewAIEventsBusevents/event_bus.py800+单例事件总线

三、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-818concurrent.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 的另一 Agent
  • Ask 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 关键字段

crew.py:158-350

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() 完整流程

crew.py:899-988

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 流程

crew.py:1397

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 流程

crew.py:1401-1431

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

工作机制

  1. Manager 不在 self.agents 里,是单独创建的"元 Agent"。
  2. 所有 Task 的 agent 字段都会被动态替换成 manager。
  3. Manager 自己不干活,通过 Delegate work to coworker 工具把任务拆给真正的执行者。
  4. Manager 的 backstory 里明确告诉它:"你不要自己回答,要委托给最合适的同事"。

5.5 Planning(可选前置规划)

Crew(planning=True) 时,prepare_kickoff 里会先用 CrewPlannerutilities/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

  1. 把每个 BaseTool 转成 OpenAI function schema(tools/agent_tools/tool_calling.py)。
  2. LLM 返回结构化的 tool_calls 数组,无需正则解析。
  3. 每个 tool_call 独立执行,结果通过 role: tool 消息回灌。
  4. 如果 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 需要做这些额外的事:

  1. 统一的上下文窗口管理(不同模型不同大小)
  2. 自动 fallback 模型
  3. 事件发射LLMCallStartedEvent / LLMCallCompletedEvent
  4. 结构化输出适配(OpenAI json_mode / Anthropic tool / Gemini responseSchema 的差异)
  5. 钩子注入点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 传给 OpenAI response_format / Anthropic tools / Gemini responseSchema,由厂商保证语法合法性,通常更可靠。

八、Tools 工具系统

8.1 BaseTool

tools/base_tool.py:102+

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_schemadescription

方式 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:SerperDevToolWebsiteSearchToolFirecrawlScrapeWebsiteTool
  • 文件:FileReadToolDirectoryReadToolPDFSearchToolDOCXSearchTool
  • RAG:RagToolCSVSearchToolJSONSearchToolXMLSearchTool
  • 代码:CodeInterpreterTool(Docker 沙箱)、GithubSearchTool
  • 数据库:MySQLSearchToolPGSearchTool

使用:

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"三分法,引入 UnifiedMemorymemory/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,尤其是异步场景")

内部:

  1. 调用 LLM 分析这段文本 → 推断 scope("user.preferences.languages")、category、importance、tags
  2. 生成 embedding
  3. 查询相似已有记忆,若相似度 > 0.85 则合并
  4. 写入 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 源类型

knowledge/source/ 提供:

  • TextFileKnowledgeSource
  • PDFKnowledgeSource
  • CSVKnowledgeSource
  • JSONKnowledgeSource
  • ExcelKnowledgeSource
  • CrewDoclingSource(自动识别格式)
  • StringKnowledgeSource(直接传字符串)

所有源都继承 BaseKnowledgeSource,需实现 load_content()

11.3 执行流程

当 Task 执行时,agent.handle_knowledge_retrieval() 会:

  1. task.description 作为 query
  2. knowledge.query(queries=[query], results_limit=3, score_threshold=0.35)
  3. 拿到相关 chunk 后拼到 prompt 前面作为上下文

十二、Events 事件总线与可观测性

12.1 单例设计

events/event_bus.py

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 事件类型

events/types/

  • agent_events.py - AgentExecutionStartedEventAgentExecutionCompletedEventAgentExecutionErrorEvent
  • crew_events.py - CrewKickoffStartedEventCrewKickoffCompletedEvent
  • task_events.py - TaskStartedEventTaskCompletedEventTaskFailedEvent
  • llm_events.py - LLMCallStartedEventLLMCallCompletedEventLLMStreamChunkEvent
  • tool_usage_events.py - ToolUsageStartedEventToolUsageFinishedEventToolUsageErrorEvent
  • memory_events.pyknowledge_events.pyflow_events.pyskill_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 installuv 装依赖
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 triggersWebhook 触发器管理

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 吃进全部历史
StreamingCrew(stream=True) 返回 CrewStreamingOutput,可边跑边展示

16.2 成本

  1. 混合模型manager_llm=gpt-4oagent.llm=gpt-4o-minifunction_calling_llm=gpt-4o-mini
  2. Prompt Caching:Anthropic 的 cache_control / OpenAI 的 prompt_cache_key 都在 LiteLLM 里透明支持。对于固定的 backstory 尤其有效。
  3. Token 监控:监听 LLMCallCompletedEvent,把 usage.total_tokens * price 入库。
  4. 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 Enterprisecrewai deploy 一键部署到官方托管环境,自带监控、限流、Secret 管理。


十七、关键源码文件速查表

类别文件行数备注
顶层导出init.py206懒加载 Memory
Agentagent/core.py1884Agent 主类
agents/agent_builder/base_agent.py抽象基类
lite_agent.py轻量变体
Executoragents/crew_agent_executor.py1617ReAct/原生双循环
agents/parser.pyReAct 输出解析
agents/tools_handler.py工具执行器
experimental/agent_executor.py下一代 Executor
Tasktask.py2000+Task 主类
tasks/task_output.py100+输出数据结构
tasks/conditional_task.py条件 Task
tasks/llm_guardrail.pyLLM 裁判
Crewcrew.py2298编排主类
process.py12Process 枚举
crews/crew_output.pyCrewOutput
Flowflow/flow.py3465Flow 主类
flow/flow_wrappers.py500+装饰器
flow/flow_config.py配置
flow/persistence/持久化
flow/visualization/可视化
LLMllm.py2558LiteLLM 包装
llms/base_llm.pyLLM 基类
hooks/llm_hooks.pyLLM 钩子
Toolstools/base_tool.py400+BaseTool
tools/structured_tool.py@tool 装饰器
tools/agent_tools/agent_tools.py委托工具
Memorymemory/unified_memory.py400+UnifiedMemory
memory/recall_flow.py检索流程
memory/analyze.pyLLM 分析
memory/storage/存储后端
Knowledgeknowledge/knowledge.py119主类
knowledge/source/各种 source
Eventsevents/event_bus.py800+事件总线
events/types/事件类型
Statestate/checkpoint_config.pyCheckpoint 配置
CLIcli/cli.py500+CLI 入口
cli/create_crew.pyCrew 脚手架
cli/create_flow.pyFlow 脚手架
Projectproject/@CrewBase 装饰器
Securitysecurity/Fingerprint 等
MCPmcp/MCP 客户端
A2Aa2a/Agent-to-Agent 协议
Telemetrytelemetry/OTLP 埋点
Testslib/crewai/tests/100K+覆盖所有模块

结语

CrewAI 用非常"Python 原生"的方式同时解决了多 Agent 框架最头疼的三件事:

  1. 表达力:Crew(自主) + Flow(显式 DAG),上限高下限低。
  2. 工程化:Pydantic-first → 可序列化 / 可 checkpoint / 可 schema-validate;CLI + YAML 让脚手架和运维都有标准答案。
  3. 可扩展: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!