ReAct 模式:推理 + 行动#

什么是 ReAct?#

ReAct = Reasoning + Acting

ReAct 是一种让大模型交替进行「思考」和「行动」的框架。每次行动后,模型会观察结果,再决定下一步。这是构建智能 Agent 的主流方式。

核心循环#

思考(Thought) → 行动(Action) → 观察(Observation) → 思考 → ...

优势#

  • 可解释性:每一步都有明确的思考过程

  • 灵活性:可以根据观察结果动态调整策略

  • 鲁棒性:遇到错误可以换一种方式重试

本章将从手动文本解析版本,逐步过渡到使用 Function Calling 的现代实现。

import json
import os
import re
import litellm
litellm.drop_params = True
from dotenv import load_dotenv

load_dotenv()

MODEL = os.getenv("LLM_MODEL")
print(f"使用模型:{MODEL}")
# gpt-5/o系列不支持自定义temperature值,统一用安全wrapper
def _c(**kw):
    _m = kw.get('model', MODEL)
    if any(_m.startswith(p) for p in ('openai/gpt-5','openai/o1','openai/o3','openai/o4')):
        kw.pop('temperature', None)
    return litellm.completion(**kw)
使用模型:openai/gpt-5-mini

Section 1:ReAct 提示格式#

经典 ReAct 格式要求模型以固定的文本结构输出,方便程序解析。

格式规范:

  • Thought: 模型的推理过程

  • Action: 要调用的工具名称

  • Action Input: 工具的输入参数

  • Observation: (程序填入)工具返回结果

  • Final Answer: 最终回答用户的内容

# ── 工具定义 ──────────────────────────────────────────────

def search(query: str) -> str:
    """模拟搜索引擎(返回假数据用于演示)"""
    results = {
        "北京天气": "北京今日晴,气温 18°C,风力 3 级,适合户外活动",
        "上海天气": "上海今日多云,气温 22°C,风力 2 级,湿度较高",
        "量子计算": "量子计算利用量子力学原理进行计算,比传统计算机更快",
    }
    for key, val in results.items():
        if key in query:
            return val
    return f"未找到关于 '{query}' 的相关信息"


def calculate(expr: str) -> str:
    """安全计算数学表达式"""
    try:
        # 只允许数字和基本运算符
        allowed = set("0123456789+-*/()., ")
        if not all(c in allowed for c in expr):
            return "错误:表达式包含非法字符"
        result = eval(expr)  # noqa: S307  # 生产中请用更安全的方式
        return str(result)
    except Exception as e:
        return f"计算错误:{e}"


def get_date() -> str:
    """返回今天的日期"""
    from datetime import date
    return str(date.today())


TOOLS = {
    "search": search,
    "calculate": calculate,
    "get_date": get_date,
}

# ── ReAct 系统提示 ────────────────────────────────────────

REACT_SYSTEM_PROMPT = """你是一个智能助手,通过「思考-行动-观察」循环来解决问题。

你可以使用以下工具:
- search(query):搜索信息,query 是搜索关键词
- calculate(expr):计算数学表达式,expr 是数学表达式字符串
- get_date():获取今天的日期,无需参数

每次输出必须严格遵循以下格式之一:

格式 A(需要使用工具时):
Thought: 你的推理过程
Action: 工具名称
Action Input: 工具输入参数

格式 B(已有足够信息时):
Thought: 你的推理过程
Final Answer: 你的最终回答

重要规则:
1. 每次只输出一个 Action 或 Final Answer
2. 等待 Observation 后再继续
3. 不要编造工具返回的结果
"""

print("工具已定义:", list(TOOLS.keys()))
print("系统提示已准备好")
工具已定义: ['search', 'calculate', 'get_date']
系统提示已准备好

Section 2:手动 ReAct 循环#

通过文本解析来驱动 ReAct 循环,是最直观的实现方式。

核心逻辑:

  1. 将任务发给模型

  2. 解析模型输出中的 ActionAction Input

  3. 调用对应工具,得到 Observation

  4. 将 Observation 追加到对话历史,继续循环

  5. 直到出现 Final Answer: 停止

