CrewAI 高级04:输出格式、缓存与工作流编排

CrewAI 高级:输出格式、缓存与工作流编排

学习路径 : Day 6-8 | 难度 : ⭐⭐⭐⭐ | 前置知识 : 多 Agent 协作

📊 1. 输出格式控制

问题:如何让 Agent 输出结构化数据?

默认情况下,Agent 输出是自由格式的文本。但实际应用中,我们常常需要:

  • JSON 格式(用于 API 交互)
  • 特定结构(用于数据处理)
  • 固定模板(用于报告生成)

解决方案:Prompt 工程

通过在任务描述中明确指定格式:

python 复制代码
json_task = Task(
    description="""
    分析 AI 行业市场情况。
    
    请严格按照以下 JSON 格式输出:
    
    {
      "title": "报告标题",
      "summary": "摘要",
      "key_points": ["要点1", "要点2", "要点3"],
      "market_size": "市场规模",
      "recommendations": ["建议1", "建议2"]
    }
    
    注意:
    1. 必须是有效的 JSON 格式
    2. 所有字段都要包含
    3. 使用双引号
    4. 不要添加其他文字
    """,
    expected_output="JSON格式的报告",
    agent=analyst,
)

# 执行
result = crew.kickoff()

# 解析 JSON
import json
report_data = json.loads(result.raw)
print(report_data["title"])
print(report_data["key_points"])

关键技巧

  1. 提供 JSON 模板: 让 Agent 知道具体格式
  2. 强调规则: "必须是有效 JSON"、"不要添加其他文字"
  3. 错误处理: 使用 try-except 处理解析失败
python 复制代码
try:
    # 移除可能的 markdown 标记
    cleaned = result.raw.strip()
    if cleaned.startswith("```"):
        cleaned = cleaned.split("```", 2)[1]
    data = json.loads(cleaned)
    print("解析成功!")
except json.JSONDecodeError as e:
    print(f"解析失败: {e}")

💾 2. 任务缓存与上下文传递

任务缓存(cache)

作用: 避免重复调用 LLM,节省成本

python 复制代码
crew = Crew(
    agents=[researcher],
    tasks=[task1, task2, task3],
    cache=True,  # ← 启用缓存
)

工作原理:

  • 基于任务描述和输入生成哈希值
  • 相同的任务不重复执行
  • 缓存在内存中(程序重启后消失)

上下文传递(context)

作用: 让后续任务可以访问前面任务的输出

python 复制代码
# 任务 1: 研究 Python
task1 = Task(
    description="研究 Python 的特点",
    agent=researcher,
)

# 任务 2: 研究 JavaScript(引用任务 1)
task2 = Task(
    description="""
    研究 JavaScript,并与 Python 对比。
    请参考之前的 Python 研究结果。
    """,
    agent=researcher,
    context=[task1],  # ← 关键!传递 task1 的输出
)

# 任务 3: 研究 Go(引用任务 1 和 2)
task3 = Task(
    description="""
    研究 Go,并与 Python 和 JavaScript 对比。
    请参考之前的研究结果。
    """,
    agent=researcher,
    context=[task1, task2],  # ← 传递多个任务的输出
)

数据流向

复制代码
task1 输出: "Python 特点:简洁、易读、生态丰富"
    ↓ (通过 context 传递)
task2 看到: 
  "研究 JavaScript,并与 Python 对比。
   请参考之前的 Python 研究结果。
   
   Python 特点:简洁、易读、生态丰富"
    ↓
task2 输出: "JavaScript vs Python: ..."
    ↓ (通过 context 传递)
task3 看到: task1 和 task2 的所有输出

完整示例

python 复制代码
from crewai import Agent, Task, Crew, Process

researcher = Agent(
    role='技术研究员',
    goal='研究编程语言特点',
    llm="deepseek/deepseek-chat",
)

# 渐进式研究
task1 = Task(
    description="研究 Python 的核心特点",
    expected_output="Python 研究报告",
    agent=researcher,
)

task2 = Task(
    description="""
    研究 JavaScript,并与 Python 对比。
    请参考之前的 Python 研究结果。
    """,
    expected_output="JavaScript 及对比报告",
    agent=researcher,
    context=[task1],
)

