不同Agent模式的应用

ReAct、Plan-and-Execute、Reflection 三种 Agent 架构模式原理与代码实战

Function Call 是 Agent 的"手"和"脚",而 Agent 架构模式则是它的"大脑思考方式"。ReAct 是边想边做,Plan-and-Execute 是谋定后动,Reflection 是三思而行。本节我们将深入每种模式的"思维回路",剖析 Function Call 在其中扮演的角色与调用节奏,并提供完全标准化、可直接运行的代码实现,让你不仅能用,更能"设计"出最适合业务场景的智能体。

学习目标

完成本节学习后,学员将能够:

  • 深刻理解 ReAct、Plan-and-Execute、Reflection 三种主流 Agent 架构模式的核心工作原理与思维范式
  • 掌握 Function Call 在不同模式中的调用时机、频率与控制策略
  • 独立实现基于 ReAct、Plan-and-Execute 和 Reflection 模式的标准化、生产级 Agent 原型

一、为什么需要不同的 Agent 架构模式?

在上一节,我们手写了一个具备"感知-决策-行动-反馈"循环的通用 Agent。它很灵活,但对于复杂任务,它可能表现得像一个"莽撞的实习生"——拿到任务就立刻动手,缺乏全局规划,容易在细节中迷失方向。

提问:当学生问"请帮我制定一个为期一个月的三角函数复习计划,并每周推送一套练习题"时,通用 Agent 会怎么做?

答案:它可能会立刻调用 generate_exercise(topic="三角函数", count=5),然后结束。因为它没有"计划"的概念。

不同模式的本质:不同的 Agent 架构模式,本质上是为了解决不同类型的问题而设计的"思考策略"。它们定义了 LLM 应该在何时、以何种方式调用 Function Call。

问题类型 推荐模式 原因
简单、线性任务(如:“识别这张图”) 通用循环 / ReAct 一步到位,无需复杂规划
复杂、多步骤任务(如:“制定复习计划”) Plan-and-Execute 需要先拆解任务,再分步执行
高精度、容错性高(如:“批改这道压轴题”) Reflection 执行后需要反思结果,确保正确性

二、ReAct 模式

2.1 核心原理与工作流程

ReAct (Reason + Act) 是最经典、最接近人类直觉的 Agent 模式。它由 Google 在 2022 年提出,其核心思想是让 LLM 在"推理"(Reasoning)和"行动"(Action)之间交替进行,逐步逼近问题的答案。

工作流程详解:

  1. 初始感知 (Perception):接收用户输入。
  2. 第一轮推理 (Reasoning):LLM 分析问题,思考"我现在需要做什么?",并输出一段推理文字(Thought)。
  3. 第一轮行动 (Action):基于推理,LLM 决定调用一个 Function Call 工具。
  4. 观察 (Observation):程序执行工具,将结果(Observation)返回给 LLM。
  5. 第二轮推理 (Reasoning):LLM 基于上一轮的行动结果,进行新一轮的思考,“我得到了XXX结果,接下来我应该…"。
  6. 第二轮行动 (Action):再次调用 Function Call。
  7. 循环… 重复 4-6 步,直到 LLM 认为任务完成,输出最终答案 (Final Answer)。

数据流格式(关键):为了让 LLM 理解这种交替模式,我们需要在 messages 中构造特定的格式。通常使用 Thought/Action/Observation 三元组。

2.2 Function Call 应用策略

调用节奏:高频、短平快。每个推理步骤后,几乎都会紧跟一个 Function Call。

控制策略:

  • 由 LLM 主导:LLM 通过输出 Thought 来决定下一步调用哪个工具。开发者只需提供工具列表。
  • 循环终止:当 LLM 的回复中不再包含 function_call,而是直接给出 Final Answer 时,循环结束。
  • 防死循环:必须设置最大轮次(如 5 轮),防止 LLM 陷入无限推理。

适用场景:信息检索、多跳问答、工具链式调用(A 工具的结果是 B 工具的输入)。

2.3 代码实现与案例解析

我们以"解答一道数学题"为例,演示 ReAct 协议的模式。

import openai
import json
import re
import os
from typing import List, Dict, Any, Tuple, Optional