def parse_action(text: str):
    """
    从模型输出中解析 Action 和 Action Input。
    返回 (action_name, action_input) 或 None。
    """
    action_match = re.search(r"Action:\s*(.+)", text)
    input_match = re.search(r"Action Input:\s*(.+)", text)

    if action_match and input_match:
        return action_match.group(1).strip(), input_match.group(1).strip()
    return None


def parse_final_answer(text: str):
    """从模型输出中解析 Final Answer"""
    match = re.search(r"Final Answer:\s*(.+)", text, re.DOTALL)
    if match:
        return match.group(1).strip()
    return None


def execute_tool(tool_name: str, tool_input: str, tools: dict) -> str:
    """执行工具并返回结果"""
    if tool_name not in tools:
        return f"错误:工具 '{tool_name}' 不存在。可用工具:{list(tools.keys())}"
    try:
        tool_fn = tools[tool_name]
        # get_date 不需要参数
        if tool_name == "get_date":
            return tool_fn()
        return tool_fn(tool_input)
    except Exception as e:
        return f"工具执行错误:{e}"


def react_agent(task: str, tools: dict, max_steps: int = 5) -> str:
    """
    手动文本解析版 ReAct Agent。

    参数:
        task: 用户任务描述
        tools: 工具字典 {name: function}
        max_steps: 最大循环步数(防止无限循环)

    返回:
        最终回答字符串
    """
    messages = [
        {"role": "system", "content": REACT_SYSTEM_PROMPT},
        {"role": "user", "content": task},
    ]

    print(f"\n{'='*60}")
    print(f"任务:{task}")
    print(f"{'='*60}")

    for step in range(max_steps):
        print(f"\n── 步骤 {step + 1} ──")

        # 调用模型
        response = _c(
            model=MODEL,
            messages=messages,
            temperature=0,
            max_tokens=512,
        )
        assistant_text = response.choices[0].message.content
        print(f"模型输出:\n{assistant_text}")

        # 追加助手消息
        messages.append({"role": "assistant", "content": assistant_text})

        # 检查是否已有最终答案
        final_answer = parse_final_answer(assistant_text)
        if final_answer:
            print(f"\n{'='*60}")
            print(f"最终答案:{final_answer}")
            print(f"{'='*60}")
            return final_answer

        # 解析并执行工具
        action_result = parse_action(assistant_text)
        if action_result:
            action_name, action_input = action_result
            print(f"\n执行工具:{action_name}({action_input})")
            observation = execute_tool(action_name, action_input, tools)
            print(f"观察结果:{observation}")

            # 将 Observation 作为用户消息追加
            messages.append({
                "role": "user",
                "content": f"Observation: {observation}"
            })
        else:
            print("无法解析 Action,终止循环")
            break

    return "达到最大步数限制,未能得出最终答案"


# 简单测试
result = react_agent(
    task="今天是几号?",
    tools=TOOLS,
    max_steps=3,
)
============================================================
任务:今天是几号?
============================================================

── 步骤 1 ──
模型输出:
Thought: 需要调用获取当前日期的工具来回答“今天是几号?”
Action: get_date
Action Input: 无

执行工具:get_date(无)
观察结果:2026-03-14

── 步骤 2 ──
模型输出:
Thought: 已有今天的日期信息,可以直接回答具体日期。
Final Answer: 今天是2026年3月14日。

============================================================
最终答案:今天是2026年3月14日。
============================================================

Section 3:用 Function Calling 实现 ReAct#

文本解析方式脆弱,容易因格式不规范而失败。 现代做法是使用 Function Calling(工具调用),让模型返回结构化 JSON。

优势对比:

维度

文本解析

Function Calling

可靠性

依赖格式规范

结构化,稳定

参数类型

全是字符串

支持数字、列表等

错误处理

复杂

简单

模型支持

所有模型

需要支持 tool_calls

# ── Function Calling 工具描述(JSON Schema 格式)─────────────

