智能体设计范式:ReAct

智能体设计范式:ReAct

1 核心思想

ReAct(Reason + Act) 将推理(Reasoning)与行动(Action)交替进行。模型在每一步不仅进行思考(产生推理轨迹),还可以选择调用外部工具(如搜索引擎、计算器),然后根据工具返回的观察结果继续推理,如此循环,直到得出最终答案。

2 工作原理

  1. 系统提示词告诉模型可用的工具及输出格式(Thought/Action/Action Input)。
  2. 模型输出一个推理步骤(Thought),如果需要外部信息,则输出 Action(工具名)和 Action Input(工具参数)。
  3. 程序解析并执行该工具,获取 Observation(观察结果)。
  4. 把 Thought、Action、Observation 拼接回对话历史,继续下一次生成。
  5. 当模型输出 Final Answer 时结束循环,返回最终答案。

3 使用场景

  • 需要多步推理并结合实时信息的问题(如"2022年世界杯冠军的教练现在执教哪支球队?")。
  • 需要计算、搜索、数据库查询等的复杂问答。
  • 任何 LLM 单次推理无法可靠完成的、需要与环境交互的任务。

4 优缺点

优点

  • 推理过程透明、可解释。
  • 灵活调用外部工具,突破模型知识截止限制。
  • 通过中间推理降低幻觉,提高最终答案可靠性。

缺点

  • 每一步都要调用 LLM,延迟较高、成本较大。
  • 格式解析可能失败,导致行动执行错误。
  • 缺乏全局规划,容易在局部绕圈。

5 Python 实现

python 复制代码
import re
import requests
from typing import List, Dict

class AliYunLLM: ...  # 定义与前文相同

# ---------- 简单工具集 ----------
def search(query: str) -> str:
    """真实搜索:使用 DuckDuckGo 免费 API"""
    try:
        url = f"https://api.duckduckgo.com/?q={requests.utils.quote(query)}&format=json"
        resp = requests.get(url, timeout=5)
        data = resp.json()
        
        # 优先返回"AbstractText"摘要
        abstract = data.get("AbstractText", "")
        if abstract:
            return abstract
        
        # 如果没有摘要,尝试取第一个关联主题的文本
        related = data.get("RelatedTopics", [])
        if related and "Text" in related[0]:
            return related[0]["Text"]
        
        return f"未找到关于'{query}'的信息。"
    except Exception as e:
        return f"搜索出错:{e}"
        
def calculator(expression: str) -> str:
    """安全计算数学表达式"""
    try:
        # 仅允许数字、运算符、括号和小数点
        if re.match(r'^[\d\+\-\*/\(\)\.\s]+$', expression):
            result = eval(expression)
            return str(result)
        else:
            return "表达式包含非法字符"
    except Exception as e:
        return f"计算错误:{e}"

TOOLS = {
    "search": search,
    "calculator": calculator
}

# ---------- ReAct Agent ----------
class ReActAgent:
    def __init__(self, llm: AliYunLLM, max_steps: int = 5):
        self.llm = llm
        self.max_steps = max_steps
        self.history: List[Dict[str, str]] = []

    def _build_system_prompt(self) -> str:
        tool_descriptions = (
            "- search(query: str): 搜索互联网,返回相关信息。\n"
            "- calculator(expression: str): 计算数学表达式,返回结果字符串。"
        )
        return (
            "你是一个具备行动能力的智能助手。你可以使用以下工具:\n"
            f"{tool_descriptions}\n\n"
            "请严格按照以下格式输出(每行以 'Thought:', 'Action:', 'Action Input:', 'Observation:' 开头):\n"
            "Thought: 对当前情况的推理\n"
            "Action: 工具名称(search 或 calculator)\n"
            "Action Input: 工具的输入\n"
            "然后你会收到一个 Observation(观察结果),接着继续推理。\n"
            "当你获得最终答案时,请用以下格式结束:\n"
            "Thought: 我现在知道最终答案了\n"
            "Final Answer: 最终答案内容\n\n"
            "开始!"
        )

    def run(self, question: str) -> str:
        self.history = []
        prompt = f"问题:{question}\n"
        for step in range(self.max_steps):
            # 调用模型
            response = self.llm.generate(prompt, system_prompt=self._build_system_prompt())
            print(f"\n--- Step {step+1} 模型输出 ---\n{response}")
            self.history.append({"role": "assistant", "content": response})

            # 检查是否包含 Final Answer
            final_match = re.search(r"Final Answer:\s*(.*)", response, re.DOTALL)
            if final_match:
                return final_match.group(1).strip()

            # 解析 Action 和 Action Input
            action_match = re.search(r"Action:\s*(.*)", response)
            action_input_match = re.search(r"Action Input:\s*(.*)", response)
            if not action_match or not action_input_match:
                # 格式解析失败,提示重新输出
                prompt += f"{response}\nObservation: 输出格式错误,请按指定格式回复。\n"
                continue

            action = action_match.group(1).strip()
            action_input = action_input_match.group(1).strip()

            # 执行工具
            if action in TOOLS:
                observation = TOOLS[action](action_input)
            else:
                observation = f"未知工具:{action}"

            print(f"Observation: {observation}")
            # 将助手输出和观察结果追加到 prompt
            prompt += f"{response}\nObservation: {observation}\n"

        return "未能得出最终答案,请增大步骤数或调整问题。"

# ---------- 示例运行 ----------
if __name__ == "__main__":
    # 配置你自己的 API 信息
    llm = AliYunLLM(api_key="your-api-key", base_url="https://dashscope.aliyuncs.com/api/v1", model="qwen-plus")
    agent = ReActAgent(llm, max_steps=5)
    question = "2022年世界杯冠军的现任主教练是谁?"
    answer = agent.run(question)
    print(f"\n最终答案:{answer}")