class StrictReActAgent:
    def __init__(self, client: openai.OpenAI, model: str = "qwen-plus"):
        self.client = client
        self.model = model
        self.max_turns = 5  # 防止无限循环
        # 定义可用的"动作"(注意:这里不再是OpenAI的functions,而是我们自己的协议)
        self.available_actions = {
            "ocr_service": self._mock_ocr_service,
            "search_knowledge": self._mock_search_knowledge,
            "calculate_expression": self._mock_calculate_expression,
            "final_answer": self._handle_final_answer
        }

    def run(self, user_input: str) -> str:
        """
        运行 ReAct Agent。
        """
        print(f"【用户提问】: {user_input}")
        print("=" * 60)
        # 初始化对话历史,注入系统提示词
        messages = [
            {"role": "system", "content": self._get_react_system_prompt()},
            {"role": "user", "content": user_input}
        ]
        turn = 0
        final_answer = None
        while turn < self.max_turns:
            turn += 1
            print(f"\n>>> 第 {turn} 轮推理与行动 <<<")
            # 调用 LLM
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                # **关键:不传递任何 functions,让 LLM 纯文本输出**
            )
            message = response.choices[0].message
            assistant_reply = message.content.strip()
            print(f"[LLM 原始输出]:\n{assistant_reply}\n")
            # 解析 LLM 的回复
            thought, action, action_input = self._parse_react_output(assistant_reply)
            if not thought or not action or not action_input:
                print("[错误] 无法解析 LLM 输出,格式不符合 ReAct 协议。")
                break
            print(f"【Thought】: {thought}")
            print(f"【Action】: {action}")
            print(f"【Action Input】: {action_input}")
            # 将 LLM 的完整回复加入历史
            messages.append({
                "role": "assistant",
                "content": assistant_reply
            })
            # 检查是否为最终答案
            if action == "final_answer":
                final_answer = action_input.get("answer", "未提供具体答案")
                print(f"【Final Answer】: {final_answer}")
                break
            # 执行动作
            if action in self.available_actions:
                observation = self.available_actions[action](action_input)
            else:
                observation = {"error": f"未知动作: {action}"}
            observation_str = json.dumps(observation, ensure_ascii=False)
            print(f"【Observation】: {observation_str}")
            # 将观察结果作为用户消息加入历史,驱动下一轮
            messages.append({
                "role": "user",
                "content": f"Observation: {observation_str}"
            })
        if not final_answer:
            final_answer = "处理过程超时或出错,未能生成最终答案。"
        return final_answer

    def _get_react_system_prompt(self) -> str:
        """返回 ReAct 模式的系统提示词"""
        return """
你是一个严谨的数学解题助手,必须严格使用 ReAct 模式工作。
你的每一步思考和行动都必须遵循以下精确格式:
Thought: <你当前的思考、推理或计划>
Action: <你要执行的动作名称,必须是以下工具之一: ocr_service, search_knowledge, calculate_expression, final_answer>
Action Input: <动作的输入参数,必须是一个合法的JSON字符串>
可用工具说明:
1. ocr_service: 用于识别学生上传的图片中的题目文本。参数: {"image_path": "字符串,图片的服务器路径"}
2. search_knowledge: 用于查询解题所需的公式或定理。参数: {"keywords": "字符串,搜索关键词"}
3. calculate_expression: 用于计算数学表达式。参数: {"expression": "字符串,要计算的表达式,如 'sqrt(25)' 或 'sin(pi/4)'"}
4. final_answer: 当你得出最终答案时使用。参数: {"answer": "字符串,你的最终答案和解释"}
重要规则:
- 每次回复必须且只能包含一个 Thought、一个 Action 和一个 Action Input。
- Action 的值必须是上述四个工具名之一,不能是其他任何值。
- Action Input 必须是严格的 JSON 格式字符串,不能有任何额外字符。
- 不要输出任何其他文本、解释或问候语。
- 如果你认为任务已完成,请使用 final_answer 工具。
现在,请开始处理用户的请求。
"""

    def _parse_react_output(self, text: str) -> Tuple[Optional[str], Optional[str], Optional[Dict[str, Any]]]:
        """
        解析 LLM 的输出,提取 Thought, Action, Action Input.
        使用正则表达式进行严格匹配。
        """
        # 定义正则模式
        # (?s) 表示让 . 匹配换行符
        pattern = r"(?s)Thought:\s*(.*?)\s*Action:\s*(.*?)\s*Action Input:\s*(.*)"
        match = re.search(pattern, text, re.IGNORECASE)
        if not match:
            return None, None, None
        thought = match.group(1).strip()
        action = match.group(2).strip()
        action_input_str = match.group(3).strip()
        # 尝试解析 Action Input 为 JSON
        try:
            action_input = json.loads(action_input_str)
        except json.JSONDecodeError:
            return thought, action, None
        return thought, action, action_input

    # --- 模拟的工具函数 ---
    def _mock_ocr_service(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """模拟OCR服务"""
        image_path = params.get("image_path", "")
        print(f"  [执行] OCR 识别: {image_path}")
        if "math" in image_path.lower():
            return {
                "success": True,
                "text": "题目: 在三角形ABC中,已知角A为60度,边AB长为5,边AC长为7,求边BC的长度。"
            }
        else:
            return {"success": False, "error": "图像识别失败或内容不包含数学题"}

    def _mock_search_knowledge(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """模拟知识查询"""
        keywords = params.get("keywords", "")
        print(f"  [执行] 知识查询: {keywords}")
        if "余弦定理" in keywords or "cosine" in keywords.lower():
            return {
                "success": True,
                "formula": "余弦定理公式: BC² = AB² + AC² - 2 × AB × AC × cos(∠A)",
                "description": "用于计算已知两边及其夹角的三角形的第三边。"
            }
        else:
            return {"success": False, "error": "未找到相关知识点"}

    def _mock_calculate_expression(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """模拟计算服务"""
        expression = params.get("expression", "")
        print(f"  [执行] 计算表达式: {expression}")
        # 这里是极度简化的模拟,实际应使用 SymPy 等库
        try:
            if "sqrt(39)" in expression:
                result = "√39 (约等于 6.245)"
            elif "**2" in expression:
                parts = expression.split("**2")
                if len(parts) == 2 and parts[0].isdigit():
                    num = int(parts[0])
                    result = str(num ** 2)
                else:
                    result = "计算结果"
            else:
                result = "计算结果"
            return {"success": True, "result": result}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def _handle_final_answer(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """处理最终答案,这里只是透传"""
        return {"final_answer": params.get("answer", "")}

# --- 测试运行 ---
if __name__ == "__main__":
    # 初始化OpenAI客户端 (请替换为你的API Key和Base URL)
    client = openai.OpenAI(
        api_key=os.getenv("DASHSCOPE_API_KEY"),
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    )
    agent = StrictReActAgent(client, model="qwen-plus")
    # 用户提问
    user_query = "老师,这道几何题我不会做,图片在 /uploads/geometry_math_001.jpg,能教我吗?"
    # 运行 Agent
    final_result = agent.run(user_query)
    print("\n" + "="*60)
    print("【EduMentor 最终回复】")
    print("="*60)
    print(final_result)

三、Plan-and-Execute 模式

3.1 核心原理与工作流程

Plan-and-Execute (计划与执行) 模式将任务分解为两个清晰的阶段:Planning (计划) 和 Execution (执行)。它先让 LLM 从宏观角度审视整个任务,制定一个详细的、分步骤的计划,然后再按计划一步步调用工具执行。

工作流程详解:

  1. 任务规划 (Planning)
    • LLM 接收用户输入。
    • LLM 输出一个完整的、结构化的计划。这个计划通常是一个步骤列表 (List of Steps)。
    • 关键:在规划阶段,LLM 不调用任何 Function Call!它只是"想”,不"做"。
  2. 计划解析 (Plan Parsing):程序代码解析 LLM 生成的计划,将其转化为一个可执行的任务队列。
  3. 步骤执行 (Execution)
    • 程序按顺序遍历任务队列。
    • 对于队列中的每一步,构造一个"子问题"提示词,提交给 LLM。
    • LLM 针对这个"子问题",决定是否调用 Function Call 工具。
    • 执行工具,获取结果。
    • 将结果记录下来,作为下一步的上下文。
  4. 最终总结 (Final Summary):所有步骤执行完毕后,可以再调用一次 LLM,让它基于所有中间结果,生成一个最终的、整合性的回复。

3.2 Function Call 应用策略

调用节奏:零频。Function Call 在执行阶段完全由程序代码直接调用,LLM 不参与决策。

控制策略:

  • 计划驱动:Function Call 的调用完全由预先生成和解析的计划决定。执行是确定性的。
  • 阶段隔离:Planning 阶段禁止 Function Call,确保计划的纯粹性。
  • 错误处理:如果某一步执行失败,可以选择重试、跳过或终止整个计划。

适用场景:复杂项目管理、多步骤工作流、需要强一致性和可预测性的任务(如生成周报、制定学习计划)。

3.3 代码实现与案例解析

我们以"规划旅行的智能助手"为例。

import openai
import json
import re
import os
from typing import Dict, Any, Optional
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())

class PlanAndExecuteAgent:
    def __init__(self, client: openai.OpenAI, model: str = "qwen-plus"):
        self.client = client
        self.model = model
        # 定义工具函数 (程序直接调用,不由LLM在执行阶段选择)
        self.tools = {
            "get_weather": self._get_weather,
            "suggest_activity": self._suggest_activity
        }

    def run(self, user_input: str):
        print(f"【用户提问】: {user_input}")
        print("=" * 60)
        # --- 阶段1: 任务规划 (Planning) - LLM 输出 JSON 计划 ---
        print("\n[阶段1: 任务规划]")
        plan_json = self._create_plan(user_input)
        print(f"解析后的结构化计划: {json.dumps(plan_json, ensure_ascii=False, indent=2)}")
        if not plan_json or "steps" not in plan_json:
            return "未能生成有效的执行计划。"
        # --- 阶段2: 步骤执行 (Execution) - 程序直接调用工具,不咨询LLM! ---
        print("\n[阶段2: 步骤执行]")
        execution_results = {}  # 存储每个步骤结果,key: step_id
        steps = plan_json["steps"]  # 每个步骤: {"id": int, "tool": str, "args": dict, "dependencies": [int]}
        for step in steps:
            step_id = step["id"]
            print(f"\n>>> 执行步骤 {step_id}: {step['tool']}({step['args']}) <<<")
            # 检查依赖 (简单验证前步结果存在且成功)
            if not self._check_dependencies(step, execution_results):
                result = {"error": "依赖步骤未完成或失败", "success": False}
                print(f"  [执行失败] 错误: {result['error']}")
            else:
                # **核心: 程序直接调用工具**
                if step['tool'] in self.tools:
                    try:
                        result = self.tools[step['tool']](**step['args'])
                        print(f"  [执行成功] 结果: {result}")
                    except Exception as e:
                        result = {"error": str(e), "success": False}
                        print(f"  [执行失败] 错误: {result['error']}")
                else:
                    result = {"error": f"未知工具: {step['tool']}", "success": False}
                    print(f"  [执行失败] 错误: {result['error']}")
            # 跟踪 status
            execution_results[step_id] = {"result": result, "success": result.get("success", False)}
        # --- 阶段3: 最终总结 - LLM 作为汇报官 ---
        print("\n[阶段3: 生成最终回复]")
        print(f'execution_results-->{execution_results}')
        final_reply = self._generate_final_reply(user_input, execution_results)
        print(f"【最终回复】:\n{final_reply}")
        return final_reply

    def _create_plan(self, user_input: str) -> Optional[Dict[str, Any]]:
        """让LLM生成 JSON 计划 (结构化输出,确保规划完整指定工具/参数/依赖)"""
        # 定义 JSON 示例字符串,避免 f-string 解析问题
        plan_example = """{
          "steps": [
            {"id": 1, "tool": "get_weather", "args": {"city": "Beijing", "date": "2025-09-24"}, "dependencies": []},
            {"id": 2, "tool": "suggest_activity", "args": {"weather": "Sunny", "city": "Beijing"}, "dependencies": [1]}
          ]
        }"""
        planning_prompt = f"""
                            你是一个出行规划师。请为以下请求制定一个详细、可行的执行计划。
                            计划以JSON格式输出,包括步骤列表,每个步骤指定id、tool、args、dependencies。
                            可用工具:
                            - get_weather: 获取天气,args: {{"city": string, "date": string (YYYY-MM-DD)}}
                            - suggest_activity: 建议活动,args: {{"weather": string, "city": string}}
                            请求: {user_input}
                            输出格式:
                            {plan_example}
                            重要: 只输出JSON,不要额外解释。dependencies 是前置步骤id列表,如果无依赖则[]。
                            """
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": planning_prompt}],
            # 关键: 规划阶段不提供任何工具,确保LLM只输出JSON计划
        )
        plan_text = response.choices[0].message.content.strip()
        # 从文本提取JSON
        try:
            json_match = re.search(r'\{.*\}', plan_text, re.DOTALL)
            # print(f'json_match-->{json_match}')
            if json_match:
                return json.loads(json_match.group(0))
            return json.loads(plan_text)
        except json.JSONDecodeError:
            print("JSON解析失败,返回空计划")
            return None

    def _check_dependencies(self, step: Dict[str, Any], results: Dict[int, Dict[str, Any]]) -> bool:
        """简单检查依赖 (验证前步结果存在且成功)"""
        for dep_id in step.get("dependencies", []):
            if dep_id not in results or not results[dep_id]["success"]:
                return False
        return True

    # --- 工具函数实现 (由程序直接调用) ---
    def _get_weather(self, city: str, date: str) -> Dict[str, Any]:
        """获取天气 (模拟)"""
        # 模拟天气数据
        return {
            "success": True,
            "city": city,
            "date": date,
            "weather": "Sunny" if "Beijing" in city else "Rainy",
            "temperature": "25°C"
        }

    def _suggest_activity(self, weather: str, city: str) -> Dict[str, Any]:
        """根据天气建议活动 (模拟)"""
        if "Sunny" in weather:
            activity = "Go for a park walk or outdoor sightseeing."
        else:
            activity = "Visit indoor museums or shopping malls."
        return {
            "success": True,
            "city": city,
            "weather": weather,
            "suggested_activity": activity
        }

    def _generate_final_reply(self, original_query: str, results: Dict[int, Dict[str, Any]]) -> str:
        """生成最终回复 (整合执行统计)"""
        # 计算统计
        total_steps = len(results)
        success_count = sum(1 for res in results.values() if res["success"])
        summary_prompt = f"""
                            原始请求: {original_query}
                            执行结果:
                            {json.dumps(results, ensure_ascii=False, indent=2)}
                            执行统计: 总步骤 {total_steps},成功 {success_count}
                            请基于以上信息,给用户一个友好、清晰的最终回复,总结已完成的工作。如果有失败,简单说明。
                            """
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": summary_prompt}]
        )
        return response.choices[0].message.content

# --- 测试 ---
if __name__ == "__main__":
    client = openai.OpenAI(
        api_key=os.getenv("DASHSCOPE_API_KEY"),
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    )
    agent = PlanAndExecuteAgent(client)
    query = "帮我规划明天在北京的出行计划,根据天气建议活动。"
    agent.run(query)

四、Reflection 模式

4.1 核心原理与工作流程

Reflection (反思) 模式的核心思想是在执行完一个或多个步骤后,主动停下来进行反思,评估当前结果的质量或正确性,并根据反思结果决定是继续、修正还是重新执行。它特别适用于对结果准确性要求极高的场景。

Reflection 反思模式流程

工作流程详解:

  1. 初始执行:采用 ReAct 或 Plan-and-Execute 模式完成初步任务。
  2. 触发反思:在关键节点(如得到最终答案前、或执行完一个复杂步骤后),主动调用一个"反思"工具或提示词。
  3. 反思评估:LLM 基于当前的所有信息(用户问题、执行过程、中间结果)进行批判性思考,回答诸如"这个答案合理吗?"、“有没有遗漏的步骤?"、“计算过程有错误吗?“等问题。
  4. 决策调整
    • 如果反思结果是正面的(“答案正确”),则输出最终答案。
    • 如果反思结果是负面的(“第二步计算有误”),则回滚到出错的步骤,重新执行或调用新工具修正。
    • 可能会触发多轮反思,直到结果令人满意或达到最大反思次数。

4.2 Function Call 应用策略

调用节奏:不定频、事件驱动。Function Call 的重新调用是由"反思"的结论触发的,而非预设的流程。

控制策略:

  • 反思触发器:需要明确定义何时进行反思(例如,每次得到一个数值结果后,或在输出最终答案前)。
  • 反思提示词:设计高质量的反思提示词是关键,它决定了反思的深度和有效性。
  • 状态回滚:系统需要有能力将对话状态或数据状态回滚到之前的某个检查点,以便重新执行。

适用场景:数学/物理题求解、代码生成与调试、法律/医疗等高风险领域的咨询。

4.3 代码实现与案例解析

我们以"求解一道物理题"为例,演示如何在 Plan-and-Execute 模式的基础上,嵌入 Reflection 模块,实现"解题-验算"的双重保障。

import openai
import json
import re
import os
import math
from typing import List, Dict, Any, Optional, Tuple
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())