FC_TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "search",
            "description": "搜索互联网上的信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "搜索关键词"
                    }
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "计算数学表达式",
            "parameters": {
                "type": "object",
                "properties": {
                    "expr": {
                        "type": "string",
                        "description": "数学表达式,如 '(18 + 22) / 2'"
                    }
                },
                "required": ["expr"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_date",
            "description": "获取今天的日期",
            "parameters": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }
    }
]


def react_agent_fc(task: str, tools: dict, max_steps: int = 5) -> str:
    """
    Function Calling 版 ReAct Agent。
    使用结构化工具调用代替文本解析。
    """
    messages = [
        {"role": "system", "content": "你是一个智能助手,通过调用工具来解决问题。先思考需要哪些信息,再调用合适的工具。"},
        {"role": "user", "content": task},
    ]

    print(f"\n{'='*60}")
    print(f"任务:{task}")
    print(f"{'='*60}")

    for step in range(max_steps):
        print(f"\n── 步骤 {step + 1} ──")

        response = _c(
            model=MODEL,
            messages=messages,
            tools=FC_TOOLS,
            tool_choice="auto",
            temperature=0,
        )

        msg = response.choices[0].message
        finish_reason = response.choices[0].finish_reason

        # 追加助手消息(包含可能的 tool_calls)
        messages.append(msg)

        # 没有工具调用 → 直接返回文本答案
        if finish_reason == "stop" or not msg.tool_calls:
            answer = msg.content or "(无文本回答)"
            print(f"最终答案:{answer}")
            return answer

        # 处理工具调用
        for tool_call in msg.tool_calls:
            fn_name = tool_call.function.name
            fn_args = json.loads(tool_call.function.arguments)

            print(f"调用工具:{fn_name},参数:{fn_args}")

            # 执行工具
            if fn_name not in tools:
                observation = f"错误:工具 '{fn_name}' 不存在"
            elif fn_name == "get_date":
                observation = tools[fn_name]()
            else:
                observation = tools[fn_name](**fn_args)

            print(f"工具结果:{observation}")

            # 将工具结果追加到消息历史
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": observation,
            })

    return "达到最大步数限制"


# 测试 Function Calling 版本
result = react_agent_fc(
    task="计算 (123 + 456) * 2 的结果",
    tools=TOOLS,
)
============================================================
任务:计算 (123 + 456) * 2 的结果
============================================================

── 步骤 1 ──
调用工具:calculate,参数:{'expr': '(123 + 456) * 2'}
工具结果:1158

── 步骤 2 ──
最终答案:(123 + 456) = 579,579 × 2 = 1158。因此结果是 1158。

Section 4:实际任务演示#

任务:查询北京和上海的天气,计算平均温度,并告诉我哪个城市更适合今天跑步。

这个任务需要:

  1. 搜索北京天气

  2. 搜索上海天气

  3. 计算平均温度

  4. 综合判断给出推荐

# 使用 Function Calling 版本演示多步任务
weather_task = "查询北京和上海的天气,计算平均温度,并告诉我哪个城市更适合今天跑步"

result = react_agent_fc(
    task=weather_task,
    tools=TOOLS,
    max_steps=6,
)

print(f"\n最终推荐:{result}")
============================================================
任务:查询北京和上海的天气,计算平均温度,并告诉我哪个城市更适合今天跑步
============================================================

── 步骤 1 ──
调用工具:search,参数:{'query': '北京 今日 天气 温度'}
工具结果:未找到关于 '北京 今日 天气 温度' 的相关信息
调用工具:search,参数:{'query': '上海 今日 天气 温度'}
工具结果:未找到关于 '上海 今日 天气 温度' 的相关信息

── 步骤 2 ──
最终答案:我刚才搜索当前天气时没有找到结果。你想怎么继续?有几个选项(请选择一个):

1) 我再试一次网上查询(我会改用更宽泛的关键词如“北京 天气 现在/实时温度”并获取上海同样的信息)。  
2) 你直接告诉我北京和上海现在的气温(摄氏度),我立即帮你计算平均温度并给出跑步建议。  
3) 只按常规经验让我直接给出建议(例如理想跑步温度区间、考虑空气质量和降水等因素的通用建议)。

另外请告诉我是否需要考虑空气质量、降雨/湿度或风速等因素,结果用摄氏度还是华氏度。你选哪个?

