多 Agent 协作实战:CrewAI / AutoGen 框架对比与生产级架构设计

0 阅读25分钟

多 Agent 协作实战:CrewAI / AutoGen 框架对比与生产级架构设计

前言: 上篇文章讲了单 Agent 的三大范式(ReAct / Plan-and-Execute / Reflexion),这篇聊聊更复杂的场景——多 Agent 协作。当单个 Agent 的能力不够用的时候,怎么让多个 Agent 配合工作?这篇文章对比主流框架,给出生产级架构设计的思路。


先说清楚:什么时候需要多 Agent?

多 Agent 不是银弹。能用单 Agent 解决的,不要用多 Agent。

多 Agent 的适用场景:

✅ 适合多 Agent 的场景:
├── 任务需要多种专业能力(写代码 + 搜索 + 发邮件)
├── 需要多人协作的工作流程(写稿 + 审核 + 发布)
├── 信息需要从多个来源汇总(多平台搜索 → 整合)
└── 需要相互校验的任务(生成 + 审查 + 修订)

❌ 不适合多 Agent 的场景:
├── 简单问答(直接问就行)
├── 单一步骤任务(不需要协作)
└── 实时性要求极高(多 Agent 有通信开销)

1. 多 Agent 协作模式

1.1 四种基本模式

"""
多 Agent 协作的四种基本模式
"""

# 模式1:层次式(Hierarchical)
# ┌─────────────┐
# │  Manager    │  ← 负责任务分配、结果汇总
# └──────┬──────┘
#    ┌───┴───┐
# ┌──┴──┐ ┌──┴──┐
# │Worker│ │Worker│  ← 负责执行具体任务
# └─────┘ └─────┘
HIERARCHICAL_PATTERN = """
特点:有一个中心协调者(Manager),分解任务给下属 Worker
优点:清晰、易理解
缺点:Manager 可能成为瓶颈
适用:任务明确、可分解的场景
"""


# 模式2:并行式(Parallel)
# ┌────────────────────┐
# │       Task         │
# └─────────┬──────────┘
#      ┌────┴────┐
#   ┌──┴──┐  ┌──┴──┐
#   │Agent│  │Agent│  ← 同时处理
#   └──┬──┘  └──┬──┘
#      └────┬────┘
#      ┌────┴────┐
#   ┌──┴──┐  ┌──┴──┐
#   │Agent│  │Agent│  ← 结果汇总
#   └─────┘  └─────┘
PARALLEL_PATTERN = """
特点:多个 Agent 同时工作,结果汇总
优点:速度快、并行度高
缺点:结果可能冲突,需要裁决机制
适用:信息收集、多方案生成
"""


# 模式3:环形协作(Round-robin)
# ┌──────────────────────────────────────┐
# │                                       │
# │  Agent-A → Agent-B → Agent-C → Agent-A│
# │                                       │
# │  每一轮的输出是下一轮的输入           │
# └──────────────────────────────────────┘
ROUND_ROBIN_PATTERN = """
特点:Agent 轮流处理,每一轮的输出影响下一轮
优点:可以逐步完善、相互补充
缺点:速度慢,可能陷入循环
适用:创意生成、写作修订
"""


# 模式4:网状式(Mesh)
#     ┌──────┐
#     │Agent │
#     │  A   │◄──────►┌──────┐
#     └───┬───┘        │Agent │
#         │            │  B   │
#     ┌───┴───┐        └───┬───┘
#     │Agent  │◄──────►───┘
#     │  C   │
#     └───────┘
MESH_PATTERN = """
特点:Agent 之间可以直接通信,没有固定顺序
优点:灵活、适应性强
缺点:通信复杂、难以追踪
适用:复杂、需要多领域知识融合的任务
"""

1.2 选择建议

模式适用场景框架选择
层次式任务分解、流程明确CrewAI (默认) / LangGraph
并行式信息收集、投票决策CrewAI + parallel
环形迭代优化、内容创作LangGraph / Custom
网状式复杂协作、需要对话AutoGen

2. CrewAI 框架实战

2.1 为什么选 CrewAI

CrewAI 是我目前用下来最顺手的多 Agent 框架:

优点:
✅ 概念清晰:Agent = 角色 + 目标 + 工具
✅ 配置简单:YAML 或代码都能定义
✅ 内置多种进程:Sequential / Parallel / Hierarchical
✅ 支持任务依赖:写清楚谁依赖谁
✅ 社区活跃:文档和示例比较全

缺点:
⚠️ 定制化有限(复杂逻辑可能不够用)
⚠️ 生产级监控/追踪要自己加
⚠️ 国内生态相对弱

2.2 完整代码示例

# crewai_demo.py

"""
CrewAI 完整示例:技术博客创作流程

流程:
1. Researcher:搜索最新 AI 技术动态
2. Writer:基于研究结果撰写文章
3. Editor:审核文章质量,给出修改意见
4. Publisher:发布到平台(模拟)
"""

from crewai import Agent, Task, Crew, Process
from crewai.tools import SerpDevTool, FileReadTool, FileWriteTool


