导语
上一篇在这里:《探索 CrewAI——简介、安装和测试(1)》
上一篇,我们已经通过如下命令,创建了一个新的项目工程。
crewai create crew my_ai_crew
并且,通过下面的命令,测试运行成功。
# 进入工程所在目录
cd my_ai_crew
# 运行Agent
crewai run
接下来,看一下当我们执行crewai run
来运行Agent的时候,究竟发生了什么。
先说结论
- 执行这个脚本:/root/miniconda3/envs/CrewAI/bin/crewai
- 执行这个脚本里的run函数:/root/miniconda3/envs/CrewAI/lib/python3.10/site-packages/crewai/cli/cli.py
- 执行这个脚步里的 run_crew 函数:/root/miniconda3/envs/CrewAI/lib/python3.10/site-packages/crewai/cli/run_crew.py
- 通过UV,根据项目文件 my_ai_crew/pyproject.toml,调用 my_ai_crew.main:run
- 最终执行到 my_ai_crew/src/my_ai_crew/main.py 里的run函数
- 调用 MyAiCrew().crew().kickoff(inputs=inputs) ,创建Agents/Tasks/Crew等,完成调用。
下面是详细的分析过程。
关于 crewai 命令
第一步,查看 crewai 这个命令行工具在哪里。
which crewai
# 输出 /root/miniconda3/envs/CrewAI/bin/crewai
可以看到,这个工具位于conda虚拟环境“CrewAI”中。这个是上一篇介绍过的,通过conda命令新增的虚拟环境。
第二步,看看 crewai 里的代码逻辑。
打开这个文件:/root/miniconda3/envs/CrewAI/bin/crewai ,可以看到内容如下:
注:下面的①②③是我手动添加到,为了方便后续的解读。
#!/root/miniconda3/envs/CrewAI/bin/python
# -*- coding: utf-8 -*-
import re
import sys
## ①
from crewai.cli.cli import crewai
if __name__ == '__main__':
## ②
sys.argv[0] = re.sub(r'(-script.pyw|.exe)?$', '', sys.argv[0])
## ③
sys.exit(crewai())
①、导入语句
这个导入语句 from crewai.cli.cli import crewai
表示从之前安装的crewai包的命令行接口模块(cli.py)中导入主命令函数 crewai。
那么,crewai包的安装路径在哪里?可以通过下面命令来查看。
pip show crewai | grep -aw "^Location" --color
# 输出如下
# Location: /root/miniconda3/envs/CrewAI/lib/python3.10/site-packages
结合上述信息可得知,最终是从下面的cli.py 中导入的。
/root/miniconda3/envs/CrewAI/lib/python3.10/site-packages/crewai/cli/cli.py
其中:
- crewai - 主包名
- cli - 子包名(第一个)
- cli - 模块名(第二个,即 cli.py 文件)
- crewai - 从 cli.py 文件中导入的函数名
打开文件查看详情,可以看到有 crewai() 函数的定义。
②、规范化命令行参数。
sys.argv[0] = re.sub(...)
这行代码的作用是清理脚本文件名(sys.argv[0]
),移除可能存在的跟平台相关的特定后缀(如 -script.pyw
或 .exe
),这样的目的是,确保在不同操作系统上保持一致的文件名。
以下是几个简单的示例:
# 可能的输入和处理后的输出:
"crewai-script.pyw" -> "crewai"
"crewai.exe" -> "crewai"
"crewai" -> "crewai"
这样做有几个好处:
-
跨平台兼容性
- Windows 上可能有 .exe 后缀
- Python 包装脚本可能有 -script.pyw 后缀
-
标准化脚本名称
- 移除不同平台特定的后缀
- 保持脚本名称的一致性
-
避免问题
- 某些程序可能依赖脚本名称
- 后缀可能导致路径解析问题
③、调用 cli.py 中的 crewai() 函数
当这个脚本/root/miniconda3/envs/CrewAI/bin/crewai
调用 crewai() 函数时,会调用到 cli.py 里对应的函数。
cli.py 里,使用了 Click 框架,新增了命令组 crewai(),并且注册了很多子命令。如下所示:
命令组核心内容:
@click.group()
@click.version_option(get_version("crewai"))
def crewai():
"""Top-level command group for crewai."""
注册的多个子命令:
@crewai.command()
def create(...): # 创建 crew 或 flow
@crewai.command()
def version(...): # 显示版本信息
@crewai.command()
def train(...): # 训练 crew
@crewai.command()
def run(...): # 运行 crew
# ... 其他子命令
所以,当我们在命令行执行crewai run
时,Click 框架解析命令行参数(得到 run)然后会执行 cli.py 里的其中一个子命令run
@crewai.command()
def run():
"""Run the Crew."""
click.echo(f"Running the Crew")
run_crew() # 会调用这个函数
进一步跟踪,发现 run_crew()
函数是在这个文件定义的:
# /root/miniconda3/envs/CrewAI/lib/python3.10/site-packages/crewai/cli/run_crew.py
# 为方便理解,下面的逻辑有简化
def run_crew() -> None:
command = ["uv", "run", "run_crew"]
pyproject_data = read_toml() # 读取 pyproject.toml 配置文件
"""在 UV 环境中运行 crew"""
subprocess.run(command, capture_output=False, text=True, check=True)
uv: UV 包管理器的命令
run: UV 的子命令,用于运行 Python 脚本
run_crew: 要执行的具体脚本名
实际执行效果,相当于直接在命令行中执行:
# 相当于在终端执行:
uv run run_crew
那么,上面的命令又到底干了什么呢?跟项目根目录的文件 pyproject.toml 有关。打开这个项目配置文件看看:
# cat pyproject.toml
[project.scripts]
my_ai_crew = "my_ai_crew.main:run"
run_crew = "my_ai_crew.main:run"
train = "my_ai_crew.main:train"
replay = "my_ai_crew.main:replay"
test = "my_ai_crew.main:test"
可以看看第4行run_crew的配置,可以得知执行uv run run_crew
时,会调用 my_ai_crew 目录下的 main.py
文件的 run
函数。
可以打开 main.py 文件进一步看看,发生了什么事情。
main.py
cat study_crewai/my_ai_crew/src/my_ai_crew/main.py
可以看到
def run():
"""
Run the crew.
"""
inputs = {
'topic': 'AI LLMs',
'current_year': str(datetime.now().year)
}
try:
MyAiCrew().crew().kickoff(inputs=inputs)
except Exception as e:
raise Exception(f"An error occurred while running the crew: {e}")
这里的逻辑很简单,创建了一个 MyAiCrew 实例,获取其关联的 crew(AI 代理团队),并使用提供的 inputs 参数启动(kickoff)这个团队开始执行任务。
进一步查看里面的实现
# my_ai_crew/src/my_ai_crew/crew.py
@CrewBase
class MyAiCrew():
"""MyAiCrew crew"""
# 以下几个函数省略具体的实现
@agent
def researcher(self) -> Agent:
@agent
def reporting_analyst(self) -> Agent:
@task
def research_task(self) -> Task:
@task
def reporting_task(self) -> Task:
@crew
def crew(self) -> Crew:
"""Creates the MyAiCrew crew"""
# To learn how to add knowledge sources to your crew, check out the documentation:
# https://docs.crewai.com/concepts/knowledge#what-is-knowledge
return Crew(
agents=self.agents, # Automatically created by the @agent decorator
tasks=self.tasks, # Automatically created by the @task decorator
process=Process.sequential,
verbose=True,
# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/
)
简单来说:
这个文件定义了一个 MyAiCrew
类,它是基于 CrewAI 框架的来实现。并且通过装饰器 @CrewBase
、@agent
、@task
和 @crew
,它分别定义了研究员(researcher)和分析师(reporting_analyst)两个 AI 代理,以及相应的研究任务和报告任务。
这个类的核心在于通过 YAML 配置文件(agents.yaml
和 tasks.yaml
)来管理代理和任务的具体设置,并在 crew()
方法中将它们组装成一个完整的工作流。当调用 kickoff()
时,代理们会按照 Process.sequential
(顺序执行)的方式完成指定的任务,整个过程支持详细的日志输出(verbose=True
)。
总结一下
到此,入口调用链路很清晰了。
总结一下:当用户执行 crewai run
时:
- 执行这个脚本:/root/miniconda3/envs/CrewAI/bin/crewai
- 执行这个脚本里的run函数:/root/miniconda3/envs/CrewAI/lib/python3.10/site-packages/crewai/cli/cli.py
- 执行这个脚步里的 run_crew 函数:/root/miniconda3/envs/CrewAI/lib/python3.10/site-packages/crewai/cli/run_crew.py
- 通过UV,根据项目文件 my_ai_crew/pyproject.toml,调用 my_ai_crew.main:run
- 最终执行到 my_ai_crew/src/my_ai_crew/main.py 里的run函数
- 调用 MyAiCrew().crew().kickoff(inputs=inputs) ,创建Agents/Tasks/Crew等,完成调用。
下一步
下一篇,来探索一下,crewai框架是如何调用大模型LLM来交互和决策的。 敬请期待。