最终推荐:我刚才搜索当前天气时没有找到结果。你想怎么继续?有几个选项(请选择一个):

1) 我再试一次网上查询(我会改用更宽泛的关键词如“北京 天气 现在/实时温度”并获取上海同样的信息)。  
2) 你直接告诉我北京和上海现在的气温(摄氏度),我立即帮你计算平均温度并给出跑步建议。  
3) 只按常规经验让我直接给出建议(例如理想跑步温度区间、考虑空气质量和降水等因素的通用建议)。

另外请告诉我是否需要考虑空气质量、降雨/湿度或风速等因素,结果用摄氏度还是华氏度。你选哪个?
# 同一任务,用手动解析版本对比
print("\n\n对比:使用手动文本解析版本\n")

result2 = react_agent(
    task=weather_task,
    tools=TOOLS,
    max_steps=8,
)
对比:使用手动文本解析版本


============================================================
任务:查询北京和上海的天气,计算平均温度,并告诉我哪个城市更适合今天跑步
============================================================

── 步骤 1 ──
模型输出:
Thought: 我需要先获取北京今天的天气信息,然后再获取上海的天气,最后计算平均温度并给出跑步建议。先查询北京今天天气。
Action: search
Action Input: 北京 今天天气

执行工具:search(北京 今天天气)
观察结果:未找到关于 '北京 今天天气' 的相关信息

── 步骤 2 ──
模型输出:
Thought: 上次搜索未返回结果,我改用更具体的关键词重新查询北京今天天气(包含实时温度与空气质量)。  
Action: search  
Action Input: 北京 今天天气 实时 温度 空气质量

执行工具:search(北京 今天天气 实时 温度 空气质量)
观察结果:未找到关于 '北京 今天天气 实时 温度 空气质量' 的相关信息

── 步骤 3 ──
模型输出:
Thought: 改用不同词组和更通用的关键词再搜一次,尝试“北京市 今日 天气 实时 温度 空气质量”以获取所需信息。  
Action: search  
Action Input: 北京市 今日 天气 实时 温度 空气质量

执行工具:search(北京市 今日 天气 实时 温度 空气质量)
观察结果:未找到关于 '北京市 今日 天气 实时 温度 空气质量' 的相关信息

── 步骤 4 ──
模型输出:

无法解析 Action,终止循环

Section 5:错误恢复#

Agent 遇到工具错误时,应该能够:

  1. 识别错误信息

  2. 调整策略(换一个工具,或换一种输入)

  3. 继续完成任务

我们通过添加一个「有时会失败」的工具来演示错误恢复。

import random

# 一个不稳定的工具(50% 概率失败)
_unstable_call_count = 0

def unstable_search(query: str) -> str:
    """模拟不稳定的搜索服务"""
    global _unstable_call_count
    _unstable_call_count += 1
    # 第一次调用必定失败,后续正常
    if _unstable_call_count == 1:
        return "错误:搜索服务暂时不可用,请稍后重试或使用其他关键词"
    return search(query)


def get_stock_price(symbol: str) -> str:
    """获取股票价格(演示用)"""
    prices = {
        "AAPL": "185.20",
        "GOOG": "141.80",
        "BTC": "67500",
    }
    symbol = symbol.upper()
    if symbol in prices:
        return f"{symbol} 当前价格:${prices[symbol]}"
    return f"错误:找不到股票代码 '{symbol}',请检查代码是否正确"


RESILIENT_FC_TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "unstable_search",
            "description": "搜索信息(有时可能失败,失败时需要重试或换关键词)",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "搜索关键词"}
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_stock_price",
            "description": "获取股票或加密货币价格,symbol 为股票代码如 AAPL、GOOG、BTC",
            "parameters": {
                "type": "object",
                "properties": {
                    "symbol": {"type": "string", "description": "股票代码"}
                },
                "required": ["symbol"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "计算数学表达式",
            "parameters": {
                "type": "object",
                "properties": {
                    "expr": {"type": "string"}
                },
                "required": ["expr"]
            }
        }
    }
]