class Reflection:
    """
    一个融合了 Plan-and-Execute 与 Reflection 模式的物理题求解器。
    核心亮点:在输出最终答案前,进行主动反思,确保答案100%正确。
    """
    def __init__(self, client: openai.OpenAI, model: str = "qwen-plus"):
        self.client = client
        self.model = model
        self.max_reflections = 3  # 最大反思次数,防死循环
        # 定义工具函数 (程序直接调用)
        self.tools = {
            "identify_problem": self._identify_problem,
            "lookup_formula": self._lookup_formula,
            "calculate_result": self._calculate_result,
            "generate_answer": self._generate_answer
        }

    def run(self, user_input: str):
        print(f"【用户提问】: {user_input}")
        print("=" * 80)
        # --- 阶段1: 任务规划 (Planning) ---
        print("\n[阶段1: 任务规划]")
        plan_json = self._create_plan(user_input)
        if not plan_json or "steps" not in plan_json:
            return "未能生成有效的解题计划。"
        print(f" LLM 生成的结构化计划:")
        print(json.dumps(plan_json, ensure_ascii=False, indent=2))
        # --- 阶段2: 步骤执行 (Execution) ---
        print("\n[阶段2: 步骤执行]")
        execution_results = {}  # {step_id: {"result": ..., "success": bool}}
        for step in plan_json["steps"]:
            step_id = step["id"]
            tool_name = step["tool"]
            args = step["args"]
            dependencies = step.get("dependencies", [])
            print(f"\n>>> 执行步骤 {step_id}: {tool_name} <<<")
            print(f"    参数: {args}")
            print(f"    依赖: {dependencies}")
            # 检查依赖是否满足
            if not self._check_dependencies(dependencies, execution_results):
                result = {"error": f"依赖步骤 {dependencies} 未完成或失败", "success": False}
                print(f" [执行失败] 原因: {result['error']}")
            else:
                # 提取依赖数据,注入到当前步骤参数中
                enriched_args = self._inject_dependency_data(args, execution_results)
                # 程序直接调用工具
                if tool_name in self.tools:
                    try:
                        result = self.tools[tool_name](**enriched_args)
                        print(f"  [执行成功] 结果: {result}")
                    except Exception as e:
                        result = {"error": str(e), "success": False}
                        print(f"  [执行失败] 错误: {result['error']}")
                else:
                    result = {"error": f"未知工具: {tool_name}", "success": False}
                    print(f"  [执行失败] 错误: {result['error']}")
            # 记录执行结果
            execution_results[step_id] = {"result": result, "success": result.get("success", False)}
        # 提取初步答案 (假设最后一步是 generate_answer)
        preliminary_answer = self._extract_preliminary_answer(execution_results)
        if not preliminary_answer:
            return "未能生成初步答案。"
        print(f"\n[初步答案]: {preliminary_answer}")
        # --- 阶段3: 反思评估 (Reflection) ---
        print("\n[阶段3: 反思评估 - 启动质量检查]")
        final_answer = self._reflect_and_refine(user_input, plan_json, execution_results, preliminary_answer)
        # --- 阶段4: 最终总结 ---
        print("\n[阶段4: 生成最终回复]")
        final_reply = self._generate_final_reply(user_input, final_answer)
        print(f"\n【最终答案】\n{final_reply}")
        return final_reply

    # --- 反思模块核心方法 ---
    def _reflect_and_refine(self, question: str, plan: Dict[str, Any], results: Dict[int, Dict[str, Any]], preliminary_answer: str) -> str:
        """
        反思与修正主循环。
        优化点:在每次反思后立即判断,若正确则立刻返回,避免无谓循环。
        """
        current_answer = preliminary_answer
        # 如果最大反思次数为0,则跳过反思,直接返回初步答案
        if self.max_reflections <= 0:
            return current_answer
        for reflection_count in range(1, self.max_reflections + 1):
            print(f"\n>>> 第 {reflection_count} 轮反思 <<<")
            # 1. 生成反思提示词
            reflection_prompt = self._build_reflection_prompt(question, plan, results, current_answer)
            # 2. 调用 LLM 进行反思 (强制 JSON 输出)
            reflection_result = self._call_llm_for_reflection(reflection_prompt)
            print(f" [反思报告]: {json.dumps(reflection_result, ensure_ascii=False, indent=2)}")
            # 3. **核心优化:立即判断反思结果**
            if reflection_result.get("is_correct", False):
                print(f" [反思通过] 答案被验证为正确。")
                return current_answer  # **立即返回,不再进行后续循环!**
            else:
                critique = reflection_result.get("critique", "未提供具体批评")
                suggestion = reflection_result.get("suggestion", "无修正建议")
                print(f" [反思发现问题] 批评: {critique}")
                print(f" [修正建议] {suggestion}")
                # 4. 根据建议修正答案
                current_answer = self._apply_correction(current_answer, suggestion)
                print(f" [已修正] 新答案: {current_answer}")
                # 如果这是最后一次反思,无论结果都返回
                if reflection_count == self.max_reflections:
                    print(f" [达到最大反思次数] 返回最后一次修正的答案。")
        return current_answer

    def _build_reflection_prompt(self, question: str, plan: Dict[str, Any], results: Dict[int, Dict[str, Any]],
                                 answer: str) -> str:
        """构建反思提示词"""
        return f"""
                你是一位严谨的物理教授,正在对一道物理题的解答进行质量审查。
                原始问题: {question}
                解题计划:
                {json.dumps(plan, ensure_ascii=False, indent=2)}
                执行过程与结果:
                {json.dumps(results, ensure_ascii=False, indent=2)}
                初步答案: {answer}
                请进行严格评估,并回答以下问题:
                1. 这个答案在物理原理和数学计算上是否绝对正确?请指出任何潜在的错误(如:公式用错、单位遗漏、符号错误、数值计算失误)。
                2. 解题过程是否完整?是否有遗漏的关键步骤?
                3. 答案是否符合物理常识?(例如:速度不能超光速,能量不能为负等)
                
                请用JSON格式回复,包含以下字段:
                - "is_correct": boolean (答案是否完全正确)
                - "critique": string (具体的、详细的批评和分析,指出错误所在)
                - "suggestion": string (具体的修正建议,如果需要重新计算,请说明如何修改)
                """

    def _call_llm_for_reflection(self, prompt: str) -> Dict[str, Any]:
        """调用LLM进行反思,强制返回JSON"""
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[{"role": "user", "content": prompt}],
                response_format={"type": "json_object"}  # 强制JSON输出
            )
            content = response.choices[0].message.content
            return json.loads(content)
        except Exception as e:
            # 反思失败,默认认为答案正确,避免阻塞流程
            print(f"    [反思调用失败] {e}")
            return {"is_correct": True, "critique": "反思过程出错", "suggestion": ""}

    def _apply_correction(self, current_answer: str, suggestion: str) -> str:
        """
        根据反思建议修正答案。
        这是一个简化实现。在真实系统中,这里可能触发重新执行某个计算步骤。
        """
        # 示例:如果建议提到"符号错误",我们手动修正
        if "符号" in suggestion and "负" in suggestion:
            if "14.00 m/s" in current_answer:
                return current_answer.replace("14.00 m/s", "-14.00 m/s")
            elif "14.0 m/s" in current_answer:
                return current_answer.replace("14.0 m/s", "-14.0 m/s")
        # 示例:如果建议提到"单位",我们修正单位
        if "单位" in suggestion and "km/h" in suggestion:
            if "m/s" in current_answer:
                # 简单模拟换算
                parts = current_answer.split(" ")
                for i, part in enumerate(parts):
                    if "m/s" in part:
                        try:
                            value = float(part.replace("m/s", ""))
                            kmh_value = value * 3.6
                            parts[i] = f"{kmh_value:.2f} km/h"
                        except:
                            pass
                return " ".join(parts)
        # 默认:在答案前加上"[修正后]"
        return f"[修正后] {current_answer}"

    # --- Plan-and-Execute 的核心方法 ---
    def _create_plan(self, user_input: str) -> Optional[Dict[str, Any]]:
        """让LLM生成一个结构化的解题计划 (JSON格式)"""
        planning_prompt = f"""
                            你是一个严谨的物理老师。请为以下物理问题制定一个详细的、分步骤的解题计划。
                            计划必须以JSON格式输出,包含步骤列表。每个步骤必须指定id、tool、args和dependencies。
                            
                            可用工具说明 (请严格按照工具名和参数名):
                            - identify_problem: 从题目文本中提取关键物理量。args: {{"problem_text": str}}
                            - lookup_formula: 根据问题类型查找物理公式。args: {{"problem_type": str}} (如"自由落体"、"牛顿第二定律")
                            - calculate_result: 根据公式和数据进行计算。args: {{"formula": str, "variables": dict}} (如 {{"g": 9.8, "h": 10}})
                            - generate_answer: 生成最终的、面向学生的答案文本。args: {{"calculation_result": number, "unit": str, "problem_summary": str}}
                            
                            请求: {user_input}
                            
                            输出格式 (示例):
                            {{
                              "steps": [
                                {{"id": 1, "tool": "identify_problem", "args": {{"problem_text": "用户原始问题"}}, "dependencies": []}},
                                {{"id": 2, "tool": "lookup_formula", "args": {{"problem_type": "自由落体"}}, "dependencies": [1]}},
                                {{"id": 3, "tool": "calculate_result", "args": {{"formula": "v = sqrt(2gh)", "variables": {{"g": 9.8, "h": 10}}}}, "dependencies": [1, 2]}},
                                {{"id": 4, "tool": "generate_answer", "args": {{"calculation_result": 14.0, "unit": "m/s", "problem_summary": "求落地速度"}}, "dependencies": [3]}}
                              ]
                            }}
                            
                            重要: 只输出JSON,不要任何额外解释。确保dependencies正确反映步骤间的依赖关系。
                            """
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[{"role": "user", "content": planning_prompt}],
            )
            plan_text = response.choices[0].message.content.strip()
            # 尝试提取并解析JSON
            json_match = re.search(r'\{.*\}', plan_text, re.DOTALL)
            if json_match:
                return json.loads(json_match.group(0))
            return json.loads(plan_text)
        except (json.JSONDecodeError, Exception) as e:
            print(f"JSON解析失败: {e}")
            return None

    def _check_dependencies(self, dependencies: List[int], results: Dict[int, Dict[str, Any]]) -> bool:
        """检查所有依赖步骤是否都已成功完成"""
        for dep_id in dependencies:
            if dep_id not in results or not results[dep_id]["success"]:
                return False
        return True

    def _inject_dependency_data(self, args: Dict[str, Any], results: Dict[int, Dict[str, Any]]) -> Dict[str, Any]:
        """
        将依赖步骤的结果数据,注入到当前步骤的参数中。
        例如,步骤3的args中可能有占位符 "$step_1.mass$",我们将其替换为步骤1的实际输出值。
        """
        enriched_args = args.copy()
        for key, value in args.items():
            if isinstance(value, str) and value.startswith("$") and value.endswith("$"):
                # 解析占位符,如 "$step_1.mass$"
                parts = value[1:-1].split(".")  # 去掉$,按.分割
                if len(parts) == 2 and parts[0].startswith("step_"):
                    dep_step_id = int(parts[0].split("_")[1])
                    dep_field = parts[1]
                    if dep_step_id in results and results[dep_step_id]["success"]:
                        dep_result = results[dep_step_id]["result"]
                        if dep_field in dep_result:
                            enriched_args[key] = dep_result[dep_field]
                        else:
                            raise ValueError(f"依赖步骤 {dep_step_id} 的结果中缺少字段: {dep_field}")
                    else:
                        raise ValueError(f"依赖步骤 {dep_step_id} 未成功执行")
        return enriched_args

    def _extract_preliminary_answer(self, results: Dict[int, Dict[str, Any]]) -> Optional[str]:
        """从执行结果中提取初步答案"""
        # 按步骤ID排序,取最后一个成功的、包含"answer"字段的结果
        for step_id in sorted(results.keys(), reverse=True):
            data = results[step_id]
            if data["success"] and "answer" in data["result"]:
                return data["result"]["answer"]
        return None

    # --- 工具函数实现 ---
    def _identify_problem(self, problem_text: str) -> Dict[str, Any]:
        """从题目中提取关键物理量"""
        # 简化实现:假设题目是标准格式
        # 在真实项目中,这里可以用NER模型或规则引擎
        data = {
            "mass": 2.0,  # kg
            "height": 10.0,  # m
            "gravity": 9.8,  # m/s²
            "problem_type": "自由落体",
            "target": "落地速度"
        }
        return {"success": True, **data}

    def _lookup_formula(self, problem_type: str) -> Dict[str, Any]:
        """根据问题类型返回物理公式"""
        formulas = {
            "自由落体": "v = sqrt(2 * g * h)",
            "匀加速直线运动": "v = v0 + a * t",
            "动能定理": "W = 0.5 * m * v^2"
        }
        formula = formulas.get(problem_type, "未知公式")
        return {"success": True, "formula": formula}

    def _calculate_result(self, formula: str, variables: Dict[str, float]) -> Dict[str, Any]:
        """执行计算 (增强版:支持多种公式写法)"""
        # 策略1: 根据公式的核心关键词判断
        if "sqrt(2" in formula and ("g" in formula and "h" in formula):
            # 提取 g 和 h 的值
            g = variables.get("g", variables.get("gravity", 9.8))
            h = variables.get("h", variables.get("height", 0.0))
            # 执行核心计算逻辑
            result = math.sqrt(2 * g * h)
            return {"success": True, "raw_value": result, "unit": "m/s"}
        # 策略2: 如果有其他公式类型,可以在这里添加 elif 分支
        # elif "v = v0 + a * t" in formula:
        #     ... 计算匀加速运动 ...
        # 如果都不匹配,返回错误
        return {"success": False, "error": f"不支持的公式或参数: {formula}, {variables}"}

    def _generate_answer(self, calculation_result: float, unit: str, problem_summary: str) -> Dict[str, Any]:
        """生成最终答案"""
        answer_text = f"根据物理公式计算,{problem_summary}为 **{calculation_result:.2f} {unit}**。"
        return {"success": True, "answer": answer_text}

    def _generate_final_reply(self, original_query: str, final_answer: str) -> str:
        """生成最终回复"""
        summary_prompt = f"""
                            原始问题: {original_query}
                            最终答案: {final_answer}
                            请用亲切、鼓励的语气,向学生解释这个答案,并简要回顾一下解题的关键步骤和反思过程,强调严谨思考的重要性。
                            """
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[{"role": "user", "content": summary_prompt}]
            )
            return response.choices[0].message.content
        except Exception as e:
            return f"生成最终回复时出错: {e}"

