AI Agent的主流设计模式之ReAct模式

ReAct模式结合了反射模式和工具使用模式,这使其成为当前AI Agent使用的最强大的模式之一。AI Agent既可以自我思考,自我纠错,还可以使用工具与世界交互。

python 复制代码
import json
import re
from typing import Dict, List, Any, Callable, Optional
import openai
from datetime import datetime


class ReActAgent:
    """
    ReAct (Reasoning + Acting) 模式AI Agent
    支持调用阿里云通义千问大模型,可扩展工具调用
    """

    def __init__(
            self,
            api_key: str,
            model: str = "qwen-max",
            base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1",
            max_iterations: int = 10,
            system_prompt: Optional[str] = None
    ):
        """
        初始化ReAct Agent

        Args:
            api_key: 阿里云API Key
            model: 模型名称 (qwen-max, qwen-plus, qwen-turbo等)
            base_url: API基础URL
            max_iterations: 最大迭代次数
            system_prompt: 系统提示词
        """
        self.client = openai.OpenAI(
            api_key=api_key,
            base_url=base_url
        )
        self.model = model
        self.max_iterations = max_iterations
        self.tools: Dict[str, Callable] = {}

        # 默认系统提示词
        self.system_prompt = system_prompt or """你是一个遵循ReAct模式的AI助手,通过思考、行动、观察的循环来解决问题。

可用工具:
{tool_descriptions}

请按以下格式回复:

思考:分析当前情况,决定下一步行动
行动:工具名称(参数),参数必须是JSON格式
观察:工具执行结果(由系统提供)

当任务完成时,输出:
思考:任务已完成
最终答案:你的最终回答

注意:
1. 每次只执行一个行动
2. 工具参数必须是有效的JSON格式
3. 根据观察结果继续思考,直到任务完成
"""

    def register_tool(self, name: str, func: Callable, description: str):
        """
        注册工具

        Args:
            name: 工具名称
            func: 工具函数
            description: 工具描述
        """
        self.tools[name] = {
            "func": func,
            "description": description
        }

    def _get_tool_descriptions(self) -> str:
        """获取工具描述文本"""
        if not self.tools:
            return "暂无可用工具"

        descriptions = []
        for name, info in self.tools.items():
            descriptions.append(f"- {name}: {info['description']}")
        return "\n".join(descriptions)

    def _build_prompt(self, user_input: str, history: List[Dict] = None) -> List[Dict]:
        """
        构建提示词

        Args:
            user_input: 用户输入
            history: 历史对话记录
        """
        messages = []

        # 系统提示词
        system = self.system_prompt.format(
            tool_descriptions=self._get_tool_descriptions()
        )
        messages.append({"role": "system", "content": system})

        # 添加历史对话
        if history:
            messages.extend(history)

        # 添加用户输入
        messages.append({"role": "user", "content": user_input})

        return messages

    def _parse_action(self, text: str) -> Optional[Dict[str, Any]]:
        """
        解析模型输出的行动指令

        Args:
            text: 模型输出文本

        Returns:
            解析后的行动指令,包含tool_name和parameters
        """
        # 匹配行动格式:工具名({"参数": "值"})
        pattern = r'行动:(\w+)\((.*?)\)'
        match = re.search(pattern, text, re.DOTALL)

        if match:
            tool_name = match.group(1)
            params_str = match.group(2).strip()

            try:
                # 尝试解析JSON参数
                parameters = json.loads(params_str) if params_str else {}
                return {
                    "tool_name": tool_name,
                    "parameters": parameters
                }
            except json.JSONDecodeError:
                # 如果不是JSON格式,作为字符串参数处理
                return {
                    "tool_name": tool_name,
                    "parameters": {"input": params_str}
                }

        return None

    def _extract_thought(self, text: str) -> Optional[str]:
        """提取思考内容"""
        pattern = r'思考:(.*?)(?=行动:|最终答案:|$)'
        match = re.search(pattern, text, re.DOTALL)
        return match.group(1).strip() if match else None

    def _extract_final_answer(self, text: str) -> Optional[str]:
        """提取最终答案"""
        pattern = r'最终答案:(.*?)$'
        match = re.search(pattern, text, re.DOTALL)
        return match.group(1).strip() if match else None

    def _execute_tool(self, tool_name: str, parameters: Dict) -> str:
        """
        执行工具

        Args:
            tool_name: 工具名称
            parameters: 工具参数

        Returns:
            工具执行结果
        """
        if tool_name not in self.tools:
            return f"错误:未知工具 '{tool_name}'"

        try:
            tool_func = self.tools[tool_name]["func"]
            result = tool_func(**parameters)
            return str(result)
        except Exception as e:
            return f"工具执行失败:{str(e)}"

    def _call_llm(self, messages: List[Dict]) -> str:
        """
        调用大模型

        Args:
            messages: 消息列表

        Returns:
            模型回复内容
        """
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                temperature=0.7,
                max_tokens=2000
            )
            return response.choices[0].message.content
        except Exception as e:
            raise Exception(f"调用大模型失败:{str(e)}")

    def run(self, user_input: str, verbose: bool = True) -> str:
        """
        运行ReAct Agent

        Args:
            user_input: 用户输入
            verbose: 是否打印执行过程

        Returns:
            Agent最终回答
        """
        # 初始化
        messages = self._build_prompt(user_input)
        iteration = 0
        full_response = ""

        if verbose:
            print(f"\n{'=' * 50}")
            print(f"用户: {user_input}")
            print(f"{'=' * 50}\n")

        # ReAct循环
        while iteration < self.max_iterations:
            iteration += 1

            # 调用大模型
            response = self._call_llm(messages)

            if verbose:
                print(f"\n--- 第 {iteration} 次迭代 ---")
                print(response)

            # 添加到消息历史
            messages.append({"role": "assistant", "content": response})
            full_response = response

            # 检查是否包含最终答案
            final_answer = self._extract_final_answer(response)
            if final_answer:
                if verbose:
                    print(f"\n✅ 任务完成!")
                return final_answer

            # 解析行动指令
            action = self._parse_action(response)
            if not action:
                # 没有行动指令,可能思考不完整,继续下一次迭代
                continue

            # 执行工具
            tool_result = self._execute_tool(
                action["tool_name"],
                action["parameters"]
            )

            if verbose:
                print(f"\n🔧 执行工具: {action['tool_name']}")
                print(f"📊 观察结果: {tool_result}")

            # 将观察结果添加到消息历史
            observation_msg = f"观察:{tool_result}"
            messages.append({"role": "user", "content": observation_msg})

        # 达到最大迭代次数
        return f"达到最大迭代次数({self.max_iterations}),任务未完成。\n最后状态:{full_response}"

    def run_stream(self, user_input: str):
        """
        流式运行(可选)
        """
        # 可以扩展实现流式输出
        pass


# ============= 示例工具函数 =============

def calculator(expression: str) -> float:
    """计算器工具:计算数学表达式"""
    try:
        # 安全计算,只允许基本的数学运算
        allowed_names = {
            k: v for k, v in math.__dict__.items()
            if not k.startswith("__")
        }
        allowed_names.update({"abs": abs, "round": round})

        result = eval(expression, {"__builtins__": {}}, allowed_names)
        return f"计算结果:{expression} = {result}"
    except Exception as e:
        return f"计算错误:{str(e)}"


def get_current_time(format: str = "%Y-%m-%d %H:%M:%S") -> str:
    """获取当前时间"""
    return datetime.now().strftime(format)


def search_weather(city: str) -> str:
    """模拟天气查询(实际可接入真实天气API)"""
    # 这里用模拟数据演示
    weather_data = {
        "北京": "晴,25°C,空气质量良",
        "上海": "多云,28°C,空气质量优",
        "广州": "阵雨,30°C,空气质量优",
        "深圳": "雷阵雨,29°C,空气质量良"
    }
    return weather_data.get(city, f"未找到{city}的天气信息")


def get_stock_price(code: str) -> str:
    """模拟股票查询"""
    # 模拟数据
    stocks = {
        "000001": "平安银行 12.50元",
        "600036": "招商银行 35.80元",
        "9988": "阿里巴巴 110.50港元"
    }
    return stocks.get(code, f"未找到代码{code}的股票信息")


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

def main():
    """主函数示例"""

    # 1. 配置阿里云API Key(从环境变量获取或直接填写)
    import os

    # 方式1:从环境变量获取(推荐)
    API_KEY = os.getenv("DASHSCOPE_API_KEY")

    # 方式2:直接填写(仅用于测试,注意安全)
    # API_KEY = "sk-你的阿里云API-KEY"

    if not API_KEY:
        print("请设置环境变量 DASHSCOPE_API_KEY")
        print("或在代码中直接填写API_KEY")
        return

    # 2. 创建Agent
    agent = ReActAgent(
        api_key=API_KEY,
        model="qwen-max",  # 可选:qwen-max, qwen-plus, qwen-turbo
        max_iterations=5
    )

    # 3. 注册工具
    agent.register_tool(
        "calculator",
        calculator,
        "计算数学表达式,参数expression为数学表达式字符串,如:'2 + 3 * 4'"
    )

    agent.register_tool(
        "get_time",
        get_current_time,
        "获取当前时间,参数format为可选的时间格式"
    )

    agent.register_tool(
        "weather",
        search_weather,
        "查询天气,参数city为城市名称"
    )

    agent.register_tool(
        "stock",
        get_stock_price,
        "查询股票价格,参数code为股票代码"
    )

    # 4. 测试不同场景

    print("\n" + "=" * 60)
    print("ReAct Agent 示例 - 调用阿里大模型")
    print("=" * 60)

    # 示例1:简单计算
    print("\n【示例1】计算问题")
    result = agent.run("计算 (25 + 37) * 2 等于多少?")
    print(f"\n最终答案:{result}")

    # 示例2:多步任务
    print("\n【示例2】多步任务:查询北京天气,然后计算温度加上10度是多少?")
    result = agent.run("帮我查一下北京的天气,然后把温度数值加上10度")
    print(f"\n最终答案:{result}")

    # 示例3:混合查询
    print("\n【示例3】混合查询:现在几点了?查询平安银行股价")
    result = agent.run("告诉我当前时间,然后查一下平安银行的股票价格")
    print(f"\n最终答案:{result}")


# ============= 简版ReAct实现(自定义提示词) =============

def simple_react_example():
    """
    简版ReAct实现 - 展示核心原理
    使用自定义提示词让大模型遵循ReAct模式
    """

    API_KEY = os.getenv("DASHSCOPE_API_KEY")
    if not API_KEY:
        print("请设置API_KEY")
        return

    client = openai.OpenAI(
        api_key=API_KEY,
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
    )

    # ReAct提示词模板
    react_prompt = """请通过思考、行动、观察的循环来解决用户问题。

可用工具:
- calculator(expression): 计算数学表达式
- get_weather(city): 查询城市天气

请按以下格式输出:

思考:[分析当前情况]
行动:[工具名称]([参数JSON])
观察:[等待系统返回]

当任务完成时,输出:
最终答案:[答案]

用户问题:{question}

开始:"""

    def call_llm(messages):
        response = client.chat.completions.create(
            model="qwen-max",
            messages=messages,
            temperature=0.7
        )
        return response.choices[0].message.content

    # 模拟工具执行
    def execute_tool(action_text):
        if "calculator" in action_text:
            # 简化解析
            return "计算结果:42"
        elif "get_weather" in action_text:
            return "北京:晴,25°C"
        return "未知工具"

    # 模拟ReAct循环
    question = "北京天气多少度?这个温度乘以2是多少?"
    messages = [{"role": "user", "content": react_prompt.format(question=question)}]

    print(f"用户:{question}\n")

    for i in range(3):  # 最多3轮
        print(f"\n--- 第{i + 1}轮 ---")

        response = call_llm(messages)
        print(f"模型:{response}")

        if "最终答案" in response:
            print("\n✅ 任务完成")
            break

        if "行动" in response:
            # 模拟执行工具
            observation = execute_tool(response)
            print(f"观察:{observation}")

            # 添加观察结果到对话
            messages.append({"role": "assistant", "content": response})
            messages.append({"role": "user", "content": f"观察:{observation}"})


if __name__ == "__main__":
    # 需要安装openai库:pip install openai

    # 运行完整示例
    main()

    # 或者运行简版示例
    # simple_react_example()
相关推荐
椒颜皮皮虾྅1 小时前
OpenVINO C# API 中文README.md
人工智能·深度学习·目标检测·计算机视觉·c#·边缘计算·openvino
火红色祥云1 小时前
Python机器学习入门与实战_笔记
笔记·python·机器学习
唐璜Taro2 小时前
Function Calling介绍
python
xin_yao_xin2 小时前
PDF 转 图片(python)
python·pdf
天云数据2 小时前
Transform Yourself,Random Walk:AI重构时代的认知、工具与组织革命
人工智能·重构
石牌桥网管2 小时前
正则表达式:匹配不包含指定字符串的文本
java·javascript·python·正则表达式·go·php
XXYBMOOO2 小时前
在 Windows 上免费测试 OpenClaw:完整安装与本地 demo 模型体验指南
人工智能·windows
前端摸鱼匠2 小时前
YOLOv8使用 Ultralytics 内置功能简化格式转换:介绍如何使用 yolo mode=data 等相关功能或辅助工具来加速和简化数据格式的准备工作
人工智能·yolo·目标检测·机器学习·目标跟踪·视觉检测