RESILIENT_TOOLS = {
    "unstable_search": unstable_search,
    "get_stock_price": get_stock_price,
    "calculate": calculate,
}


def react_agent_resilient(task: str, max_steps: int = 8) -> str:
    """具备错误恢复能力的 ReAct Agent"""
    messages = [
        {
            "role": "system",
            "content": (
                "你是一个智能助手。当工具调用失败时,不要放弃,"
                "尝试换一种方式(如换关键词重试、使用备选工具)。"
            )
        },
        {"role": "user", "content": task},
    ]

    print(f"\n{'='*60}")
    print(f"任务(错误恢复测试):{task}")
    print(f"{'='*60}")

    for step in range(max_steps):
        print(f"\n── 步骤 {step + 1} ──")
        response = _c(
            model=MODEL,
            messages=messages,
            tools=RESILIENT_FC_TOOLS,
            tool_choice="auto",
            temperature=0,
        )
        msg = response.choices[0].message
        messages.append(msg)

        if not msg.tool_calls:
            answer = msg.content or "(无回答)"
            print(f"最终答案:{answer}")
            return answer

        for tc in msg.tool_calls:
            fn_name = tc.function.name
            fn_args = json.loads(tc.function.arguments)
            print(f"调用工具:{fn_name},参数:{fn_args}")

            tool_fn = RESILIENT_TOOLS.get(fn_name)
            if tool_fn is None:
                observation = f"错误:工具 '{fn_name}' 不存在"
            else:
                observation = tool_fn(**fn_args)

            # 标记错误消息,引导模型注意
            if "错误" in observation:
                print(f"工具报错:{observation}")
            else:
                print(f"工具结果:{observation}")

            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": observation,
            })

    return "达到最大步数限制"


# 重置计数器,测试错误恢复
_unstable_call_count = 0
result = react_agent_resilient(
    task="查询北京天气,并告诉我今天是否适合骑行"
)
============================================================
任务(错误恢复测试):查询北京天气,并告诉我今天是否适合骑行
============================================================

── 步骤 1 ──
调用工具:unstable_search,参数:{'query': '北京 今天天气 实时 温度 降雨 风速 空气质量 AQI'}
工具报错:错误:搜索服务暂时不可用,请稍后重试或使用其他关键词

── 步骤 2 ──
调用工具:unstable_search,参数:{'query': '北京市 今天天气 预报 逐小时 风速 降雨 概率 空气质量 AQI'}
工具结果:未找到关于 '北京市 今天天气 预报 逐小时 风速 降雨 概率 空气质量 AQI' 的相关信息

── 步骤 3 ──
最终答案:我刚尝试获取实时天气但搜索服务暂时不可用,暂时无法直接拉取北京的实时数据。可以这样继续,你选一个告诉我就行:

- 我现在重试一次在线查询(我会换关键词或再试几次)。  
- 或者你把手机/网页上看到的当前温度、降雨情况、风速/风力、AQI(空气质量)给我,我来判断是否适合骑行。  
- 或者你想要我给出一份「如何自己快速判断是否适合骑行」的清单和决策规则(我下面先把清单发给你,直接可用)。

先把判断要点和决策规则给你(可直接按此自查):

1. 是否下雨/路面情况
- 不下雨且路面干燥:适合。  
- 毛毛雨/细雨(小雨)且有挡泥/护具:勉强可行,注意防滑、减速。  
- 中到大雨、路面积水或有雨夹雪/结冰:不建议骑行。

2. 风力
- 风速 < 6–8 m/s(约 20–30 km/h):一般可安全骑行。  
- 风速 8–12 m/s(30–43 km/h):会影响稳定性,遇阵风要谨慎、选择避风路线或改乘。  
- 风速 > 12–15 m/s(>43–54 km/h)或有强阵风:不安全,建议不骑。

3. 温度
- 舒适:约 10–28°C(根据个人耐寒/耐热略有差异)。  
- 冷:< 5°C 要穿暖、注意路面结冰,低于 -5°C 严重影响安全与制动。  
- 热:> 35°C 不建议进行长时间或高强度骑行,注意补水与避暑。