def create_blog_crew(topic: str):
    """创建博客创作团队"""
    
    # 定义工具
    search_tool = SerpDevTool()
    file_read = FileReadTool()
    file_write = FileWriteTool()
    
    # ========== Agent 定义 ==========
    
    # 1. 研究员
    researcher = Agent(
        role="高级研究员",
        goal=f"收集关于「{topic}」的全面、有价值的信息",
        backstory="""
        你是一位经验丰富的技术研究员,擅长:
        - 从多个渠道收集一手信息
        - 识别信息的可靠性和时效性
        - 提炼关键观点和趋势
        
        你搜索的资料会成为文章的基础,所以要尽量全面准确。
        """,
        tools=[search_tool],
        verbose=True,  # 打印详细日志
        allow_delegation=False  # 不需要委托给其他人
    )
    
    # 2. 写手
    writer = Agent(
        role="技术内容创作者",
        goal="将研究成果转化为通俗易懂、有价值的文章",
        backstory="""
        你是一位专业的内容创作者,擅长:
        - 用简洁清晰的语言解释复杂技术
        - 结构化组织文章(开篇 / 正文 / 总结)
        - 在专业性和可读性之间找到平衡
        
        你的文章要:
        - 有干货、有深度
        - 语言生动、不枯燥
        - 给读者带来实际收获
        """,
        tools=[file_read, file_write],
        verbose=True,
        allow_delegation=False
    )
    
    # 3. 编辑
    editor = Agent(
        role="资深编辑",
        goal="确保文章质量达到发布标准",
        backstory="""
        你是一位严格的编辑,职责是:
        - 检查文章逻辑是否清晰
        - 指出需要补充或修改的地方
        - 给出具体、可操作的修改建议
        
        你要对读者负责,不要放过任何问题。
        """,
        verbose=True,
        allow_delegation=False
    )
    
    # ========== Task 定义 ==========
    
    # 任务1:研究
    research_task = Task(
        description=f"""
        深入研究「{topic}」相关的内容:
        1. 搜索最新进展和趋势
        2. 找到3-5个有代表性的案例或应用
        3. 整理关键信息点和技术要点
        4. 评估信息的可靠性和时效性
        
        输出:一份结构化的研究报告
        """,
        agent=researcher,
        expected_output="包含最新动态、案例、技术要点的研究报告"
    )
    
    # 任务2:写作
    writing_task = Task(
        description="""
        基于研究报告,撰写一篇完整的文章:
        1. 标题要吸引人
        2. 开篇引入要抓人
        3. 正文结构清晰,有逻辑
        4. 结尾有总结和行动指引
        
        目标读者:有一定技术基础的学习者
        字数:2000-3000字
        """,
        agent=writer,
        expected_output="一篇完整的文章草稿",
        context=[research_task]  # 依赖研究任务的结果
    )
    
    # 任务3:编辑审核
    editing_task = Task(
        description="""
        审核文章草稿:
        1. 检查文章结构和逻辑
        2. 指出需要改进的地方
        3. 给出具体的修改建议
        
        如果发现问题,标注出来让写手修改。
        """,
        agent=editor,
        expected_output="包含修改建议的审核报告",
        context=[writing_task]  # 依赖写作任务的结果
    )
    
    # 任务4:修订(如果需要)
    revision_task = Task(
        description="""
        根据编辑的反馈修订文章。
        确保所有提到的问题都得到解决。
        """,
        agent=writer,
        expected_output="修订后的最终文章",
        context=[editing_task],
        async_execution=False  # 同步执行,等待编辑完成
    )
    
    # ========== Crew 定义 ==========
    
    crew = Crew(
        agents=[researcher, writer, editor],
        tasks=[research_task, writing_task, editing_task, revision_task],
        process=Process.sequential,  # 顺序执行
        verbose=True
    )
    
    return crew


def run_blog_creation(topic: str):
    """执行博客创作流程"""
    
    print(f"🚀 开始创作:「{topic}」\n")
    
    crew = create_blog_crew(topic)
    result = crew.kickoff()
    
    print("\n" + "="*60)
    print("✅ 创作完成!")
    print("="*60)
    
    return result


if __name__ == "__main__":
    result = run_blog_creation("AI Agent 开发实战")
    
    # result 包含:
    # - raw: 原始输出
    # - tasks_output: 每个任务的输出
    print(result.raw)

2.3 并行执行示例

# parallel_crew.py

"""
并行执行:多源信息收集

场景:同时从多个来源收集信息,然后汇总
"""

from crewai import Agent, Task, Crew, Process


def create_research_crew(topic: str):
    """创建并行研究团队"""
    
    # 三个研究员,分别负责不同来源
    researcher_news = Agent(
        role="新闻研究员",
        goal=f"收集「{topic}」的最新新闻动态",
        backstory="你专注于追踪最新的新闻和事件..."
    )
    
    researcher_tech = Agent(
        role="技术研究员",
        goal=f"深入研究「{topic}」的技术原理和实现",
        backstory="你专注于技术细节和实现方案..."
    )
    
    researcher_cases = Agent(
        role="案例研究员",
        goal=f"收集「{topic}」的真实应用案例",
        backstory="你专注于寻找有代表性的实践案例..."
    )
    
    # 三个独立的研究任务
    task_news = Task(
        description=f"搜索「{topic}」的最新新闻",
        agent=researcher_news,
        expected_output="最新动态汇总"
    )
    
    task_tech = Task(
        description=f"分析「{topic}」的技术原理",
        agent=researcher_tech,
        expected_output="技术原理说明"
    )
    
    task_cases = Task(
        description=f"收集「{topic}」的应用案例",
        agent=researcher_cases,
        expected_output="案例列表"
    )
    
    # 汇总任务
    synthesizer = Agent(
        role="内容整合师",
        goal="整合三个研究员的结果,生成完整报告",
        backstory="你擅长将不同来源的信息整合成连贯的报告..."
    )
    
    task_synthesize = Task(
        description="整合研究结果,生成完整报告",
        agent=synthesizer,
        expected_output="结构完整的综合报告",
        context=[task_news, task_tech, task_cases]
    )
    
    # 并行执行前三个任务
    research_crew = Crew(
        agents=[researcher_news, researcher_tech, researcher_cases],
        tasks=[task_news, task_tech, task_cases],
        process=Process.parallel
    )
    
    # 再执行汇总
    final_crew = Crew(
        agents=[synthesizer],
        tasks=[task_synthesize],
        process=Process.sequential
    )
    
    return research_crew, final_crew