task3 = Task(
    description="""
    研究 Go 语言,并与 Python 和 JavaScript 进行三方对比。
    制作对比表格。
    """,
    expected_output="三种语言综合对比报告",
    agent=researcher,
    context=[task1, task2],
)

crew = Crew(
    agents=[researcher],
    tasks=[task1, task2, task3],
    cache=True,  # 启用缓存
)

result = crew.kickoff()
print(result)  # 最终的综合对比报告

🔄 3. 工作流编排(Flows)

什么是工作流?

将多个 Crew 按顺序组合,形成复杂的多阶段流程。

复制代码
研究 Crew → 写作 Crew → 编辑 Crew
    ↓           ↓           ↓
 研究报告    文章初稿    最终文章

为什么需要工作流?

单个 Crew 的局限:

  • 所有 Agent 在一个团队
  • 适合简单任务

工作流的优势:

  • 不同阶段用不同 Crew
  • 更灵活,可以加入条件判断
  • 适合复杂的多阶段任务

完整示例:内容生产流水线

python 复制代码
from crewai import Agent, Task, Crew, Process

llm = "deepseek/deepseek-chat"

# ===== Crew 1: 研究团队 =====
researcher = Agent(
    role='市场研究员',
    goal='收集和分析市场数据',
    llm=llm,
)

research_task = Task(
    description="分析 AI 行业的最新发展趋势",
    expected_output="市场研究报告",
    agent=researcher,
)

research_crew = Crew(
    agents=[researcher],
    tasks=[research_task],
)

# ===== Crew 2: 写作团队 =====
writer = Agent(
    role='技术作家',
    goal='将研究报告转化为文章',
    llm=llm,
)

writing_task = Task(
    description="""
    基于以下市场研究报告,撰写一篇面向创业者的文章。
    
    市场研究报告:
    {research_report}
    
    要求:标题吸引人,语言通俗易懂。
    """,
    expected_output="面向创业者的文章",
    agent=writer,
)

writing_crew = Crew(
    agents=[writer],
    tasks=[writing_task],
)

# ===== Crew 3: 编辑团队 =====
editor = Agent(
    role='主编',
    goal='优化文章质量',
    llm=llm,
)

editing_task = Task(
    description="""
    审核并优化以下文章草稿。
    
    文章草稿:
    {draft_article}
    
    请优化标题、结构和可读性。
    """,
    expected_output="优化后的最终文章",
    agent=editor,
)

editing_crew = Crew(
    agents=[editor],
    tasks=[editing_task],
)

# ===== 执行工作流 =====
def run_workflow():
    # 步骤 1: 研究
    print("步骤 1/3: 市场研究")
    research_result = research_crew.kickoff()
    print("✅ 研究完成!")
    
    # 步骤 2: 写作(传入研究报告)
    print("\n步骤 2/3: 文章写作")
    writing_result = writing_crew.kickoff(
        inputs={"research_report": str(research_result)}  # ← 传参
    )
    print("✅ 写作完成!")
    
    # 步骤 3: 编辑(传入文章初稿)
    print("\n步骤 3/3: 文章编辑")
    editing_result = editing_crew.kickoff(
        inputs={"draft_article": str(writing_result)}  # ← 传参
    )
    print("✅ 编辑完成!")
    
    return research_result, writing_result, editing_result

# 运行
research, writing, editing = run_workflow()

# 保存各阶段结果
with open("research.txt", "w") as f:
    f.write(str(research))

with open("final_article.txt", "w") as f:
    f.write(str(editing))

关键点解析

1. 为什么要用 str() 转换?
python 复制代码
inputs={"research_report": str(research_result)}

因为 kickoff() 返回的是 CrewOutput 对象,但 inputs 只接受基本类型:

  • str, int, float, bool, dict, list
  • CrewOutput 对象
2. 参数如何传递?
python 复制代码
# 传入参数
writing_crew.kickoff(
    inputs={"research_report": "报告内容..."}
)

# Task 中使用占位符
writing_task = Task(
    description="""
    基于以下报告撰写文章:
    {research_report}  # ← 自动替换为实际内容
    """
)
3. 工作流模式