# --- 测试运行 ---
if __name__ == "__main__":
    # 配置您的 OpenAI 客户端 (请替换为您的 API Key)
    client = openai.OpenAI(
        api_key=os.getenv("DASHSCOPE_API_KEY"),
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    )
    agent = Reflection(client, model="qwen-plus")
    # 用户提问 (一个典型的易错题)
    user_query = "一个物体从10米高的地方自由下落,求它落地时的速度大小。忽略空气阻力。(g=9.8m/s²)"
    # 运行 Agent
    agent.run(user_query)

五、混合模式:教育场景的终极武器

在真实的教育应用中,单一模式往往难以应对所有需求。混合模式 (Hybrid Mode) 是将多种 Agent 模式组合使用,发挥各自优势。

5.1 组合策略解析

任务类型 推荐混合模式 Function Call 策略
制定学期学习规划 Plan-and-Execute + ReAct 先用 P&E 制定宏观计划(每月目标),再用 ReAct 执行每周/每日的具体任务(推送题目、安排答疑)。
智能批改与深度辅导 ReAct + Reflection 用 ReAct 快速调用 OCR 和答案比对工具得出初步批改结果;再用 Reflection 对学生的典型错误进行深度反思,生成个性化讲解。
复杂问题求解辅导 Reflection + Plan-and-Execute 先用 Reflection 确保每一步推理和计算的正确性;对于需要多工具协作的子任务,用 P&E 模式确保执行流程的清晰。