def run_parallel_research(topic: str):
    """执行并行研究流程"""
    
    print(f"🔍 开始并行研究:「{topic}」\n")
    
    research_crew, final_crew = create_research_crew(topic)
    
    # 第一步:并行研究
    print("📡 三个研究员同时工作...\n")
    research_result = research_crew.kickoff()
    
    # 第二步:汇总
    print("📝 整合师正在汇总...\n")
    final_result = final_crew.kickoff(
        inputs={"previous_output": research_result.raw}
    )
    
    return final_result

3. AutoGen 框架实战

3.1 AutoGen 的特点

AutoGen(微软开源)和 CrewAI 的定位不太一样:

AutoGen 特点:
✅ 更灵活:Agent 之间可以真正「对话」
✅ 对话式:适合需要来回讨论的场景
✅ 微软背书:质量和持续性有保障
✅ 多种模式:对话 / 群聊 / 层级

CrewAI 特点:
✅ 配置更简单
✅ 概念更直观
✅ 任务管理更强

简单理解:
- CrewAI = 任务管理框架
- AutoGen = 对话框架

3.2 AutoGen 代码示例

# autogen_demo.py

"""
AutoGen 示例:代码生成 + 审查对话

流程:
1. User 发起请求
2. Assistant 生成代码
3. Critic 审查代码
4. Assistant 根据反馈修改
5. 循环直到 Critic 满意
"""

from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager


def create_code_review_system():
    """创建代码审查系统"""
    
    # 1. 助手 Agent(生成代码)
    assistant = AssistantAgent(
        name="Coder",
        system_message="""
        你是一位经验丰富的 Python 开发者。
        根据用户需求编写高质量的代码。
        
        职责:
        - 编写清晰、规范的代码
        - 添加必要的注释
        - 考虑边界情况
        
        如果审查员提出问题,要积极修改。
        """,
        llm_config={
            "model": "gpt-4o",
            "api_type": "openai"
        }
    )
    
    # 2. 审查员 Agent
    critic = AssistantAgent(
        name="Critic",
        system_message="""
        你是一位严格的代码审查员。
        
        审查要点:
        1. 代码正确性:逻辑是否正确?
        2. 代码规范:是否符合 PEP8?
        3. 安全性:是否有潜在风险?
        4. 性能:是否有优化空间?
        5. 可读性:是否易于理解?
        
        审查方式:
        - 如果代码有问题,明确指出问题所在
        - 如果代码OK,说 "代码审查通过"
        - 必要时给出修改建议
        """,
        llm_config={
            "model": "gpt-4o",
            "api_type": "openai"
        }
    )
    
    # 3. 用户代理(模拟用户)
    user_proxy = UserProxyAgent(
        name="User",
        human_input_mode="NEVER"  # 不需要人工输入
    )
    
    return assistant, critic, user_proxy


def code_review_workflow(requirement: str):
    """执行代码审查工作流"""
    
    assistant, critic, user_proxy = create_code_review_system()
    
    # 初始化群聊
    group_chat = GroupChat(
        agents=[user_proxy, assistant, critic],
        max_round=10,  # 最多对话10轮
        speaker_selection_method="round_robin"  # 轮流发言
    )
    
    manager = GroupChatManager(groupchat=group_chat)
    
    # 开始对话
    user_proxy.initiate_chat(
        manager,
        message=f"""
请帮我实现以下功能:

{requirement}

完成后,等待审查员的反馈并根据需要修改代码。
"""
    )
    
    # 获取对话历史
    chat_history = user_proxy.chat_messages.get(manager, [])
    
    return chat_history


# ===== 更简单的两人对话示例 =====

def simple_conversation():
    """简单的两 Agent 对话"""
    
    coder = AssistantAgent(
        name="Coder",
        system_message="你是一位 Python 开发者,简洁高效。"
    )
    
    tester = AssistantAgent(
        name="Tester",
        system_message="你是一位测试工程师,负责验证代码的正确性。"
    )
    
    # 测试场景:写一个排序函数
    tester.initiate_chat(
        coder,
        message="""
写一个 Python 函数,对列表进行排序并返回结果。
然后测试它是否能正确排序 [3, 1, 4, 1, 5, 9, 2, 6]
"""
    )

4. 生产级架构设计

4.1 整体架构

"""
多 Agent 系统的生产级架构

┌─────────────────────────────────────────────────────────────┐
│                      API Layer                               │
│              (FastAPI / Flask / 各种 API)                   │
└──────────────────────┬──────────────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────────────┐
│                   Router / Orchestrator                     │
│         任务路由、负载均衡、异常处理                         │
└──────────────────────┬──────────────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────────────┐
│                    Agent Runtime                            │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐            │
│  │  Agent-A   │  │  Agent-B   │  │  Agent-C   │            │
│  │  (专用)    │  │  (通用)    │  │  (专用)    │            │
│  └─────┬──────┘  └─────┬──────┘  └─────┬──────┘            │
│        │                │                │                   │
│  ┌─────▼────────────────▼────────────────▼─────┐            │
│  │              Tool Execution Layer           │            │
│  │   (搜索引擎 / 数据库 / API / 文件系统)       │            │
│  └─────────────────────────────────────────────┘            │
└──────────────────────┬──────────────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────────────┐
│                   Memory Layer                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                 │
│  │ 短期记忆  │  │ 长期记忆  │  │ 情节记忆  │                 │
│  │(上下文)   │  │(向量DB)   │  │(任务日志) │                 │
│  └──────────┘  └──────────┘  └──────────┘                 │
└─────────────────────────────────────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────────────┐
│                   Monitoring                               │
│          (日志 / 指标 / 追踪 / 告警)                        │
└─────────────────────────────────────────────────────────────┘
"""

