ReAct:(Reasoning + Acting,推理与行动)

从零手写了一个 ReAct范式的 Agent

本文以最简单的逻辑代码为基础,用浅显易懂的语言,描述ReAct范式是啥

核心机制:ReAct 循环工作流

agent遵循的一般范式:

用户提问 -> [Thought 思考] -> [Action 发起工具调用] -> [PAUSE 暂停并等待]
                                                              │
回答用户 <- [Answer 输出] <- [Observation 喂回结果] <- [执行 Python 函数]

1. 基础准备与客户端测试

from langchain_openai import ChatOpenAI
import os
from load_dotenv import load_dotenv
load_dotenv()

if os.environ.get("OPENAI_API_KEY"):
    print("Bro API KEY Variable exists")
else :
    raise ValueError("OPENAI_API_KEY not found")

from langchain_core.prompts import PromptTemplate
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, PydanticOutputParser


llm_openai = ChatOpenAI(model = "gpt-5-mini-2025-08-07", temperature=0)

代码作用:加载环境变量(读取 .env 中的 OPENAI_API_KEY),初始化 OpenAI SDK 客户端。

2. 核心类定义:带有记忆的 Agent 封装

class Agent:
    def __init__(self, system=""):
        self.system = system
        self.messages = []
        if self.system:
            self.messages.append({"role": "system", "content":system})

    def __call__(self, message):
        self.messages.append({"role": "user", "content": message})
        result = self.execute()
        self.messages.append({"role": "assistant", "content": result})
        return result

    def execute(self):
        completion = llm_openai.invoke(self.messages)
        return completion.content

设计巧思:

  • 对话上下文维护(self.messages):大模型本身是没有记忆模块,Agent 类通过一个list维护了从 system prompt 到每一次 user 和 assistant 的完整对话历史。

  • 可调用对象(call):重写了 call 方法,使得实例可以像函数一样被直接调用(例如 abot(“你好”))。每次调用都会把输入追加到对话历史中,向 OpenAI 发送请求,并记录大模型的返回内容。

  • 确定性输出(temperature=0):在上一节中将温度设为 0,确保大模型的推理和工具选择高度稳定、可预测。

3. Prompt 引导与工具定义(Agent 的“大脑”与“双手”)

脑部指令(System Prompt)

prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.

Your available actions are:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary

average_dog_weight:
e.g. average_dog_weight: Collie
returns average weight of a dog when given the breed

Example session:

Question: How much does a Bulldog weigh?
Thought: I should look the dogs weight using average_dog_weight
Action: average_dog_weight: Bulldog
PAUSE

You will be called again with this:

Observation: A Bulldog weights 51 lbs

You then output:

Answer: A bulldog weights 51 lbs
""".strip()

prompt 变量定义了 ReAct 的少样本规则(Few-Shot Few-Examples),告诉大模型必须严格按照 Thought -> Action -> PAUSE 的格式输出,并在观察完成后输出 Answer。

手头的工具

def calculate(what):
    return eval(what)

def average_dog_weight(name):
    if name in "Scottish Terrier": 
        return("Scottish Terriers average 20 lbs")
    elif name in "Border Collie":
        return("a Border Collies average weight is 37 lbs")
    elif name in "Toy Poodle":
        return("a toy poodles average weight is 7 lbs")
    else:
        return("An average dog weights 50 lbs")

known_actions = {
    "calculate": calculate,
    "average_dog_weight": average_dog_weight
}
  • calculate:利用 Python 的 eval() 实时计算数学表达式。
  • average_dog_weight:模拟一个查询数据库或 API 的函数,返回犬种体重。
  • known_actions(工具注册表):这是一个字典,建立了文本名称(如 “calculate”)到实际 Python 函数对象(如 calculate)的映射关系。

4. 正则解析与自治循环引擎

action_re = re.compile('^Action: (\w+): (.*)$')   # python regular expression to selection action
def query(question, max_turns=5):
    i = 0
    bot = Agent(prompt)
    next_prompt = question
    while i < max_turns:
        i += 1
        result = bot(next_prompt)
        print(result)
        actions = [
            action_re.match(a) 
            for a in result.split('\n') 
            if action_re.match(a)
        ]
        if actions:
            # There is an action to run
            action, action_input = actions[0].groups()
            if action not in known_actions:
                raise Exception("Unknown action: {}: {}".format(action, action_input))
            print(" -- running {} {}".format(action, action_input))
            observation = known_actions[action](action_input)
            print("Observation:", observation)
            next_prompt = "Observation: {}".format(observation)
        else:
            return

核心作用:Agent 的“动力引擎(Execution Engine)”,实现真正的自动化。
原理解析

  • 正则提取(action_re):从大模型返回的文本中,精准提取出 Action: 工具名: 参数(例如从 Action: calculate: 37 + 20 中提取出 calculate 和 37 + 20)。
  • 死循环/最大轮次防护(max_turns=5):防止 Agent 进入死循环或无限消耗 Token,设置熔断保护。
  • 控制流反转:执行 Python 函数得到结果(observation),包装成 Observation: xxx 的格式追加回 Prompt,驱动 Agent 进行下一轮思考。

使用示例

调用 query(“I have 2 dogs, a border collie and a scottish terrier. What is their combined weight”),底层发生多轮的交互互动:

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                        用户提出复杂问题                                                    │   
│ "I have 2 dogs, a border collie and a scottish terrier.  What is their combined weigh?" │
└─────────────────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
  【第 1 轮 Loop】
  • LLM 思考:需要先查 Border Collie 体重。
  • LLM 输出:
    Thought: I should look up the weight of a Border Collie.
    Action: average_dog_weight: Border Collie
    PAUSE
  • Python 引擎拦截:匹配到 Action,暂停调用 LLM!
  • 执行 Python 函数:average_dog_weight("Border Collie") -> "37 lbs"
  • 反馈观察结果:Observation: a Border Collies average weight is 37 lbs
                                    │
                                    ▼
  【第 2 轮 Loop】
  • LLM 思考:已知边牧 37lbs,还需要查 Scottish Terrier 体重。
  • LLM 输出:
    Thought: Now I need to look up the weight of a Scottish Terrier.
    Action: average_dog_weight: Scottish Terrier
    PAUSE
  • Python 引擎拦截:执行 average_dog_weight("Scottish Terrier") -> "20 lbs"
  • 反馈观察结果:Observation: Scottish Terriers average 20 lbs
                                    │
                                    ▼
  【第 3 轮 Loop】
  • LLM 思考:两只狗体重已知(37 和 20),需要计算 37 + 20。
  • LLM 输出:
    Thought: I need to calculate 37 + 20.
    Action: calculate: 37 + 20
    PAUSE
  • Python 引擎拦截:执行 calculate("37 + 20") -> 57
  • 反馈观察结果:Observation: 57
                                    │
                                    ▼
  【第 4 轮 Loop】
  • LLM 思考:所有信息收集完毕,计算完成,可以给出最终答案了。
  • LLM 输出(不含 Action):
    Answer: The combined weight of a Border Collie and a Scottish Terrier is 57 lbs.
  • Python 引擎检测:没有匹配到 Action,退出 while 循环,任务完成!
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