4. 空气质量(AQI)
- 0–50(优):安全。  
- 51–100(良):多数人可骑,敏感人群注意短时逗留。  
- 101–150(轻度污染,对敏感人群不利):建议短途且强度低;敏感者避免。  
- >150(中度或以上污染):不建议骑行(尤其是长时间或高速骑行)。

5. 能见度/其他
- 大雾、能见度低、路灯不足、施工路段或路况差时不建议骑行或选择短程路线并开灯/穿反光衣。

6. 装备和安全建议(无论天气都应遵守)
- 佩戴头盔、灯、反光衣/条;下雨带挡泥板或穿防水外套;备水、手机、充电宝;选择有自行车道或车流少路线;遇到大风/暴雨/空气污染可改乘地铁/公交/网约车。

简单决策规则(把上面条件综合):
- 如果(无降雨)且(风速 < 8 m/s)且(AQI ≤ 100)且(温度适中),则“适合骑行(正常骑行)”。  
- 如果只有一项轻微不利(小雨、风速接近阈值或AQI 100-150),则“可骑但需注意并缩短/放慢行程或选避风路线”。  
- 如果有一项严重不利(大雨、风 >12 m/s、AQI >150、能见度差或路面结冰),则“不建议骑行”。

如何快速查到这些信息(你可以在手机上或电脑上照做):
- 搜索关键词:“北京 天气 实况” / “Beijing weather” 或 打开天气类APP(墨迹天气、彩云天气、和风天气、天气通、苹果/安卓系统自带天气)。  
- 查风速/风向:在逐小时预报里看“风速/风向”或“风力等级(几级)”。  
- 查降雨概率/分钟级雷达:看“未来1小时降雨/雷达回波”。  
- 查AQI:搜索“北京 AQI”或打开 aqicn.org(世界空气质量项目)、中国环境监测总站或本地空气质量APP。
- 若你愿意,把你看到的“当前温度 / 降雨(有/无或降雨量) / 风速(m/s 或 km/h 或几级) / AQI”发给我,我马上判断并给出建议和替代路线建议。

要我现在重试在线查询吗?还是你贴一下当前数据?

进阶:ReAct 步骤追踪可视化#

为了更好地理解 Agent 的执行路径,我们可以记录每一步的详细信息。

def react_agent_traced(task: str, tools: dict, fc_tools: list, max_steps: int = 6):
    """
    带完整追踪记录的 ReAct Agent。
    返回 (最终答案, 追踪记录列表)
    """
    messages = [
        {"role": "system", "content": "你是一个智能助手,通过调用工具来解决复杂问题。"},
        {"role": "user", "content": task},
    ]
    trace = []  # 记录每一步

    for step in range(max_steps):
        response = _c(
            model=MODEL,
            messages=messages,
            tools=fc_tools,
            tool_choice="auto",
            temperature=0,
        )
        msg = response.choices[0].message
        messages.append(msg)

        step_record = {
            "step": step + 1,
            "thought": msg.content,
            "actions": [],
        }

        if not msg.tool_calls:
            step_record["final_answer"] = msg.content
            trace.append(step_record)
            return msg.content, trace

        for tc in msg.tool_calls:
            fn_name = tc.function.name
            fn_args = json.loads(tc.function.arguments)

            if fn_name == "get_date":
                observation = tools[fn_name]()
            else:
                observation = tools[fn_name](**fn_args)

            step_record["actions"].append({
                "tool": fn_name,
                "args": fn_args,
                "result": observation,
            })

            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": observation,
            })

        trace.append(step_record)

    return "达到步数限制", trace


# 运行并打印可视化追踪
answer, trace = react_agent_traced(
    task="今天几号?(18 + 22) / 2 等于多少?",
    tools=TOOLS,
    fc_tools=FC_TOOLS,
)

print("\n" + "="*60)
print("执行追踪记录")
print("="*60)
for record in trace:
    print(f"\n[步骤 {record['step']}]")
    if record.get('thought'):
        print(f"  思考:{record['thought']}")
    for action in record.get('actions', []):
        print(f"  行动:{action['tool']}({action['args']})")
        print(f"  结果:{action['result']}")
    if record.get('final_answer'):
        print(f"  ✓ 最终答案:{record['final_answer']}")
