做内容智能体项目时我花了不少时间踩过一条弯路一开始只是写脚本调 DALL-E 的接口后来需求多了发现光是用户一句帮我出一张图背后要处理的提示词清洗、风格约束、尺寸选择、文件命名就已经很繁琐更别提还要把生成结果继续喂给下一个环节。直到我把这个流程交给 CrewAI 体系去编排才真正体会到什么叫团队作业——不是把多个AI功能塞进一个大循环而是让几个有明确分工的智能体各自负责一段再由框架去调度协作。今天这篇就分享一次完整的实战记录如何用 CrewAI 开发一个智能体让它基于一句话需求自主分析、构思提示词并调用 DALL-E 完成图像生成。这篇内容适合三类人已经熟悉 Python、想从单点调 API升级到多智能体协同工作流的开发者做内容运营或设计辅助工具需要批量生成配图、概念图、预览图的人以及纯粹好奇AI Agent 到底怎么编排工具的学习者。我会把从环境搭建、工具封装、Agent 设计、任务编排到避坑排查的完整过程一次讲透尽量让基础薄弱的人也能按步骤跑通。1. 整体设计为什么是CrewAI DALL-E而不是一条脚本走到底如果你只是在命令行里写一个 Python 脚本调一次 DALL-E 接口把图片保存下来其实几分钟就能完成。但真实项目从来不是一条直线而是一串决策链。一个合格的生产流程要回答的问题包括用户那句话到底想要什么风格画面主体是什么要不要背景描述生成的图片适合放在什么版式里如果效果不好怎么调整提示词重试如果这些逻辑全都堆在一个 Python 文件里用 if-else 去判断可维护性会越来越差。CrewAI 的价值在于它把决策和执行拆成不同角色的智能体让框架来串联。1.1 CrewAI 的核心概念和你平时写代码的差别初次接触 CrewAI 的人会被几个专有名词绕晕Agent智能体、Task任务、Process流程、Tool工具。用生活里的例子来理解就是你把一个设计需求交给一个虚拟工作室里面有文案策划、艺术总监、出图专员三个员工。你只负责下达需求策划先分析需求、写绘图提示词总监审核风格出图专员拿着提示词去 DALL-E 生成图像。这些员工就是 Agent他们要做的事情就是 Task彼此之间的交接规则就是 Process出图专员手里的 DALL-E 接口封装就是 Tool。这样设计带来的第一个好处是职责边界清晰。写提示词的 Agent 不需要关心 DALL-E 的 API 参数出图专员也不需要关心用户原话里的修辞第二个好处是上下文可追溯每个 Agent 的输出可以成为下一个 Task 的输入中间过程你可以通过 verbose 模式完整看到第三个好处是替换灵活未来如果想把 DALL-E 换成其他绘画模型只需要改 Tool 内部实现Agent 的角色设定几乎不用动。1.2 这个方案最适合什么场景我测试下来CrewAI DALL-E 的组合最擅长的是批量生成同风格素材、用户需求模糊时的自主澄清和图像与其他任务串联这三类场景。比如你要做一个系列插画每个主题都需要统一的视觉风格如果只靠固定提示词模板所有图会显得僵化而用一个专门的提示词工程师Agent 去解析每个主题的差异再套用风格前缀出来的图会自然很多。另外因为 CrewAI 天然支持多步骤任务链你可以让图像生成后自动接一个写配文的 Agent或者接一个检查图片是否合规的 Agent比如是否包含不该出现的敏感元素。这是单一脚本很难优雅实现的分工却是实际内容生产中很常见的需求。2. 开发前的环境准备与参数策略动手前我会先把准备工作做扎实避免后面写到一半发现 Key 没配、模型参数理解错了回头返工。下面这一步是你任何 Agent 项目的地基。2.1 依赖安装与项目目录我建议用虚拟环境隔离依赖避免污染全局 Python 环境。命令很简单在一个空目录里执行mkdir crewai_dalle_demo cd crewai_dalle_demo python -m venv .venv source .venv/bin/activate # Windows 下用 .venv\Scripts\activate pip install crewai openai # 根据需要可加 crewai[tools]这里有两个容易踩的坑想提醒你。第一CrewAI 和 openai 的版本更新很快API 签名偶尔会调整建议安装到 requirements.txt 里锁住版本而不是每次拉最新。第二如果你需要调用搜索、爬取网页之类的工具再装crewai[tools]目前我们这个场景只需要 DALL-E基础版就够了装多了会拉入一堆不需要的依赖反而增加出错面。项目目录我习惯这样组织crewai_dalle_demo/ .env # 存放 API Key tools/ __init__.py dalle_tool.py # DALL-E 工具封装 agents.py # Agent 定义 tasks.py # Task 定义 crew.py # Crew 编排入口 output/ # 图像输出目录这么分是因为后面如果扩展其他模型或工具每个模块可以独立演进。你一开始嫌麻烦不拆文件也没关系但代码一旦超过一百行拆开调试会舒服得多。2.2 API Key 与运行配置在.env文件里写入你的密钥然后让代码加载它OPENAI_API_KEYsk-your-key-here在crew.py顶部用import os from dotenv import load_dotenv load_dotenv() assert os.getenv(OPENAI_API_KEY), 缺少 OPENAI_API_KEY要注意的是CrewAI 里的 Agent 本身是依赖大语言模型做推理的这个模型默认也走 OpenAI 的接口所以通常你就用同一个 Key 同时给 Agent 的 LLM 和 DALL-E 工具用。如果你是用其他兼容 OpenAI 协议的服务商也可以通过设置OPENAI_BASE_URL来改 endpoint。我这里就直接用官方接口讲。2.3 搞清楚 DALL-E 模型的关键参数拿到接口密钥后先别急着写 Agent把图像生成的参数策略定下来。DALL-E 模型目前主要是两个版本在流通DALL-E 2 和 DALL-E 3。我的建议是优先级选 DALL-E 3因为它在理解复杂提示词、文本渲染、细节精度上都好太多价格虽贵一点但产出率更高、返工少。如果项目有严格的成本控制可以允许用户通过参数在两者之间切换。常用的参数有这些我给你列个实用对照表参数DALL-E 3 可选值我的推荐策略size1024x1024 / 1792x1024 / 1024x1792方形图默认 1024x1024横幅和竖幅按场景选qualitystandard / hd概念草图用 standard成品输出用 hdn一次生成张数DALL-E 3 仅支持 1DALL-E 2 可以多张response_formaturl / b64_json建议 b64_json本地保存更稳定size 的选择在智能体工作流里尤其重要。比如一个做公众号封面配图的 Agent更适合横图 1792x1024如果生成小说人物立绘竖图 1024x1792 更好。所以 Tool 参数不要写死而是让调用的 Agent 能根据上下文自行传入合理尺寸。这也正好体现工具封装的灵活性。3. 核心实现把 DALL-E 封装成智能体能用的 Tool在这一节我会把工具封装、Agent 文案设计、任务编排三个主要代码块完完整整拆开讲。这是整个项目的核心建议一边看一遍动手敲。3.1 用 BaseTool 封装同步与异步请求在 CrewAI 里自定义一个工具最标准的方式是继承BaseTool定义输入结构args_schema然后实现_run方法。这个_run方法就是智能体真正调用工具时执行的逻辑。因为 CrewAI 在部分场景下支持异步执行我会同时实现_arun用一个简单的方式把同步逻辑桥接过去。代码如下保存为tools/dalle_tool.pyimport asyncio import base64 import uuid from pathlib import Path from typing import Type, Optional from openai import OpenAI from pydantic import BaseModel, Field from crewai.tools import BaseTool class DALLEImageInput(BaseModel): prompt: str Field(..., description用于生成图像的详细提示词应包含主体、风格、构图、光影等信息) size: str Field(default1024x1024, description图像尺寸可选 1024x1024、1024x1792、1792x1024) quality: str Field(defaultstandard, description图像质量standard 或 hd) output_dir: str Field(defaultoutput, description图像保存目录) class DALLEImageTool(BaseTool): name: str DALL-E 图像生成器 description: str ( 当需要根据提示词生成图像时使用此工具。 输入一段详细的英文或中文提示词返回本地保存的图片路径。 尺寸请根据用途合理选择方形配图用 1024x1024横版用 1792x1024竖版用 1024x1792。 ) args_schema: Type[BaseModel] DALLEImageInput def _run(self, prompt: str, size: str 1024x1024, quality: str standard, output_dir: str output) - str: client OpenAI() response client.images.generate( modeldall-e-3, promptprompt, sizesize, qualityquality, n1, response_formatb64_json, ) b64_data response.data[0].b64_json if not b64_data: raise RuntimeError(API 未返回图像数据) image_bytes base64.b64decode(b64_data) out_path Path(output_dir) out_path.mkdir(parentsTrue, exist_okTrue) filename f{uuid.uuid4().hex}.png file_path out_path / filename file_path.write_bytes(image_bytes) return str(file_path) async def _arun(self, prompt: str, size: str 1024x1024, quality: str standard, output_dir: str output) - str: return await asyncio.to_thread(self._run, prompt, size, quality, output_dir)几个细节你需要注意。第一Field(..., description...)里的描述不是装饰它会直接成为 Agent 理解参数含义的元信息所以一定要写得直白让智能体知道传什么值、为什么。第二我在description里特意写了尺寸选择建议这是引导智能体正确决策的关键——它比你写在代码注释里有效得多。第三使用b64_json而不是url是为了避免保存时才去远程下载多一次网络请求多一分失败风险。第四也是很多人容易忽略的uuid.uuid4().hex生成的文件名可以避免并发环境下文件覆盖问题。当你以后把 Crew 跑成多实例这个习惯能救你一次。3.2 图像保存与返回格式的处理方式有些项目希望工具返回图片而不只是保存路径。这里我单独说一下返回格式的设计。工具的唯一出口是返回值这个值会作为后续 Agent 的上下文参考。如果你返回一个路径字符串后续 Agent 能读到路径如果你返回图片已生成这种废话后续 Agent 什么有效信息都拿不到。所以我倾向于让_run返回这样一个字符串图片已生成并保存至: output/3fa42cd.png 尺寸: 1024x1024 提示词: 一只戴围巾的橘猫坐在窗台上看书暖黄色调水彩风格这样写的目的是组成一条可读执行记录因为大模型看到的信息越结构化后续转述或决策的准确性越高。你也可以返回 JSON 字符串效果类似。我测试下来纯路径有时会让后续 Agent 产生接下来要自己去读文件的误会而描述加上下文之后它就知道这是终态了不会再做多余动作。3.3 提示词工程与 Agent 角色文案设计Tool 封装完成后就要设计智能体的身份了。这里设计得好不好直接决定出图质量和用户体感。很多人以为给 Agent 一个名字就行其实还有灵魂三件套角色、目标、背景故事。在 CrewAI 中每个 Agent 都可以配置role、goal、backstory这些字段会被系统拼成对 LLM 系统的 prompt因此别写废话。我以一个名为视觉策划的 Agent 为例from crewai import Agent planner Agent( role视觉策划师, goal将用户模糊的图像需求转化为可供 DALL-E 使用的详细提示词, backstory你是一位资深视觉策划有十年互联网内容设计经验。 你擅长拆解用户需求判断画面氛围、构图风格、色彩倾向 并把这些判断翻译成精准、简洁、适合绘图模型理解的提示词。, llmgpt-4o, verboseTrue, )注意我故意没有在 goal 里写生成图片因为生成图片的动作是由后面的步骤完成的这个 Agent 只负责提示词。在多 Agent 协作中职责边界越清楚系统越稳定。到了这里提示词本身怎么写我要分享三条实测有效的经验。第一不要只写一只猫要写一只橘猫坐在窗台上阳光斜射书本翻开柔和的水彩质感构图以猫为主体背景虚化。第二风格关键词要放在主体描述之后而不是开头如暖黄色调日系插画风高细节放后面。第三如果你需要系列一致性在提示词里固定一个视觉锚点比如统一采用圆润线条和大地色系。4. 任务编排让图像生成进入智能体协作流程工具和 Agent 就位后下一步就是定义任务链。CrewAI 的任务和 Agent 功不可没的地方在于它支持引用其他任务的结果作为上下文这让多个 Agent 之间形成了真正的工作流。4.1 定义 Task描述、预期输出与上下文创建一个tasks.py把两个任务串起来。第一个任务交给视觉策划 Agent第二个任务执行图像生成。第二个任务需要引入第一个任务的结果也就是说Agent 必须先在上下文里看到策划师写的提示词才能调用图像生成工具。from crewai import Task task_analyze Task( description分析用户需求{user_request}。输出一段可以作为 DALL-E 提示词的中文文本。, expected_output5到6句话的绘图提示词包含主体、风格、构图、光线、色彩要求。, agentplanner, ) task_generate Task( description读取视觉策划师生成的提示词并调用图像生成工具产出图片。 根据内容比例选择合适尺寸最终输出图片路径和简短说明。, expected_output一张已经保存到本地的图片返回本地路径。, agentpainter, context[task_analyze], )这里{user_request}是占位符执行crew.kickoff(inputs{...})时会自动填充。context数组表示该任务可以参考task_analyze的输出。这是顺序流程中最重要的一环少了它painter 根本看不到策划结果极容易自己脑补一个提示词导致生成风格跑偏。4.2 运行 Crew 并观察执行链路把 Agent 和 Task 汇合到crew.pyfrom crewai import Crew, Process crew Crew( agents[planner, painter], tasks[task_analyze, task_generate], processProcess.sequential, verboseTrue, ) result crew.kickoff(inputs{user_request: 请生成一张适合做小红薯封面的冬日饮品插画}) print(result)Process.sequential表示严格按顺序执行。在verboseTrue状态下控制台会打印每一步的思考过程和工具调用日志这对调试非常有用。你会发现 Agent 会自言自语式地输出中间思考然后调用DALL-E 图像生成器工具最后返回图片路径。这里我特别想强调一个实操心得第一次运行尽量用一个非常具体的 user_request比如一张建筑线条图而不是一张好看的图。过于空泛的需求会给 Agent 太多自由发挥空间结果不稳定而且不方便你判断到底是提示词的问题还是代码的问题。等你把链路跑通了再逐步加大需求的模糊程度观察策划 Agent 的补全能力。4.3 进阶如何把流程升级为带条件的 Flows如果你已经跑通了上面的顺序流程你会发现一个痛点用户如果说生成一张图流程肯定执行但如果用户说帮我查资料并生成一张相关的图顺序流程就不够用了因为你需要先根据资料内容决定绘图提示词。这时候可以试试 CrewAI 的 Flows 机制。Flows 允许你用 Python 函数直接控制分支、合并和循环可以把它当做一个更灵活的接线员。一个最小示例思路是用一个start()入口接收需求根据需求调用研究员 Agent拿到参考资料摘要判断资料中是否有需要绘图的场景有则路由到绘图 Crew没有则直接返回文本结果。这种方式我把它用在了一个小 Demo 里效果是用户说帮我写一篇宠物零食的种草文案并配图Flows 会先让文案 Agent 写完内容再从中提取视觉关键词再触发绘图 Crew 生成配图。这一步属于进阶优化但如果你想把图像生成真正产品化付出这个学习成本很值得。5. 实测案例一句需求到成品图片的完整链路写代码只是开始真正能说明问题的是运行日志。我拿一次实际测试过程来解释你可以对照着理解整个逻辑是否如预期。5.1 测试输入与输出我输入的测试需求是用户需求我想给我的博客配一张科技感强的横幅封面图主题是人工智能与机器人协作。运行约十几秒后输出结果如下精简版主任务输出 图片已生成并保存至: output/9f8e51a2c.png 尺寸: 1792x1024 提示词: 未来感蓝色调的人工智能与机器人协作场景赛博朋克风格双手与机械臂共同触碰全息界面背景有流动数据光带柔和的光晕高细节横版构图这个结果说明链路正常走通了策划 Agent 正确识别了博客封面这个使用场景所以建议工具生成了 1792x1024 横版图painter Agent 忠实执行了策略没有自创一个尺寸。5.2 中间 Agent 做了什么我打开 verbose 日志能看到视觉策划 Agent 的核心输出 最终方案生成未来感蓝色调、赛博朋克风格的场景主体是人与机械臂协作。这个思考过程之所以值得关注是因为它不仅仅是传话还对输入做了场景化判断。它通过 backstory 里对视觉策划经验的描述自动选择了匹配的构图和色调。这也是为什么我强调角色背景文案要真实、具体而不只是一句你是绘图助手。5.3 结果文件与后续交付生成的图片默认存放在output/目录。如果你把工具里的输出目录参数暴露给上层 Agent它甚至可以根据用户要求创建以日期或项目名命名的子目录方便批量管理。此外如果你希望把这个流程接到内容发布系统可以在 Task 里再追加一个写出图说明的 Agent让它把图片路径 尺寸 提示词整理成 Markdown 格式直接作为交付文档。我实际用的方案就是让第三个 Agent 产出这样的总结省掉人工整理。6. 常见问题与避坑实录这部分是我最想写的因为单看官方文档你很难预知这些坑。我按照现象—原因—解决方案的方式整理方便你快速检索。6.1 API 报错排查速查表错误现象常见原因解决方案401 UnauthorizedAPI Key 未加载或格式错误检查.env是否存在检查环境变量加载代码是否执行429 Rate Limit请求频率超过配额增加time.sleep重试或对工具做本地限流400 Bad Request参数不合法如 DALL-E 3 传了 n1检查模型参数尤其 n 值和 size 是否匹配图片保存失败输出目录无权限output_dir.mkdir(parentsTrue, exist_okTrue)确认路径可写Agent 不调用工具工具描述不清晰在工具 description 中写明何时调用、传什么参数、返回什么6.2 Agent装模作样不调用工具怎么办这个问题我遇到不下三次Agent 收到任务后开始自己编一段图像描述然后直接返回文本说图片已经生成但其实没调用工具。根源在于任务描述给了 Agent 太强的话痨倾向但工具使用的引导不够强。我最终的解法有三层。第一任务描述里明确写必须调用 DALL-E 图像生成器 工具否则无法产出图片。第二在工具的 description 里加一句如果不使用此工具图像不会出现在磁盘上。第三检查 Agent 的max_iter是否设置太低默认值可能导致它在复杂任务中还没调用工具就放弃改到max_iter15通常能缓解。6.3 出图质量不稳定、风格不统一怎么办连续生成多张图时你可能会发现它们各自为政。这个问题的本质是提示词缺少稳定的锚。解决方案是要求策划 Agent在生成的提示词末尾固定追加统一的风格标识比如绘画风格现代扁平插画高饱和低亮度主色为克莱因蓝与暖白。然后人只需维护一个品牌视觉规范文件并通过context或inputs传给 Agent。我在项目里就把规范放在了user_request后的补充参数里效果立竿见影。另外DALL-E 3 对中文提示词的理解确实比对英文提示词弱一些。我不是让你不用中文而是建议让策划 Agent 在生成提示词时同时输出一个英文版本因为模型对英文视觉名词的语义理解更稳。也可以直接设置 Agent 的output_pydantic字段让它输出结构化 JSON里面同时有zh_prompt和en_prompt。这样工具调用时无需判断语言直接取英文版即可。6.4 成本控制与缓存策略图像生成 API 是按张计费的所以智能体一旦失控反复调用你的账单会非常难看。我加了两道保险。第一在 Task 描述里明确如果不满意第一版请先修改提示词再重新生成最多尝试 2 次。第二在 Tool 内部实现一个简单缓存将提示词做哈希如果同一提示词在指定目录已有对应图片直接返回旧路径不再调用 API。这种做法在测试阶段尤其省成本也避免你反复跑同一实验时重复扣费。缓存代码也不复杂简单示意import hashlib prompt_hash hashlib.sha256(prompt.encode(utf-8)).hexdigest()[:16] cached_path out_path / f{prompt_hash}.png if cached_path.exists(): return f图片已生成且命中缓存: {cached_path}把上面这段加在_run的 API 调用之前就能显著减少重复生成。如果你接入了其他绘图模型这个缓存思路同样适用。6.5 内容安全与合规过滤作为生产级项目你还需要在需求入口做内容筛选。DALL-E 模型本身有内容策略会拒绝部分敏感请求但智能体工作流里最好在视觉策划 Agent 执行前就加一个合规检查步骤让一个专门的 Agent 判断需求是否适合生成。如果判断为不适合直接终止流程不要浪费一次 API 调用。这样既省成本也避免生成被拒绝的报错影响整体流程。我个人跑了这么多轮下来的体会是CrewAI 的核心价值不是又多了一个调 API 的框架而是它把代码逻辑的组织方式从我的顺序变成了角色的分工。当你把图像生成这样的能力装进一个智能体团队里后面接写作、审核、发布等任务都变得顺理成章。最后再分享一个小技巧工具类的输入参数尽量用Field(..., description...)写清楚这一条能让你的 Agent 综合素质直接提升一个档次。希望这篇实战记录能帮你把一句话生成图片这种需求做成一个真正可靠、可控、可扩展的智能体项目。