AI 工具 ReAct

147 阅读6分钟

ReAct:让语言模型变聪明的“思考 + 行动”策略

在构建更智能的语言模型应用时,仅靠 LLM 自身并不足够。我们希望它不仅能“说得像人”,还要“像人一样做事”。这就引出了一个非常核心的理念——Agent(智能体) ,以及其中的一种典型策略:ReAct(Reason + Act)。


什么是 Agent?

**Agent(智能体)是一种通过结合语言模型(LLM)与外部工具/数据源,使得模型具有“观察 → 思考 → 行动 → 反馈”**能力的设计模式。

你可以把它看作是一种让大语言模型变得更加聪明、更加自主解决复杂任务的“套路”。核心思想是:

  1. 编写一个良好的 Prompt(提示词),定义模型行为的规则;
  2. 人类提问,LLM 接收到问题;
  3. 模型根据提示开始推理,并决定是否需要使用外部工具;
  4. 外部系统根据模型的请求调用函数,获得结果,再传回 LLM;
  5. LLM 综合多轮思考与工具反馈,得出最终答案。

整个过程就像一个闭环(loop)——模型不断地“思考 → 观察反馈 → 再思考”,直到完成任务。


###ReAct 框架:Reason + Act

ReAct 是一种特殊的 Agent 思路,它把两个关键点结合起来:

  • Reasoning(思考) :模型会用自然语言解释自己的想法,比如“我需要先查一下今天的天气”。
  • Acting(行动) :模型根据推理结果,主动调用工具或函数,比如调用 getWeather("Beijing")

这个框架的核心作用就是:

把一个大问题拆分成一系列小任务,逐步推理、逐步执行,每一步都可以借助外部资源。

ReAct 智能体工具的创建

  • 创建一个 agent.py 文件
# 导入所需的库
import json  # 用于处理JSON数据
from llm import client  # 导入LLM客户端
from prompt import REACT_PROMPT  # 导入预设的提示模板
from tools import get_closing_price,tools  # 导入工具函数
import re  # 导入正则表达式库

# 定义发送消息到LLM的函数
def send_messages(messages):
    """
    向LLM发送消息并获取响应
    :param messages: 消息列表
    :return: LLM的响应
    """
    response = client.chat.completions.create(
        model="deepseek-chat",  # 使用deepseek-chat模型
        messages=messages,  # 传入消息列表
        temperature=0.1,  # 设置温度参数,控制生成文本的随机性
    )
    return response

if __name__ == "__main__":
    # 设置助手的角色说明
    instructions = "你是一个股票助手,可以回答股票相关的问题"

    # 设置用户查询
    query = "青岛啤酒和贵州茅台的收盘价哪个贵?"
    
    # 使用模板构建完整的提示
    prompt = REACT_PROMPT.format(instructions=instructions,tools=tools,tool_names="get_closing_price",input=query)
    
    # 初始化消息列表
    messages = [{"role": "user", "content": prompt}]

    # 开始对话循环
    while True:
        # 发送消息并获取响应
        # print("发送消息到模型...", messages)

        response = send_messages(messages)
        response_text = response.choices[0].message.content

        # 打印模型的回复
        print("大模型的回复:")
        print(response_text)

        # 检查是否有最终答案
        # 使用正则表达式搜索回复文本中是否包含"Final Answer:",\s*匹配任意空白字符,(.*)捕获冒号后面的所有内容
        final_answer_match = re.search(r'最终答案:\s*(.*)', response_text)
        if final_answer_match:
            # 从正则匹配结果中提取第一个捕获组(括号内匹配到的内容),即"Final Answer:"后面的文本内容
            final_answer = final_answer_match.group(1)
            print("最终答案:", final_answer)
            # 如果有最终答案,结束对话
            break

        # 将模型的回复添加到消息历史
        messages.append(response.choices[0].message)

        # 解析模型回复中的动作和参数
        action_match = re.search(r'Action:\s*(\w+)', response_text)
        action_input_match = re.search(r'Action Input:\s*({.*?}|".*?")', response_text, re.DOTALL)  # 非贪婪匹配,匹配到第一个"}"或"""

        # 如果成功解析到动作和参数
        if action_match and action_input_match:
            tool_name = action_match.group(1)  # 获取工具名称
            params = json.loads(action_input_match.group(1))  # 解析参数
            print("工具名称:", tool_name)
            print("参数:", params)

            if tool_name == "get_closing_price":
                observation = get_closing_price(params["name"])  # 调用工具函数
                print("调用第三方API结果:", observation)
                # 将观察结果添加到消息历史
                messages.append({'role': 'user', 'content': f"observation:{observation}"})
  • 创建一个 llm.py文件