============================================================
执行追踪记录
============================================================

[步骤 1]
  行动:get_date({})
  结果:2026-03-14
  行动:calculate({'expr': '(18 + 22) / 2'})
  结果:20.0

[步骤 2]
  思考:今天是 2026年3月14日。(18 + 22) / 2 = 20。需要我帮你算其他题吗?
  ✓ 最终答案:今天是 2026年3月14日。(18 + 22) / 2 = 20。需要我帮你算其他题吗?

练习题#

  1. 扩展工具集:添加一个 get_weather(city) 工具,返回真实天气数据(可以用 OpenWeatherMap API)。

  2. 自定义最大步数:当任务特别复杂时,max_steps=5 可能不够。试着运行一个需要 8 步的任务,观察会发生什么。

  3. 并行工具调用:部分模型支持在一次响应中调用多个工具(parallel_tool_calls)。修改 react_agent_fc 以支持并行调用,并测试查询两个城市天气的任务是否变快。

  4. 自定义停止条件:除了 Final Answer,添加一个「置信度检查」:如果模型输出包含「我不确定」,自动重新搜索。

总结#

ReAct 让模型具备了「先想后做」的能力#

核心概念

说明

Thought

模型的推理过程,可见、可调试

Action

调用外部工具(搜索、计算、API 等)

Observation

工具返回的真实结果

循环

不断迭代直到得出最终答案

两种实现方式对比#

方式

优点

缺点

文本解析

兼容所有模型

格式脆弱,易出错

Function Calling

结构化、稳定、支持复杂参数

需要模型支持

最佳实践#

  • 始终设置 max_steps 防止无限循环

  • 工具函数要有清晰的错误返回(让模型知道失败原因)

  • 系统提示中明确工具的使用边界

  • 记录执行追踪,便于调试

下一章我们将学习如何为 Agent 添加记忆能力,处理长期任务。

# 知识检验:完整演示
print("ReAct Agent 完整能力演示")
print("="*60)
print(f"模型:{MODEL}")
print(f"可用工具:{list(TOOLS.keys())}")
print()

# 综合任务
final_result = react_agent_fc(
    task=(
        "先告诉我今天日期,然后查一下北京天气,"
        "再计算 18 摄氏度转华氏温度(公式:F = C * 9/5 + 32)"
    ),
    tools=TOOLS,
    max_steps=6,
)

print(f"\n任务完成!")
ReAct Agent 完整能力演示
============================================================
模型:openai/gpt-5-mini
可用工具:['search', 'calculate', 'get_date']


============================================================
任务:先告诉我今天日期,然后查一下北京天气,再计算 18 摄氏度转华氏温度(公式:F = C * 9/5 + 32)
============================================================

── 步骤 1 ──
调用工具:get_date,参数:{}
工具结果:2026-03-14
调用工具:search,参数:{'query': '北京 天气'}
工具结果:未找到关于 '北京 天气' 的相关信息
调用工具:calculate,参数:{'expr': '18 * 9/5 + 32'}
工具结果:64.4

── 步骤 2 ──
调用工具:search,参数:{'query': '北京 今天 天气 现在'}
工具结果:未找到关于 '北京 今天 天气 现在' 的相关信息

── 步骤 3 ──
最终答案:今天是 2026-03-14。

关于北京的即时天气:我刚尝试检索当前的天气信息,但未能获取到实时天气数据。要么是检索受限,要么暂时无法访问天气源。你想让我再试一次吗?或者你可以告诉我你希望查的具体区/街道,我再精确检索。

如果你需要一个大致参考:3月的北京通常是春季过渡期,日间气温常在约 5–15°C 之间,早晚偏凉,空气相对干燥并可能有风,有时会下小雨或沙尘(仅作典型季节性参考,非实时预报)。

温度换算:
F = C * 9/5 + 32
代入 C = 18:
F = 18 * 9/5 + 32 = 64.4°F

要不要我再尝试获取实时天气,或为你查具体时段的预报(今天/24小时/7天)?

任务完成!