# ===== 核心组件 =====

class AgentRuntime:
    """
    Agent 运行时
    管理 Agent 的生命周期
    """
    
    def __init__(self):
        self.agents: dict[str, Agent] = {}
        self.tools: dict[str, Callable] = {}
        self.memory = AgentMemory()
        self.monitor = Monitor()
    
    def register_agent(self, name: str, agent: Agent):
        """注册 Agent"""
        self.agents[name] = agent
    
    def register_tool(self, name: str, tool: Callable):
        """注册工具"""
        self.tools[name] = tool
    
    def execute_task(self, task: Task) -> Result:
        """执行任务"""
        
        # 1. 选择合适的 Agent
        agent = self._select_agent(task)
        
        # 2. 获取相关记忆
        context = self.memory.recall(task.description)
        
        # 3. 执行
        with self.monitor.track(agent.name, task.id):
            result = agent.execute(task, context)
        
        # 4. 存储结果到记忆
        self.memory.remember(task.description, result)
        
        return result
    
    def _select_agent(self, task: Task) -> Agent:
        """根据任务类型选择 Agent"""
        # 简单路由:按标签匹配
        for agent in self.agents.values():
            if any(tag in agent.capabilities for tag in task.tags):
                return agent
        
        # 默认返回通用 Agent
        return self.agents.get("general")

4.2 记忆系统

# memory_system.py

"""
Agent 记忆系统

多层记忆:
1. Working Memory:当前任务的上下文
2. Short-term Memory:当前会话的历史
3. Long-term Memory:持久化的知识(向量数据库)
"""

from dataclasses import dataclass
from typing import Optional
import time


@dataclass
class MemoryItem:
    """记忆条目"""
    content: str
    memory_type: str  # "working" | "short_term" | "long_term"
    importance: float  # 重要程度 0-1
    timestamp: float
    tags: list[str]


class AgentMemory:
    """
    Agent 记忆系统
    
    设计原则:
    - 重要信息 → 强化存储
    - 不重要信息 → 定期清理
    - 失败经验 → 长期记住
    """
    
    def __init__(self, vector_db=None):
        self.working_memory: list[str] = []  # 当前上下文
        self.short_term: list[MemoryItem] = []  # 短期记忆
        self.long_term: list[MemoryItem] = []  # 长期记忆(简化版,生产用向量数据库)
        self.vector_db = vector_db
    
    def remember(
        self,
        content: str,
        memory_type: str = "working",
        importance: float = 0.5,
        tags: list[str] = None
    ):
        """存储记忆"""
        
        item = MemoryItem(
            content=content,
            memory_type=memory_type,
            importance=importance,
            timestamp=time.time(),
            tags=tags or []
        )
        
        if memory_type == "working":
            self.working_memory.append(content)
            # 限制大小
            if len(self.working_memory) > 20:
                self.working_memory = self.working_memory[-20:]
        
        elif memory_type == "short_term":
            self.short_term.append(item)
            if len(self.short_term) > 100:
                self._compress_short_term()
        
        elif memory_type == "long_term":
            self.long_term.append(item)
            if self.vector_db:
                # 存入向量数据库
                pass
    
    def recall(self, query: str, limit: int = 5) -> list[str]:
        """检索记忆"""
        results = []
        
        # 工作记忆(直接返回)
        results.extend(self.working_memory[-limit:])
        
        # 短期记忆(关键词匹配)
        for item in self.short_term[-20:]:
            if query.lower() in item.content.lower():
                results.append(item.content)
        
        # 长期记忆(向量搜索)
        if self.vector_db:
            vector_results = self.vector_db.search(query, limit=limit)
            results.extend([r["content"] for r in vector_results])
        
        return results[:limit]
    
    def remember_episode(
        self,
        task: str,
        actions: list[str],
        result: str,
        success: bool,
        lesson: str = ""
    ):
        """记录情节记忆(任务执行记录)"""
        
        # 失败经验很重要,必须记住
        if not success:
            self.remember(
                content=f"任务「{task}」失败的教训:{lesson}",
                memory_type="long_term",
                importance=0.9  # 失败经验重要度高
            )
        
        # 成功经验也记住
        if success and lesson:
            self.remember(
                content=f"任务「{task}」成功的经验:{lesson}",
                memory_type="long_term",
                importance=0.7
            )
    
    def _compress_short_term(self):
        """压缩短期记忆,保留重要的"""
        # 按重要性排序
        sorted_memory = sorted(
            self.short_term,
            key=lambda x: x.importance,
            reverse=True
        )
        # 保留最重要的 50 条
        self.short_term = sorted_memory[:50]

5. 框架对比总结

特性CrewAIAutoGenLangGraph
定位任务导向对话导向状态机
上手难度⭐⭐⭐⭐⭐⭐⭐⭐⭐
灵活性最高
多 Agent 协作
监控/追踪一般一般需自己加
适合场景任务流水线对话协作复杂工作流

我的选择建议:

快速原型、业务流程 → CrewAI
多 Agent 对话、讨论 → AutoGen
复杂状态机、精确控制 → LangGraph
生产级、需要定制 → 组合使用 + 自定义

6. 踩坑和经验

坑1:Agent 之间通信要清晰

多个 Agent 协作时,最容易出问题是信息传递不清

# ❌ 容易出问题
task_b = Task(
    description="处理上一步的结果",
    context=[task_a]
)

