crewai CLI:从 create 到 deploy 的完整生命周期
59 天我们都在读"库"(怎么定义 Agent/Task/Crew、怎么执行)。但一个真实项目还需要"工具链":怎么创建项目骨架、跑起来、训练、回放调试、测试、重置记忆、部署上线。这就是 crewai 命令行。今天读 crewai_cli:用 click 组织的命令组、create 怎么生成 @CrewBase 脚手架、run 怎么在 uv 子进程里跑你的 Crew、train/replay/test 各自的角色、以及 CLI 为什么被拆成独立包、crewai.cli 的兼容 shim 怎么工作。学完你就掌握了 CrewAI 项目的完整落地路径。
create 是打地基放线(生成项目结构)、run 是通水通电(真跑起来)、train 是精装调试(让 Agent 学得更好)、replay 是回看监控录像(从某一步重跑)、test 是竣工验收、deploy 是交房入住(上线)。而且这套工具箱有意思——它自己不干活,而是调 uv run 在你项目的独立环境里干活,就像工头不亲自砌墙,而是指挥工人在工地干。痛点:库写好了,怎么变成能交付的项目
Agent/Task/Crew,可写个 .py 手动 python main.py 跑,离"产品"还很远:项目结构怎么规范(配置放哪、依赖怎么锁)?团队协作怎么统一环境?怎么快速试不同 LLM provider?训练出的参数怎么保存、怎么从中间步骤重跑调试、怎么部署到云上让别人调用?这些"工程化"的活,不该每个人自己发明一遍。crewai CLI 用 click 把一组子命令组织成一棵命令树,覆盖项目全生命周期:create(脚手架)→ install(装依赖)→ run(执行)→ train/replay/test(调优与验证)→ reset-memories(清状态)→ deploy(上线)。核心设计:CLI 本身只做"参数解析 + 组装命令",真正的执行统统交给 uv run 在你项目的隔离环境里跑——CLI 与你的运行环境解耦。| 命令 | 作用 | 本质 |
|---|---|---|
crewai create crew X | 生成项目骨架 | 拷模板 + 改名字 |
crewai run | 跑 Crew/Flow | uv run run_crew 子进程 |
crewai train N -f x.pkl | 训练 N 轮存参数 | uv run train |
crewai replay -t <id> | 从某任务重跑 | 用 task_id 定位 |
crewai test | 评测 | uv run test |
crewai deploy create | 部署到 AMP | 调云端 API |
click 命令组:一棵命令树
顶层是一个 click group,子命令挂在它下面(crewai_cli/cli.py:101):
# crewai_cli/cli.py:101
@click.group()
@click.version_option(_get_cli_version()) # crewai --version
def crewai() -> None:
"""Top-level command group for crewai."""
# :138 一个子命令的样子
@crewai.command()
@click.argument("type", required=False, default=None, type=click.Choice(["crew", "flow"]))
@click.argument("name", required=False, default=None)
@click.option("--provider", type=str, help="The provider to use for the crew")
@click.option("--classic", is_flag=True, help="Use classic Python/YAML project structure")
def create(type, name, provider, skip_provider=False, classic=False, declarative=False):
"""Create a new crew, or flow."""
...
入口在 pyproject 里声明(lib/crewai/pyproject.toml:147):
# pyproject.toml:147
[project.scripts]
crewai = "crewai_cli.cli:crewai" # 命令 crewai → 指向 crewai() 这个 group
@click.group()把 crewai 变成一个"命令组"——它本身不干活,而是能挂子命令。crewai create/crewai run 都是它的孩子。@click.version_option自动加 --version。click 帮你处理这些通用脚手架,不用手写。@click.argument / @click.option声明式定义位置参数和选项。type=click.Choice([...]) 限定取值,输错自动报错并提示。[project.scripts]★装包时,pip/uv 会据此生成一个叫 crewai 的可执行文件,运行它就调 crewai_cli.cli:crewai。这就是"为什么终端能敲 crewai"。click 用装饰器声明式地描述"这个命令有哪些参数",自动生成 help、自动校验、自动分发。让加一个新命令只需写一个带装饰器的函数,可维护性和一致性都远胜手写。create:生成 @CrewBase 脚手架
create 支持 crew/flow、经典/JSON/声明式多种模板(crewai_cli/cli.py:155):
# crewai_cli/cli.py:155
def create(type, name, provider, skip_provider=False, classic=False, declarative=False):
"""Create a new crew, or flow."""
dmn_mode = is_dmn_mode_enabled()
if not type:
if dmn_mode:
raise click.UsageError("TYPE is required when CREWAI_DMN is set. ...")
... # 交互式补问 type/name
# 最终委托给 create_crew.py / create_flow.py 拷贝模板、替换项目名
生成的结构正是 Day 55 学的 @CrewBase 布局:
my_crew/
├── pyproject.toml # 依赖 + [project.scripts] run_crew/train/...
├── src/my_crew/
│ ├── crew.py # @CrewBase 类:@agent/@task/@crew
│ ├── main.py # 入口:kickoff()
│ └── config/
│ ├── agents.yaml # 角色/目标/背景(Day 55 的花名册)
│ └── tasks.yaml # 任务描述(任务单)
type = crew | flow两种项目形态:Crew(Day 19 的团队编排)或 Flow(阶段7 事件驱动)。用 click.Choice 限定,选错即报错。--provider选 LLM 供应商(OpenAI/Anthropic/...)。生成时把对应配置写进模板,省得你手配。--classic / --declarative经典 Python/YAML 结构 vs 声明式/JSON 结构。不同团队偏好不同——CLI 都给你留了模板。交互式补问没传 type/name 时(非 DMN 模式)会交互询问。对新手友好;DMN(自动化)模式则强制显式传参、不交互。create 的本质就是"拷一份模板项目、把里面的占位名替换成你的项目名"。它不神奇,但它把 Day 55 那套 @CrewBase + YAML 的最佳实践固化成默认起点——你不用从零想"项目该怎么摆"。run:CLI 不亲自跑,交给 uv 子进程
run 命令解析参数后转交 run_crew(crewai_cli/cli.py:523):
# crewai_cli/cli.py:523
def run(trained_agents_file, definition, inputs):
"""Run the Crew or Flow."""
if trained_agents_file is not None and definition is not None:
raise click.UsageError("--filename can only be used when running crews")
run_crew(trained_agents_file=trained_agents_file, definition=definition, inputs=inputs)
而真正执行是在 uv 环境里跑一个脚本(crewai_cli/run_crew.py:740):
# crewai_cli/run_crew.py:740
def _execute_uv_script(script_name, *, entity_type, trained_agents_file=None):
"""Execute a project script through uv."""
command = ["uv", "run", script_name] # 例:uv run run_crew
env = build_env_with_all_tool_credentials() # 注入工具鉴权环境变量
if trained_agents_file:
env[CREWAI_TRAINED_AGENTS_FILE_ENV] = trained_agents_file
try:
subprocess.run(command, capture_output=False, text=True, check=True, env=env) # ★子进程
except subprocess.CalledProcessError as e:
_handle_run_error(e, entity_type) # 转成友好错误提示
except Exception as e:
click.echo(f"An unexpected error occurred: {e}", err=True)
run 只做参数校验比如"--filename 只能配合 crew 用"这种互斥检查。真活儿它不干,转交 run_crew。职责单一。["uv", "run", script_name]★核心:CLI 起一个 uv 子进程跑你项目的 run_crew 脚本(pyproject 里定义的入口)。你的 Crew 在项目自己的虚拟环境里执行。build_env_with_all_tool_credentials把各种工具的鉴权(API key 等)作为环境变量注入子进程。凭据通过 env 传递,不落代码。capture_output=False不捕获输出 → 子进程的 stdout/stderr 直接透传到你终端。所以 verbose 日志你能实时看到。check=True + 异常处理子进程非零退出就抛 CalledProcessError,转成友好提示(如"没装 crewai 包")。uv tool install),而你的项目有自己锁定的依赖版本——两者极易依赖冲突(CLI 要 pydantic v2,你项目锁了别的版本…)。子进程 + uv run:让 Crew 在项目自己的隔离环境里跑,CLI 只当"启动器"。代价是多一次进程启动开销、输出要透传,但换来了"CLI 全局装一次、各项目环境互不干扰"。隔离性 > 一点点性能——生产工具的正确选择。train / replay / test:调优与验证三兄弟
train 也是起 uv 子进程,但多了参数校验(crewai_cli/train_crew.py:6):
# crewai_cli/train_crew.py:6
def train_crew(n_iterations: int, filename: str) -> None:
"""Train the crew by running a command in the UV environment."""
command = ["uv", "run", "train", str(n_iterations), filename]
try:
if n_iterations <= 0:
raise ValueError("The number of iterations must be a positive integer.")
if not filename.endswith(".pkl"):
raise ValueError("The filename must not end with .pkl") # 注意这条校验(见边界)
result = subprocess.run(command, capture_output=False, text=True, check=True)
...
except subprocess.CalledProcessError as e:
click.echo(f"An error occurred while training the crew: {e}", err=True)
replay 用 task_id 从中间步骤重跑(crewai_cli/cli.py:274):
# crewai_cli/cli.py:254
@crewai.command()
@click.option("-t", "--task_id", ...) # 从哪个任务重放
@click.option("-f", "--trained_agents_file", ...)
def replay(task_id, trained_agents_file):
"""Replay the crew execution from a specific task."""
...
train N filename.pkl跑 N 轮,把学到的建议/参数存进 pkl 文件(Day 24 训练)。之后 run/replay 可用 -f 加载它。n_iterations <= 0 校验训练轮数必须为正。CLI 层就拦下无意义输入,不用等子进程跑起来才报错。replay -t <task_id>★调试神器:Crew 跑到一半某步出错,不用从头再跑(贵!),用 task_id 从那一步重放。呼应 Day 24 replay。test(:477) 跑 N 轮评测、给 Crew 表现打分。上线前的"竣工验收"。👶 小白:train 和 test 有啥区别?
👨🏫 老师:train 是"让 Agent 变得更好"——多轮跑 + 人工反馈,把经验存成 pkl,下次带着经验干活。test 是"考核 Agent 现在多好"——不改它,只跑几轮打分评估。一个是练,一个是考。replay 则是"从录像的某一帧继续"——调 bug 用,避免重跑整条流水线。
reset-memories 与 deploy:清状态与上线
reset-memories 用一堆 flag 精细控制清什么(crewai_cli/cli.py:311):
# crewai_cli/cli.py:311
@crewai.command()
@click.option("-m", "--memory", is_flag=True, help="Reset MEMORY")
@click.option("-kn", "--knowledge", is_flag=True, help="Reset KNOWLEDGE storage")
@click.option("-a", "--all", is_flag=True, help="Reset ALL memories")
def reset_memories(...): # :342
"""Reset the crew memories (long, short, entity, ...)."""
deploy 本身又是一个子命令组(crewai_cli/cli.py:572):
# crewai_cli/cli.py:572
@crewai.group()
def deploy() -> None:
"""Deploy the Crew CLI group."""
@deploy.command(name="create") # :577 crewai deploy create
@click.option("-y", "--yes", is_flag=True, help="Skip the confirmation prompt")
@click.option("--skip-validate", is_flag=True, help="Skip the pre-deploy validation checks.")
def deploy_create(...):
... # 打包 + 调 CrewAI AMP 云端 API 部署
reset-memories 分类 flag短期/长期/实体记忆、知识库分开清。因为"清全部"太粗暴——调试时你可能只想清短期记忆保留知识库。@crewai.group() 嵌套★deploy 自己又是 group,下面挂 create/list/logs 等。命令树可以多层——把相关操作聚成子命名空间。login / logout(:549/:560) 部署前要登录 CrewAI AMP(云平台)。CLI 管理 token(TokenManager),凭据本地存。--skip-validate部署前默认做校验(配置齐不齐、能不能跑)。老手可跳过,但默认帮你把关。CLI 被拆成独立包 + 兼容 shim
CLI 从 crewai.cli 搬到了独立包 crewai_cli,老导入靠一个 import shim 兜住(crewai/cli/__init__.py):
# crewai/src/crewai/cli/__init__.py
"""Deprecated: use ``crewai_cli`` instead.
The CLI was extracted into the standalone ``crewai-cli`` package. Legacy
``from crewai.cli.X import Y`` imports are intercepted here and resolved to
the corresponding ``crewai_cli.X`` module so downstream code keeps working."""
_PREFIX = "crewai.cli"; _TARGET = "crewai_cli"
warnings.warn("crewai.cli is deprecated; import from crewai_cli instead.", DeprecationWarning)
class _ShimFinder(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path, target=None):
if fullname != _PREFIX and not fullname.startswith(_PREFIX + "."):
return None
mapped = _TARGET + fullname[len(_PREFIX):] # crewai.cli.X → crewai_cli.X
module = importlib.import_module(mapped)
... # 造一个 spec 指向已导入的 crewai_cli 子模块
_finder = _ShimFinder()
if _finder not in sys.meta_path:
sys.meta_path.insert(0, _finder) # 装进 import 系统的查找链
拆成独立包把体积大、依赖多(click、TUI、部署 API)的 CLI 从核心库剥离。想用库的人不必被迫装一堆 CLI 依赖。MetaPathFinder shim★老代码 from crewai.cli.X import Y 不会立刻崩——这个自定义 finder 拦截该导入、重定向到 crewai_cli.X。平滑迁移。DeprecationWarning重定向的同时打警告,提醒开发者改成新导入。兼容但不纵容:给迁移窗口,也给明确信号。sys.meta_path.insert(0)把 finder 插到导入查找链最前面,抢在默认机制前处理 crewai.cli.*。这是 Python import 系统的高级扩展点。from crewai.cli... 的老项目/教程/第三方代码立刻全崩——破坏性升级,用户骂声一片。写 shim:老导入继续能用(只是打个 deprecation 警告),给生态一个平滑迁移期。代价是多维护一段 import 魔法、以及"两条路径并存"的短期复杂。这是成熟库的通用做法——向后兼容是承诺,破坏性变更要给迁移路径。呼应 Day 58 里 A2AConfig 标 Deprecated 却仍保留的同一原则。边界 + 今日小结
train_crew(train_crew.py:19)里写着 if not filename.endswith(".pkl"): raise ValueError("The filename must not end with .pkl")——条件和错误信息读起来"反直觉"(判断"不以 .pkl 结尾"却说"不能以 .pkl 结尾")。实际语义是要求你传的名字符合约定。读源码遇到这种"信息像是拧过"的地方,别急着照字面理解,要以运行时行为为准——真实 CLI 会告诉你正确格式。这也提醒:错误文案是最容易和逻辑脱节的地方。crewai run 报"没装 crewai 包"
CLI(crewai-cli)可以单独 uv tool install,但它不含运行 Crew 所需的 crewai 核心库。run_crew.py(:40)专门准备了提示:uv tool install --force 'crewai[tools]',并提醒 zsh 下要加引号(否则 crewai[tools] 被当成通配符)。CLI 装了 ≠ 能跑 Crew——run 依赖项目环境里有完整 crewai。这是"CLI 与运行环境解耦"的副作用,第一次跑常踩。🧠 今天你应该能回答
- crewai CLI 覆盖了项目哪些生命周期阶段?
- 为什么终端能敲
crewai?([project.scripts] 入口) - 为什么 CLI 起
uv run子进程而不是直接 import 执行? - train / test / replay 各自解决什么问题?
- deploy 为什么是个子 group?命令树能几层?
- CLI 为什么拆成独立包?import shim 怎么兼容老导入?
✋ 10 分钟动手
P=lib/cli/src/crewai_cli
sed -n '101,170p' $P/cli.py # group + create 骨架
sed -n '497,575p' $P/cli.py # run / update / login / deploy group
sed -n '740,768p' $P/run_crew.py # _execute_uv_script 子进程执行
sed -n '6,33p' $P/train_crew.py # train 参数校验 + subprocess
sed -n '1,75p' ../../crewai/src/crewai/cli/__init__.py # 兼容 shim
# 亲手走一遍生命周期
crewai create crew demo && cd demo && crewai install && crewai run