四种模式完整对比:

模式 核心思想 Function Call 节奏 优势 劣势 教育场景应用
ReAct 边推理边行动 高频、交替 灵活、适应性强、适合探索性任务 缺乏全局观、可能陷入局部最优、易受幻觉影响 智能答疑、错题原因即时分析
Plan-and-Execute 先计划,后执行 低频、集中、受控 结构清晰、可预测性强、适合复杂流程 计划可能僵化、无法应对突发变化、前期规划成本高 制定学习计划、批量作业处理、课程内容编排
Reflection 执行后反思修正 不定频、事件驱动 结果可靠性高、容错性强、适合高精度任务 执行效率低、可能陷入反思循环、实现复杂 高难度题目批改、解题过程验证、个性化深度辅导
混合模式 组合拳 动态调整 取长补短、应对复杂多变场景 系统设计复杂、调试难度大 构建完整的 EduMentor 智能教育助手

5.2 设计原则

  1. 分层设计:将大任务分解,不同层级采用不同模式。顶层用 P&E 做规划,底层用 ReAct 做执行。
  2. 事件驱动:用 Reflection 作为"质量检查关卡”,在关键节点自动触发。
  3. 模式切换:设计一个"模式路由器”,根据当前任务的性质(简单/复杂、高风险/低风险)动态选择最合适的模式。

六、本节小结

本节深入剖析了 ReAct、Plan-and-Execute、Reflection 三种主流 Agent 架构模式的核心工作原理,并提供了完整的代码实现。关键要点:

  • ReAct 适合探索性、实时性任务,高频调用 Function Call
  • Plan-and-Execute 适合复杂流程、需要可预测性的任务,规划阶段不调用 Function Call
  • Reflection 适合高精度要求任务,事件驱动式调用 Function Call
  • 混合模式 是真实业务场景的终极方案,取长补短应对复杂需求