# ✅ 明确说明需要什么
task_b = Task(
    description=f"""
    基于以下研究结果,撰写文章:
    
    {task_a.expected_output}
    
    注意:重点关注XX方面,忽略YY方面
    """,
    context=[task_a]
)

坑2:不要让 Agent 产生过多对话

多 Agent 对话很灵活,但也很容易陷入无限循环

解决:设置 max_round,并且设计好终止条件。

坑3:监控和日志必须做好

多 Agent 出问题很难排查,因为涉及多个 Agent 的交互。

必备:

  • 每个 Agent 的输入输出日志
  • 任务执行时间
  • 错误追踪

结语

多 Agent 协作是 Agent 发展的下一个重点方向。从单 Agent 到多 Agent,不只是数量上的变化,而是从「一个聪明的工具」到「一个协作团队」的质变。

框架只是工具,核心还是理解什么场景需要协作、怎么协作

希望这篇文章对你有帮助。下次聊点别的方向~


相关阅读:

AI Agent开发实战:ReAct / Plan-Execute / Reflexion 范式完整解析

前言: 最近在项目中落地 AI Agent,从最初的「LLM+工具调用」到现在的完整 Agent 架构,踩了不少坑。这篇文章把核心范式梳理清楚,配上可运行的代码,希望帮你少走弯路。


先说结论

做 Agent 这类项目,核心问题只有一个:LLM 怎么知道自己该干什么、该怎么干?

解决这个问题有三条成熟路径:

范式核心理念适合场景复杂度
ReAct边想边做,思考和行动交替工具调用、信息检索⭐⭐
Plan-and-Execute先想好再做,计划和执行分离复杂多步骤任务⭐⭐⭐
Reflexion做完了想一想,总结经验需要自我改进的任务⭐⭐⭐⭐

三条路不是非此即彼,可以组合使用。


1. 为什么需要 Agent 范式?

先看一个反常识的事实:直接让 LLM 调工具,效果往往很差。

比如你想让 LLM 帮你查资料然后写报告,最朴素的做法是:

用户: 帮我查一下最新的AI Agent进展
LLM: (调用搜索工具)
LLM: 好的,我查到了以下内容...(开始写)

这有什么问题?

问题1:LLM 可能不知道该不该调用工具

用户: 1+1等于多少
LLM: 调用搜索工具(query="1+1等于多少")

这种基础问题根本不需要搜索,但 LLM 可能会「想太多」。

问题2:LLM 可能不知道该用什么工具

LLM 面对「帮我查资料并写报告」这样的任务,它得决定:

  • 先搜索还是先写?
  • 用哪个搜索引擎?
  • 搜到多少条够用?

问题3:LLM 不会反思和修正

如果第一次搜索结果不满意,LLM 不会主动换关键词重试。

Agent 范式就是为了解决这三个问题。


2. ReAct 范式:边想边做

2.1 核心思想

ReAct = Reasoning + Action

Thought: 我需要先搜索"AI Agent最新进展"
Action: search(query="AI Agent最新进展 2024")
Observation: 搜索到10条结果...

Thought: 第一条看起来最相关,我来读一下
Action: browse(url="...")
Observation: 文章内容是...

Thought: 内容够用了,我来写报告
Action: answer("基于搜索结果的报告...")

关键点:Thought 是 LLM 的「自言自语」,让它解释为什么要调用这个工具。

2.2 完整代码实现

# react_agent.py

import json
from dataclasses import dataclass, field
from enum import Enum
from typing import Callable


class ActionType(Enum):
    """Agent 能执行的动作类型"""
    SEARCH = "search"
    BROWSE = "browse" 
    READ_FILE = "read_file"
    WRITE_FILE = "write_file"
    CODE = "execute_code"
    ANSWER = "answer"  # 最终回答,结束标志


@dataclass
class Step:
    """执行步骤记录"""
    step_num: int
    thought: str
    action: ActionType
    action_input: dict
    observation: str = ""


class ReActAgent:
    """
    ReAct 范式的 Agent 实现
    
    核心流程:
    1. Think: 让 LLM 分析当前状态,决定下一步
    2. Act: 执行选定的动作
    3. Observe: 获取执行结果
    4. 循环直到 LLM 说「回答完毕」
    """
    
    def __init__(
        self,
        llm_client,
        tools: dict[str, Callable],
        max_iterations: int = 15
    ):
        self.llm = llm_client
        self.tools = tools
        self.max_iterations = max_iterations
    
    def think(self, history: list[Step]) -> tuple[ActionType, str, dict]:
        """
        让 LLM 思考下一步该做什么
        返回: (动作类型, 思考过程, 动作参数)
        """
        
        # 构建历史上下文
        history_text = self._format_history(history)
        
        # 工具说明
        tool_desc = "\n".join([
            f"- {name}: {tool.__doc__ or '无描述'}"
            for name, tool in self.tools.items()
        ])
        
        prompt = f"""你是一个智能助手,正在帮用户完成任务。

请分析当前情况,决定下一步动作。

可用工具:
{tool_desc}

{history_text}

请严格按照以下 JSON 格式输出,不要输出其他内容:
{{
    "thought": "你的思考过程,解释为什么选择这个动作",
    "action": "工具名称(必须是可用工具之一) 或 'answer' 表示回答用户问题",
    "action_input": {{"参数名": "参数值"}} // 如果是 answer 则为空对象
}}
"""
        
        response = self.llm.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
            temperature=0,
            response_format={"type": "json_object"}
        )
        
        result = json.loads(response.choices[0].message.content)
        
        action_type = (
            ActionType.ANSWER 
            if result["action"] == "answer" 
            else ActionType(result["action"])
        )
        
        return action_type, result["thought"], result.get("action_input", {})
    
    def act(self, action_type: ActionType, action_input: dict) -> str:
        """执行动作"""
        
        if action_type == ActionType.ANSWER:
            return "FINAL_ANSWER"
        
        tool = self.tools.get(action_type.value)
        if not tool:
            return f"错误:工具 {action_type.value} 不存在"
        
        try:
            result = tool(**action_input)
            return str(result)
        except Exception as e:
            return f"执行错误:{str(e)}"
    
    def run(self, user_query: str) -> dict:
        """
        执行完整的 ReAct 循环
        """
        history: list[Step] = []
        
        for i in range(self.max_iterations):
            
            # Step 1: Think
            action_type, thought, action_input = self.think(history)
            
            # Step 2: Act
            observation = self.act(action_type, action_input)
            
            # 记录步骤
            step = Step(
                step_num=i + 1,
                thought=thought,
                action=action_type,
                action_input=action_input,
                observation=observation
            )
            history.append(step)
            
            # Step 3: 检查是否结束
            if action_type == ActionType.ANSWER:
                return {
                    "status": "success",
                    "steps": history,
                    "answer": history[-1].observation
                }
        
        return {
            "status": "max_iterations",
            "steps": history,
            "answer": "已达到最大迭代次数"
        }
    
    def _format_history(self, history: list[Step]) -> str:
        if not history:
            return "(暂无历史记录)"
        
        lines = []
        for step in history[-5:]:
            lines.append(
                f"步骤{step.step_num}:\n"
                f"  思考:{step.thought}\n"
                f"  动作:{step.action.value}({step.action_input})\n"
                f"  结果:{step.observation[:100]}..."
            )
        return "\n".join(lines)