顺序模式(本示例):

python 复制代码
result1 = crew1.kickoff()
result2 = crew2.kickoff(inputs={"data": result1})
result3 = crew3.kickoff(inputs={"data": result2})

条件模式:

python 复制代码
result = crew1.kickoff()

if "正面" in str(result):
    final = positive_crew.kickoff()
else:
    final = negative_crew.kickoff()

并行模式:

python 复制代码
import concurrent.futures

with concurrent.futures.ThreadPoolExecutor() as executor:
    future1 = executor.submit(crew1.kickoff)
    future2 = executor.submit(crew2.kickoff)
    
    result1 = future1.result()
    result2 = future2.result()

💡 高级技巧总结

1. JSON 输出

python 复制代码
# Prompt 中指定格式
task = Task(
    description="""
    请严格按照以下 JSON 格式输出:
    {
      "field1": "value1",
      "field2": ["item1", "item2"]
    }
    """
)

# 解析结果
import json
data = json.loads(result.raw)

2. 任务缓存

python 复制代码
crew = Crew(
    agents=[...],
    tasks=[...],
    cache=True,  # 相同任务不重复执行
)

3. 上下文传递

python 复制代码
task2 = Task(
    description="基于之前的结果继续...",
    context=[task1],  # 传递 task1 输出
)

4. 工作流编排

python 复制代码
# 阶段 1
result1 = crew1.kickoff()

# 阶段 2(传入阶段 1 结果)
result2 = crew2.kickoff(
    inputs={"previous": str(result1)}
)

# 阶段 3(传入阶段 2 结果)
result3 = crew3.kickoff(
    inputs={"previous": str(result2)}
)

🎓 实战建议

场景 1: 数据报告生成

python 复制代码
# 工作流
数据收集 → 数据分析 → 报告生成 → 格式优化

# 使用
- cache=True(避免重复分析)
- context 参数(传递中间结果)
- JSON 输出(结构化数据)

场景 2: 内容创作

python 复制代码
# 工作流
研究 → 写作 → 编辑 → 排版

# 使用
- 多个 Crew(不同阶段)
- inputs 传递(阶段间数据)
- 条件判断(根据研究结果决定写作方向)

场景 3: 代码开发

python 复制代码
# 工作流
需求分析 → 架构设计 → 编码 → 测试 → 文档

# 使用
- Hierarchical 模式(项目经理协调)
- context 传递(设计文档传递给开发)
- cache(避免重复测试)

📝 总结

通过本文,你学会了:

  • ✅ 使用 Prompt 工程控制 JSON 输出
  • ✅ 任务缓存机制(cache=True)
  • ✅ 上下文传递(context 参数)
  • ✅ 工作流编排(多 Crew 组合)
  • ✅ 阶段间数据传递(inputs 参数)
相关推荐
可涵不会debug15 小时前
最近又挖到 MuMu 模拟器的新活,跟 AI 搭上线了
人工智能
qcx2315 小时前
【系统学AI】08 Plan-then-Execute范式:先想好再做,比ReAct强在哪
前端·人工智能·react.js·ai·react·plan execute
前端开发小透明15 小时前
Harness Engineering 实战:从前端开发视角,让AI Agent真正落地生产
人工智能
ting945200015 小时前
ModelHub 深度技术解析:macOS 原生菜单栏 LLM 模型管理工具,补齐 Ollama/MLX/LM Studio 生态短板
人工智能·macos·架构·策略模式
“码”力全开15 小时前
【架构深析】基于 Docker 与边缘计算的 AI 视频管理平台:从 GB28181/RTSP 统一接入到源码交付的闭环演进
人工智能·docker·架构
oscar99915 小时前
自动化测试中的“顽疾”:AI 如何最终攻克不稳定测试
人工智能·不稳定测试
2401_8322981015 小时前
布局全球出海赛道,OpenClaw全球化版本发布,抢占海外开源智能体市场
人工智能
jiayong2315 小时前
harness 与 hermes-agent 技术栈、构建与部署
人工智能·ai·智能体·harness·hermes-agent