前阵子我想让AI帮我产出一份比较完整的市场调研报告,单开一个对话框问模型,出来的内容总是“全而不深”。后来我换了个思路:不再让一个模型同时干三份活,而是把工作拆成三个人来干——一个找资料、一个写初稿、一个做校对。效果一下子就好了很多。这个思路落地的工具,就是标题里写的CrewAI。
CrewAI是一个用Python写的多智能体编排框架,核心就是让你能用代码定义一个“数字小团队”,给每个成员分配不同的角色、任务和协作规则,然后让它自动跑完一整条工作流水线。这篇文章我不讲那种动辄上千行的架构设计,就围绕“角色分配”这件事,带你从零跑起来第一个多智能体项目。适合完全没写过智能体的新手,也适合已经手动调过Prompt、想往多智能体方向走一点的人。
1. 为什么单个提示词搞不定的活儿,要拆给多个智能体干
1.1 一个Prompt包打天下,卡在哪儿
你可以试一下,让模型“帮我调研一下新能源行业,然后写一篇有数据、有观点的分析文章”。这种看似合理的Prompt,实际跑出来通常有两个毛病:
第一,它会把“调研”和“写作”混在一次推理里完成。模型一边在脑子里找知识,一边已经在组织文章结构,结果就是数据没有出处、观点没有论证链条,整篇文章像是把脑海里所有相关词句串了一遍。
第二,没有一个独立的“校对角色”来检查逻辑漏洞。单个模型完成任务就收工了,它不会回头质疑自己:这个结论和前面引用的数据矛盾了怎么办?第二段和第四段说的是同一个意思怎么办?
这不是模型不够聪明,而是我们在用一个模型承担三个岗位。任何一个正常人被要求“一边查资料一边写稿一边质检”,表现都会打折扣,大模型也一样。
1.2 多智能体的本质:把大Prompt拆成小岗位
CrewAI的思路很朴素:与其让一个Agent干完所有事,不如把一条工作流拆成多个节点,每个节点只关注一小块目标,然后由一个编排器把它们串起来。
这里说个我一直觉得特别贴切的类比:你把CrewAI当成一个部门,Agent是部门里的员工,Task是工单,Process是你们部门的工作流程。员工接到工单、完成交付,下一个人拿到上一个的产出继续干活。大家职责清晰,产出自然比一个人干四份活要稳定。
所以,第一个要建立的概念就是:多智能体不是“很多模型在开会”,而是“多个职责不同的模型在接力”。
2. 动手前的关键认知:CrewAI里的角色不是“人格”而是“岗位说明书”
2.1 三个核心概念:Agent、Task、Crew
我先用一个最小模型把CrewAI的所有关键概念讲清楚,你后面看代码就不会晕。
Agent(智能体):相当于团队里的一个成员。在CrewAI里,Agent不是一个人格,而是一份岗位说明书。它通常由三个字段决定“这个员工是谁、要干什么、怎么干”:
role:岗位名,比如“资深行业研究员”。模型会把这个当成自己的身份标签。goal:工作目标,也就是你期望它完成什么KPI。backstory:背景设定,相当于入职培训材料,可以写清楚工作习惯、关注点、输出风格。
Task(任务):一张工单。它描述“这件事具体做什么”以及“交付物长什么样”。一个Agent可以干多个Task,但更推荐一个Task对应一个明确的交付物。
Crew(团队):把Agent和Task组合起来的地方。你需要在这里声明团队里有谁、任务有哪些、按什么顺序执行。Crew就是那个编排器,跑起来之后会按照你配置的流程自动推进。
一句话总结:Agent定义“谁来做”,Task定义“做什么”,Crew定义“怎么配合着做完”。
2.2 role、goal、backstory为什么会直接影响输出质量
这是整篇最容易被忽略的点。CrewAI在底层调用大模型时,会把Agent的role、goal、backstory以及当前Task的description、expected_output组装成系统提示词和任务提示词。
也就是说,你在这些字段里写了什么,模型就会按什么人设、什么目标、什么工作习惯来回答。写“你是一个研究员”,和写“你是一名专注新能源产业链的资深分析师,擅长从年报数据中提炼结论,会用对比表格呈现结果”,模型产出的内容深度完全不同。
这不是玄学,而是提示词工程搬到多智能体框架里之后,变成了代码里明确定义的字段。后面第五章我会放一组具体对比,你先记住这个结论:角色字段写得好不好,直接决定团队产出质量。
2.3 Sequential与Hierarchical:两种团队协作模式怎么选
CrewAI默认支持两种流程模式:
| 维度 | SequentialProcess | HierarchicalProcess |
|---|---|---|
| 协作方式 | 任务按顺序执行,先A后B再C | 有一个Manager Agent负责拆解和分发任务 |
| 适用场景 | 流程固定的流水线 | 任务不固定、需要动态决策的场景 |
| 成本 | 相对较低,谁干多少活很清楚 | 更高,Manager本身也要消耗Token |
| 上手难度 | 低,新手首选 | 较高,需要设计Manager的角色和目标 |
新手我的建议非常直接:先别碰HierarchicalProcess。你只需要把任务按先后顺序排好,用SequentialProcess跑通一遍,理解Agent之间是怎么传递信息的,后面再考虑要不要加一个“小领导”。
3. 实战搭建:用三位“数字员工”搭建一个内容生产小团队
3.1 环境准备与安装
第一步当然是装环境。CrewAI的安装非常简单:
pip install crewai crewai-tools建议你把CrewAI装在一个干净的Python虚拟环境里,避免和其他项目的依赖冲突。Python 3.10到3.12基本没什么问题,3.13版本有的依赖可能还没跟上,容易踩坑。
装完之后,你需要准备一个大模型API的Key。CrewAI默认走OpenAI格式的接口,在命令行设置环境变量即可:
export OPENAI_API_KEY="sk-你的密钥"如果你用的是其他大模型API,只要服务商提供OpenAI兼容接口,通常也能直接适配,只是需要在代码里改一下base_url参数。
提示:如果你在Windows上开发,用
set OPENAI_API_KEY=你的密钥;在PowerShell里用$env:OPENAI_API_KEY="你的密钥"。
3.2 完整代码:研究、写作、编辑三段式流水线
下面这段代码是我跑通第一个CrewAI项目时用的完整实现,你直接复制到一个.py文件里就能运行。
import os from crewai import Agent, Task, Crew, Process # 如果你已经把Key写进环境变量,这里可以不用显式再写 os.environ["OPENAI_API_KEY"] = "你的API Key" # 1. 定义一个Agent:行业研究员 researcher = Agent( role="资深行业研究员", goal="针对指定行业主题,收集并整理出有数据支撑的关键趋势", backstory=( "你在咨询公司做了8年行业研究,擅长从公开报告中提炼核心数据。" "你习惯用简洁的分点结构输出发现,并会标注每条结论的数据来源类型。" ), verbose=True, allow_delegation=False, ) # 2. 定义一个Agent:内容撰稿人 writer = Agent( role="商业分析文章撰稿人", goal="将研究员的输出材料改写成结构清晰、语言流畅的专业文章", backstory=( "你是一名商业编辑,写过多篇深度行业分析。" "你重视逻辑递进,喜欢用事实说话,避免空泛的形容词。" ), verbose=True, allow_delegation=False, ) # 3. 定义一个Agent:内容校对编辑 editor = Agent( role="严格的内容校对编辑", goal="检查文章的逻辑、数据一致性和表达清晰度,并给出最终修改稿", backstory=( "你做文字编辑工作超过10年,对前后矛盾和数据来源不可靠的内容极其敏感。" "你只输出修改后的完整文章,不输出修改理由清单。" ), verbose=True, allow_delegation=False, ) # 三个Task,按顺序衔接 research_task = Task( description=( "调研‘2025年充电桩行业的发展趋势’," "重点关注市场规模、政策方向、技术路线和主要企业的布局。" ), expected_output=( "一份包含5条关键趋势的调研要点,每条趋势包含具体数据或事实来源" ), agent=researcher, ) writing_task = Task( description=( "根据研究员的调研结果,撰写一篇完整的行业分析文章。" "文章需要有引言、分论点、数据支撑和总结。" ), expected_output="一篇不少于800字的中文商业分析文章", agent=writer, ) editing_task = Task( description=( "对撰稿人产出的文章进行严格校对。重点检查:数据是否前后矛盾," "论点是否有数据支撑,是否有重复表达。直接输出修改后的完整文章。" ), expected_output="修改后的最终完整文章", agent=editor, ) crew = Crew( agents=[researcher, writer, editor], tasks=[research_task, writing_task, editing_task], process=Process.sequential, verbose=True, ) result = crew.kickoff() print("最终输出:") print(result)这段代码逻辑很直接:
- 三个Agent分别对应“找料的人”“写稿的人”“改稿的人”。
- 三个Task按顺序排列:先调研,再写稿,最后校对。
crew.kickoff()就是启动整个团队工作的入口。
我强烈建议第一次跑的时候把verbose=True打开,这样你能在终端里清楚看到每个Agent收到了什么指令、产出了什么内容,对理解工作流非常有帮助。
3.3 运行起来后,你会看到什么
运行上面这个脚本,终端会输出类似这样的日志(不同版本可能略有差异):
- 当前正在执行的任务名称,比如“研究充电桩行业发展趋势”。
- 被指派执行的Agent角色,比如“资深行业研究员”。
- 该Agent思考或者产出内容的过程摘要。
- 最终一轮跑完后,打印出整个Crew的综合输出。
第一次看到这个运行过程,你会非常直观地感受到什么叫“分工”:研究员只输出调研要点,撰稿人只基于要点写正文,编辑只做修改。三个人各干各的,流水线清晰可见。
注意:运行时间和消耗Token会明显高于单次Prompt调用。因为你在跑三个智能体,每一个智能体都至少有一次完整的模型调用。任务越长、Agent越多,时间越长,这是正常现象,不用担心。
4. 角色分配里的真实“坑”:我从三个失败案例学会的
4.1 角色职责重叠:两个Agent抢着做同一件事
我第一次搭建这个团队时,给“研究员”的角色描述里顺手写了“并输出一篇完整的分析文章”,结果就是研究员在调研阶段就把文章写完了,后面写手被迫“二次加工”,输出的内容反而变啰嗦了。
排查思路很简单:打开verbose日志,看某个Task实际是由哪个Agent执行的,再看他收到的description原文,基本就能定位问题。我当时是把自己写的角色描述当成了“给机器的指令”,忘了它同时也是“给模型的人设边界说明”。
这个问题在CrewAI里非常常见,因为Agent的goal和Task的description是两套独立的字段。你要记住一个判断标准:**如果一个Task的描述能被另一个Agent的Role顺理成章地完成,说明你的分工有问题。**我在实践里的做法是写角色时先反问一句“这个岗位负责什么、不负责什么”,并在backstory里明确写“不负责的事情不要做”。
4.2 上下文传递断裂:第二个Agent天然不知道第一个Agent干了啥
这个坑比职责重叠更隐蔽,也更重要。
在CrewAI的执行过程中,如果一个Task没有显式声明它需要上一个Task的输出,那下一个Task理论上只有一个任务描述,并不天然带着上文。结果就是:撰稿人可能完全无视研究员写的调研要点,自己凭空生成了文章,或者把研究员提到的数据给改掉了。
排查链路是这样的:先看最终文章里的数据是不是研究员原文提到的,再往前翻日志,看“writing_task”这个任务的输入上下文里有没有包含“research_task的输出”。如果没有,说明上下文没有传递过去。
解决办法也很简单。在CrewAI里,Task支持声明它的数据来源:
writing_task = Task( description="撰写文章", expected_output="800字分析文章", agent=writer, context=[research_task] # 显式声明依赖上一环节的产出 )加了context参数,CrewAI就会把research_task的输出打包成上下文,传给撰稿人。这种显式声明依赖的方式,比我一开始“靠运气传递”要稳定得多。
4.3 授权和工具不足:Agent明明能调用工具却一直不用
第三个坑是工具调用。CrewAI里的Agent默认是不会自己去搜网、查数据库、跑代码的,除非你明确给它挂载工具。很多新手以为Agent就是全能的,给一个角色就能自动上网查资料,这不对。
我当时让“研究员”去查某个行业最新市场规模,它直接基于训练数据里的旧数字编了一个结果,在日志里看起来还挺像那么回事。后来我才意识到,应该给它挂一个联网搜索工具,或者至少让它在输出里注明数据来源的时间。
如果你需要联网信息,可以接CrewAI配套的工具包,或者自己定义一个搜索工具。这个我再第六节展开讲。这里你先记住结论:Agent不是全知全能的,不挂工具它就只能靠模型自己的知识库硬编。
5. 角色定义质量决定团队上限:一组真实输入对比
5.1 敷衍版角色定义:运行时会发生什么
只看定义不跑任务,你很难感受到差异。我在这里放一组实际对比,让你看看同样的任务,角色定义敷衍和精细会造成多大差别。
敷衍版是这样的:
researcher_wrong = Agent( role="研究员", goal="研究充电桩行业趋势", backstory="你是一个研究员。", )看起来是不是很像你平时写Prompt的样子?实际跑下来,它输出的调研内容大概是:“充电桩行业近年来快速发展,市场规模持续扩大,技术不断进步”。全是正确但空泛的废话,没有具体数据,没有可验证的结论,甚至“市场规模持续扩大”这种话放在任何行业都成立。
问题出在哪?你只说“你是一个研究员”,模型不知道自己是哪个领域的研究员、为谁研究、研究完用什么格式汇报。它只能选择最安全、最通用的表达方式,于是产出了正确的废话。
5.2 精确版角色定义:相同成本下输出差距
同一产品跑出的精确定义版本:
researcher_good = Agent( role="新能源产业链资深研究员", goal="围绕充电桩行业给出5条有数据支撑的关键趋势判断", backstory=( "你长期跟踪充电桩产业链,熟悉上游元器件、中游制造、下游运营三个环节。" "你输出结论时会主动提供数据支撑,并用分点列表组织内容。" "本次研究服务于一篇面向行业从业者的分析文章。" ), )跑下来的输出会明显不同:它会主动拆成5条趋势,每条都带市场规模、增长率、或典型企业的布局方向,并且会用“根据某机构统计”这种来源性表述。
差距的核心就是:你把模型的“人设”划定得越具体,它在生成时搜索自己知识库的范围就越聚焦,发散的概率越低。这就是为什么我一直强调,CrewAI里的Agent不是在扮演角色,而是在按照岗位说明书工作。
5.3 让多个Agent“交接”时信息不丢的三个技巧
结合前面的实战经验,我整理了三个让交接更稳定的技巧,你可以在自己的团队里直接用。
第一,每个Task的expected_output要写清楚“交付格式”。比如“分别用5个要点输出,每个要点必须包含一个具体数据”,这个字段是模型判断“任务是否完成”的标准,写清楚它,后面交接时下游Agent拿到的材料才会规整。
第二,下游任务描述里主动“点”一下上游产出。在description里写“请严格基于研究员的调研结果展开,不要新增未经证实的数据”,这能有效减少下游Agent自己发挥的空间。
第三,善用context参数。CrewAI提供显式的任务依赖绑定,把上游Task传给下游Task,比让下游Agent“凭感觉”理解更可靠。你可以在下文第5.2节代码看到这个写法的具体位置。
6. 从“脚本团队”到“能接活的小组”:进阶扩展思路
6.1 给数字员工配上工具箱
跑通基础流程之后,你大概率会发现一个问题:Agent光靠模型自带知识,很多事实性的东西会编。解决思路是给Agent挂工具。
CrewAI里自定义工具非常简单,用@tool装饰器就能把一个函数转成Agent可调用的工具:
from crewai.tools import tool @tool("读取本地资料文件") def read_local_file(file_path: str) -> str: """读取指定路径下文本文件的内容""" with open(file_path, "r", encoding="utf-8") as f: return f.read()然后在定义Agent时传入:
researcher = Agent( role="行业研究员", goal="结合本地行业资料完成调研", backstory="你习惯基于一手资料做分析。", tools=[read_local_file], )这样,研究员就能在需要的时候读取你准备好的资料文件,而不是凭空编造数据。类似的工具你还可以自己写:查询库存、读取数据库、调用内部接口等,只要能返回文本信息,都可以封装成工具给Agent用。
实操心得:工具一次不要挂太多。挂3个以上的工具,模型在“该用工具还是不该用工具”之间反复权衡,反而容易出错。我一般一个Agent最多挂2到3个和它的职责强相关的工具。
6.2 换掉默认模型:本地部署和更便宜的方案
CrewAI默认调用云端大模型API,但如果你做的是多轮内部测试,Token消耗会上升得比较快。有一个省钱思路是本地部署模型,通过Ollama这类工具跑一个本地模型,CrewAI也支持自定义llm参数。
from crewai import LLM local_llm = LLM( model="ollama/你的本地模型名", base_url="http://localhost:11434", ) researcher = Agent( role="行业研究员", goal="完成调研", backstory="你是一名研究员", llm=local_llm, )本地跑模型的好处是免费、数据不出本机,缺点是小参数量模型的复杂推理能力和工具调用效果确实不如云端大模型。我的建议是:日常调试用本地模型省成本,跑正式内容用云端大模型保障质量。
6.3 让团队记住上下文:memory与知识库的简单玩法
前面提到过,CrewAI的多智能体协作默认是一次性执行,一个任务结束就翻篇了。如果你希望Agent能记住跑过的任务,或在后续对话中复用之前的信息,可以用记忆机制。
在创建团队时开启记忆:
crew = Crew( agents=[researcher, writer, editor], tasks=[research_task, writing_task, editing_task], process=Process.sequential, memory=True, )开启后Agent会保留短期记忆,CrewAI还有长期记忆配置,可以让Agent在后续多次执行时复用经验。不过要注意:记忆功能会额外消耗存储和Token,简单的流水线任务没必要开。
我第一次跑CrewAI项目的时候,最大的体会是:多智能体的门槛其实不在写代码,而在“拆活”的思维方式。你要能忍住不让一个Agent干所有事,强迫自己把工作拆成一个一个小岗位。只要这个思维转过来,CrewAI剩下的就是几个字段的事。建议你先照着上面的代码把三个人跑通,再慢慢加工具、加流程、换模型,最后你会发现自己已经能设计一支能接真实任务的数字小团队了。