# ===== 使用示例 =====

def demo():
    """演示 ReAct Agent 的使用"""
    
    # 模拟的工具函数
    def search(query: str) -> str:
        """搜索工具"""
        results = {
            "AI Agent最新进展": "1. Anthropic Claude Agent\n2. OpenAI Swarm\n3. AutoGen\n4. CrewAI",
            "AI Agent框架对比": "ReAct vs Plan-and-Execute vs Reflexion",
            "LLM工具调用": "Function Calling 是主流方案"
        }
        return results.get(query, f"搜索到关于「{query}」的内容...")
    
    def browse(url: str) -> str:
        """浏览网页"""
        return f"正在浏览 {url},获取到文章内容..."
    
    def calculate(expr: str) -> str:
        """计算器"""
        try:
            result = eval(expr)
            return str(result)
        except:
            return "计算错误"
    
    # 创建 Agent
    agent = ReActAgent(
        llm_client=None,  # 实际使用时传入真实的 LLM client
        tools={
            "search": search,
            "browse": browse,
            "calculate": calculate
        }
    )
    
    # 执行
    result = agent.run("1+1等于多少?")
    print(f"状态: {result['status']}")
    print(f"回答: {result['answer']}")
    
    # 打印执行过程
    print("\n执行过程:")
    for step in result["steps"]:
        print(f"\n步骤 {step.step_num}:")
        print(f"  思考: {step.thought}")
        print(f"  动作: {step.action.value}")


if __name__ == "__main__":
    demo()

2.3 ReAct 的实际效果

我在一个「技术博客助手」项目里用过 ReAct,效果对比:

场景不用 ReAct用 ReAct
简单问题经常调用搜索(浪费)直接回答
复杂问题执行一步忘一步思路清晰,可追溯
错误处理错了就错了可以重试、修正

最明显的改善:Agent 的行为变得可解释了。每一步在做什么、为什么这么做,都有记录。


3. Plan-and-Execute:先想好再做

3.1 核心思想

ReAct 是「边想边做」,Plan-and-Execute 是「先想好完整计划,再按计划执行」。

用户任务:帮我准备一场技术分享

Plan:
1. 确定主题 → [需要:用户确认]
2. 搜索相关资料 → [需要:搜索引擎]
3. 整理资料写成大纲 → [需要:大纲模板]
4. 写 PPT → [需要:PPT 工具]
5. 检查并优化 → [需要:用户确认]

和 ReAct 的区别:

ReActPlan-and-Execute
计划没有全局计划先制定完整计划
适应性灵活,可随时调整计划固定,灵活性差
可解释性每步可解释计划本身就很好看懂
适用场景简单、探索性任务复杂、流程固定任务

3.2 代码实现

# plan_and_execute.py

from dataclasses import dataclass
from typing import Optional
import json


@dataclass
class PlanStep:
    """计划步骤"""
    step_id: int
    task: str
    tool: Optional[str]
    params: dict
    depends_on: list[int] = None  # 依赖的前置步骤 ID
    result: str = ""