# 定义一个客户端,用于从 LLM 获取响应
from openai import OpenAI
import os
from dotenv import load_dotenv  # 加载环境变量
load_dotenv('.env.local')

client = OpenAI(
  api_key=os.getenv('DEEPSEEK_API_KEY'),
  base_url='https://api.deepseek.com/v1'
)
  • 创建一个 .env.local 文件
DEEPSEEK_API_KEY=sk-b0c7f1eddb97463fbbfdf61*********

创建一个 prompt.py 文件

# 使用三引号"""创建多行字符串,可以保留字符串中的换行和格式
REACT_PROMPT = """
{instructions}

TOOLS:

您可以使用以下工具:

{tools}

使用工具时,请使用以下格式:

思考: 我需要使用工具吗? 是
行动: 要采取的行动,必须是[{tool_names}]其中之一
行动输入: 该行动的输入内容

然后等待人类使用 Observation 回复您操作的结果。
... (这个思考/行动/行动输入/观察可以重复 N 次)
当您有回应要对人类说,或者不需要使用工具时,您必须使用以下格式:

思考: 我需要使用工具吗? 否
最终答案: [在此输入您的回应]

开始!

新输入: {input}

"""

创建文件 tools.py

# 工具列表,第三方函数的说明书
tools = [
  {
    "name": "get_closing_price",
    "description": "使用该工具获取指定股票的收盘价格",
    "parameters": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "股票名称,例如:贵州茅台、青岛啤酒等"
        }
      },
      "required": ["name"]
    }
  }
]


def get_closing_price(name):
  # 朝第三方 API 请求获取股票收盘价
  if name == "贵州茅台":
    return '1488.21'
  elif name == "青岛啤酒":
    return '67.92'
  else:
    return '未搜索到该股票'

创建文件 .gitignore

.env.local

ReAct 执行示例:

text
复制代码
Question: 明天适合出门吗?

Thought: 我需要查一下明天的天气
Action: getWeather("明天", "北京")
Observation: 明天是晴天,气温26Thought: 天气不错,适合出门
Final Answer: 适合

在这个流程中,模型:

  1. 表达了自己的思考(Thought)
  2. 调用了一个工具(Action)
  3. 接收了反馈(Observation)
  4. 最终形成结论(Final Answer)

背后是一个“思考-行动-反馈”的循环

这套机制背后的设计,就是一个 Agent 的典型“循环”结构:

  1. 模型分析人类提问
  2. 判断是否调用工具(用推理方式表示出来)
  3. 我们作为中间层接收 action 指令并实际调用工具
  4. 把工具结果传给模型
  5. 模型继续思考下一步,直到完成任务

这套机制可以用以下形式简化理解:

css
复制代码
[人类问题] → LLM(思考)→ 工具调用 → LLM(观察+思考)→ … → 最终答案

ReAct vs Function Calling:有什么不同?

虽然 ReAct 也在“调用工具”,但和现在主流的 Function Calling 有几个本质区别:

项目ReActFunction Calling
主体驱动LLM 自主判断是否调用外部系统触发调用逻辑
表达形式自然语言 Thought, Action, Observation结构化 JSON 格式函数调用
任务能力多步推理 + 调用,适合复杂 Agent单步调用,适合高效 API 集成
可解释性强(能看到每一步思考)弱(只看到结果)

ReAct 更适合构建具有“人类思维方式”的智能体,而 Function Calling 更适合在产品中快速接入外部工具和接口。


为什么 ReAct 很重要?

  • 它让 LLM 具备“自我规划”的能力

  • 具备清晰的逻辑链条,可解释性强

  • 能够处理复杂任务,比如:

    • 多轮问答
    • 数据分析
    • 自动网页搜索
    • 自动脚本执行
    • 电商推荐、规划路径等

总结

ReAct 并不仅仅是让 LLM“会调用工具”,而是让它变成一个更像人类的问题解决者(Problem Solver)

模型不再只是回答,而是在思考 → 行动 → 观察反馈 → 再思考中一步步逼近正确答案。