class PlanAndExecuteAgent:
    """
    Plan-and-Execute 范式的 Agent
    
    核心流程:
    1. Planning: 让 LLM 制定完整计划
    2. Execution: 按计划执行(处理依赖关系)
    3. Validation: 验证结果是否符合预期
    """
    
    def __init__(self, llm_client, executor):
        self.llm = llm_client
        self.executor = executor  # 实际执行器
    
    def planning(self, goal: str) -> list[PlanStep]:
        """
        制定执行计划
        """
        
        prompt = f"""用户目标:{goal}

请将这个目标分解为可执行的步骤。
要求:
1. 每个步骤应该清晰、具体
2. 每个步骤对应一个工具
3. 标注步骤之间的依赖关系

请按以下 JSON 格式返回:
{{
    "steps": [
        {{
            "step_id": 1,
            "task": "具体任务描述",
            "tool": "工具名称",
            "params": {{"参数名": "参数值"}},
            "depends_on": []  // 依赖的步骤 ID 列表
        }}
    ]
}}
"""
        
        response = self.llm.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
            temperature=0,
            response_format={"type": "json_object"}
        )
        
        result = json.loads(response.choices[0].message.content)
        
        steps = []
        for s in result.get("steps", []):
            steps.append(PlanStep(
                step_id=s["step_id"],
                task=s["task"],
                tool=s.get("tool"),
                params=s.get("params", {}),
                depends_on=s.get("depends_on", [])
            ))
        
        return steps
    
    def execute_plan(self, plan: list[PlanStep]) -> dict[int, str]:
        """
        执行计划
        处理步骤间的依赖关系
        """
        results: dict[int, str] = {}
        
        for step in plan:
            # 等待依赖完成
            while not all(dep in results for dep in step.depends_on):
                # 实际应该用异步,这里简化处理
                import time
                time.sleep(0.01)
            
            # 替换参数中的引用
            params = self._substitute_params(step.params, results)
            
            # 执行
            if step.tool:
                result = self.executor.execute(step.tool, params)
            else:
                result = "无工具,直接完成"
            
            results[step.step_id] = result
            
            print(f"✓ 步骤 {step.step_id} 完成: {step.task}")
        
        return results
    
    def _substitute_params(self, params: dict, results: dict) -> dict:
        """替换参数中的引用,比如 {2.output} 表示引用步骤2的输出"""
        import re
        
        new_params = {}
        for k, v in params.items():
            if isinstance(v, str):
                # 替换 {step_id.output_key} 格式
                matches = re.findall(r'\{(\d+)\.output\}', v)
                for step_id in matches:
                    step_id = int(step_id)
                    if step_id in results:
                        v = v.replace(f'{{{step_id}.output}}', results[step_id])
            new_params[k] = v
        return new_params
    
    def run(self, goal: str) -> dict:
        """完整的 Plan-and-Execute 流程"""
        
        print(f"🎯 目标: {goal}")
        print("📋 制定计划中...\n")
        
        # 1. 制定计划
        plan = self.planning(goal)
        
        print("📋 计划:")
        for step in plan:
            deps = f" (依赖步骤 {step.depends_on})" if step.depends_on else ""
            print(f"  {step.step_id}. {step.task}{deps}")
        
        print("\n🚀 开始执行...\n")
        
        # 2. 执行计划
        results = self.execute_plan(plan)
        
        # 3. 汇总
        final_answer = self._summarize(results, plan)
        
        return {
            "goal": goal,
            "plan": plan,
            "results": results,
            "final_answer": final_answer
        }
    
    def _summarize(self, results: dict, plan: list[PlanStep]) -> str:
        """汇总执行结果"""
        summary = []
        for step in plan:
            if step.step_id in results:
                summary.append(f"[步骤{step.step_id}] {step.task}: {results[step.step_id][:50]}...")
        return "\n".join(summary)


# ===== 使用示例 =====

def demo_plan_execute():
    """演示 Plan-and-Execute"""
    
    class SimpleExecutor:
        def execute(self, tool: str, params: dict) -> str:
            tools = {
                "search": f"搜索「{params.get('query', '')}」的结果",
                "write": f"写入文件: {params.get('filename', '')}",
                "format": f"格式化内容为 {params.get('format', '')}",
                "email": f"发送到邮箱: {params.get('to', '')}"
            }
            return tools.get(tool, "未知工具")
    
    agent = PlanAndExecuteAgent(
        llm_client=None,  # 实际使用传入
        executor=SimpleExecutor()
    )
    
    result = agent.run("帮我搜索 AI Agent 最新进展,写成文章保存,然后发给我")
    
    print("\n" + "="*50)
    print("最终结果:")
    print(result["final_answer"])


if __name__ == "__main__":
    demo_plan_execute()

4. Reflexion:做完了想一想

4.1 核心思想

Reflexion = Act + Observe + Reflect

第1次尝试:
  Action: search("AI Agent教程")
  Observe: 搜到了10条结果
  Reflect: 搜索关键词太宽泛,应该更具体

第2次尝试:
  Action: search("AI Agent开发实战 ReAct")
  Observe: 搜到了更精准的结果
  Reflect: 这次效果好,继续
  
第3次尝试:
  Action: browse("...")
  Observe: 找到了想要的内容
  Reflect: 任务完成,可以总结了

Reflexion 的关键创新:Agent 不仅观察结果,还评估「做得好不好」,并从错误中学习。

4.2 代码实现

# reflexion_agent.py

import json
from dataclasses import dataclass
from typing import Optional


@dataclass
class Reflection:
    """反思记录"""
    goal: str
    action: str
    observation: str
    evaluation: str  # "成功" / "部分成功" / "失败"
    analysis: str    # 失败原因分析
    strategy: str    # 改进策略
    should_retry: bool
    retry_hint: Optional[str]


class ReflexionAgent:
    """
    Reflexion 范式的 Agent
    
    核心流程:
    1. Execute: 执行动作
    2. Observe: 观察结果
    3. Reflect: 评估做得好不好
    4. 如果需要重试,根据反思调整后重试
    """
    
    def __init__(self, llm_client, tools: dict):
        self.llm = llm_client
        self.tools = tools
        
        # 记忆:存储历史反思
        self.reflections: list[Reflection] = []
    
    def reflect(
        self,
        goal: str,
        action: str,
        observation: str
    ) -> Reflection:
        """
        反思:评估动作效果,决定是否重试
        """
        
        prompt = f"""任务目标:{goal}

执行的动作:{action}

观察到的结果:{observation}

请进行自我反思:

1. 这个动作是否有效推进了目标?
2. 如果效果不好,问题出在哪里?
3. 如果要重试,如何改进?

请按 JSON 格式返回:
{{
    "evaluation": "成功 / 部分成功 / 失败",
    "analysis": "详细分析",
    "strategy": "改进策略(如果有)",
    "should_retry": true / false,
    "retry_hint": "具体如何重试"
}}
"""
        
        response = self.llm.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
            temperature=0,
            response_format={"type": "json_object"}
        )
        
        result = json.loads(response.choices[0].message.content)
        
        reflection = Reflection(
            goal=goal,
            action=action,
            observation=observation,
            evaluation=result["evaluation"],
            analysis=result["analysis"],
            strategy=result.get("strategy", ""),
            should_retry=result["should_retry"],
            retry_hint=result.get("retry_hint", "")
        )
        
        # 存储到记忆
        self.reflections.append(reflection)
        
        return reflection
    
    def get_relevant_reflections(self, goal: str) -> list[Reflection]:
        """获取与当前任务相关的历史反思"""
        # 简化版:返回最近的3条
        # 实际可以用向量相似度匹配
        return self.reflections[-3:]
    
    def run(self, goal: str, max_attempts: int = 3) -> dict:
        """完整的 Reflexion 流程"""
        
        history = []
        
        for attempt in range(max_attempts):
            
            # 获取相关反思经验
            relevant = self.get_relevant_reflections(goal)
            
            # 决定下一步动作
            action = self._decide_action(goal, history, relevant)
            
            # 执行
            observation = self._execute(action)
            
            # 反思
            reflection = self.reflect(goal, action, observation)
            
            history.append({
                "attempt": attempt + 1,
                "action": action,
                "observation": observation,
                "reflection": reflection
            })
            
            # 检查是否成功
            if reflection.evaluation == "成功":
                return {
                    "status": "success",
                    "result": observation,
                    "history": history,
                    "attempts": attempt + 1
                }
            
            # 如果失败且不应该重试
            if not reflection.should_retry:
                break
        
        return {
            "status": "failed",
            "result": "多次尝试后仍未成功",
            "history": history,
            "attempts": max_attempts
        }
    
    def _decide_action(
        self,
        goal: str,
        history: list,
        relevant_reflections: list[Reflection]
    ) -> str:
        """决定下一步动作(需要结合 LLM)"""
        
        reflection_hint = ""
        if relevant_reflections:
            reflection_hint = "\n\n之前的反思经验:\n"
            for r in relevant_reflections:
                if r.strategy:
                    reflection_hint += f"- {r.strategy}\n"
        
        prompt = f"""目标:{goal}
{reflection_hint}

请决定下一步动作(工具调用)。
"""
        
        # 实际应该调用 LLM 决定
        return "search(query='AI Agent最新进展')"
    
    def _execute(self, action: str) -> str:
        """执行动作"""
        # 实际根据 action 调用对应工具
        return "执行结果..."

5. 实战建议:怎么选、怎么用

5.1 选型建议

场景                           推荐范式
──────────────────────────────────────────────
简单的单步任务                  不需要 Agent
工具调用、信息检索              ReAct
复杂多步骤、流程固定           Plan-and-Execute  
需要自我改进、长程任务          Reflexion
复杂 + 需要改进                 ReAct + Reflexion

5.2 实际项目中我是怎么组合的

class HybridAgent:
    """
    混合 Agent:结合多种范式的优点
    """
    
    def __init__(self, llm_client, tools):
        self.llm = llm_client
        self.tools = tools
        self.react = ReActAgent(llm_client, tools)
        self.reflexion = ReflexionAgent(llm_client, tools)
    
    def run(self, task: str, mode: str = "auto"):
        """
        根据任务复杂度自动选择范式
        
        mode:
        - "auto": 自动选择
        - "react": 强制使用 ReAct
        - "reflexion": 强制使用 Reflexion
        """
        
        if mode == "auto":
            # 简单任务用 ReAct
            if self._is_simple_task(task):
                return self.react.run(task)
            # 复杂任务用 Reflexion
            else:
                return self.reflexion.run(task)
        
        elif mode == "react":
            return self.react.run(task)
        
        elif mode == "reflexion":
            return self.reflexion.run(task)
    
    def _is_simple_task(self, task: str) -> bool:
        """简单判断任务复杂度"""
        # 简单指示词
        simple_keywords = ["查", "找", "告诉", "回答"]
        complex_keywords = ["分析", "比较", "评估", "优化"]
        
        simple_score = sum(1 for k in simple_keywords if k in task)
        complex_score = sum(1 for k in complex_keywords if k in task)
        
        return simple_score > complex_score

6. 踩坑总结

做 Agent 这段时间,几个经验教训:

坑1:不要让 Agent 自己决定循环次数

LLM 不太擅长判断「我已经做够了」,容易陷入无限循环。加一个 max_iterations,并设置合理的上限。

坑2:工具描述要精确

LLM 靠工具描述来决定用什么工具。描述模糊会导致乱选工具。

# ❌ 不好
def search(query):
    """搜索"""

# ✅ 好
def search(query: str) -> str:
    """
    在网络上搜索信息。
    
    参数:
        query: 搜索关键词,建议简洁准确
    返回:
        搜索结果摘要(最多10条)
    用途:
        用于查找最新信息、技术文档、新闻等
    """

坑3:Observation 要结构化

不要只返回「查到了」这种模糊结果,要返回具体的数据结构,方便 LLM 后续处理。


结语

ReAct、Plan-and-Execute、Reflexion 这三种范式,没有绝对的优劣之分,关键是看场景。

我的经验是先从 ReAct 开始,够用就好。等遇到瓶颈了,再根据具体问题引入其他范式。

下篇文章聊聊多 Agent 协作——当单个 Agent 不够用的时候,怎么让多个 Agent 配合工作。


相关阅读: