基于LangChain构建ReAct Agent:让AI学会"思考-行动-观察"

基于LangChain构建ReAct Agent:让AI学会"思考-行动-观察"

从原理到实战,手把手教你构建一个能自主推理和行动的AI智能体


一、什么是ReAct Agent?

1.1 从一个问题说起

想象一下,你让AI帮你查"北京今天天气怎么样,适合穿什么衣服"。传统的大模型会怎么做?它可能会根据训练记忆给出一个大概的回答——但这种回答可能是过时的、不准确的。

如果换一种方式:让AI先思考需要查天气,然后行动——调用天气API获取实时数据,再观察结果,根据温度数据进一步思考穿衣建议,最后给出回答。这就是ReAct(Reasoning + Acting)模式的核心思想。

ReAct Agent的工作方式其实挺像人类解决问题的过程。它不是一次性把整个流程都规划好,而是在有一个整体目标的前提下,走一步看一步:先观察当前情况,思考下一步该做什么,执行动作,再观察结果,根据结果决定下一步。这个过程会一直循环,直到任务完成。

1.2 传统方式 vs ReAct方式

传统编程/提示词 ReAct Agent
决策方式 预先定义好所有步骤 每步根据观察动态决策
灵活性 遇到意外情况容易卡住 能根据中间结果调整策略
可解释性 黑盒 思考过程可见
适用场景 流程固定的任务 复杂、不确定的任务

打个比方,传统方式就像你出门前把整个路线都查好,然后严格按照路线走。而ReAct更像是你出门后,走一段路,看看周围环境,再决定下一步往哪走。

1.3 ReAct的核心循环:思考→行动→观察

ReAct Agent的核心是一个不断循环的过程:

┌─────────────────────────────────────────────────────┐
│                                                      │
│   思考 (Thought) ──→ 行动 (Action) ──→ 观察 (Observation)
│         ↑                              │            │
│         └──────────────────────────────┘            │
│                                                      │
│             直到获得最终答案                          │
└─────────────────────────────────────────────────────┘

具体来说

  1. Thought(思考):LLM根据当前上下文,推理下一步该做什么
  2. Action(行动):调用一个工具(如搜索、计算器、API请求)
  3. Observation(观察):获得工具执行的结果
  4. 循环:根据观察结果,决定是否继续或给出最终答案

二、环境准备

2.1 安装依赖

# 创建虚拟环境(可选)
python -m venv react_env
source react_env/bin/activate  # Linux/Mac
# react_env\Scripts\activate   # Windows

# 安装核心依赖
pip install langchain langchain-openai langchain-community
pip install python-dotenv
pip install duckduckgo-search  # 用于网络搜索工具
pip install tavily-python      # 备选:更专业的搜索API

2.2 配置API Key

创建一个 .env 文件:

OPENAI_API_KEY=your_openai_api_key_here
# 如果使用Tavily搜索
TAVILY_API_KEY=your_tavily_api_key_here

三、从零构建ReAct Agent

3.1 定义工具(Tools)

工具是Agent的"手脚",用来执行具体的操作。我们以计算器文本长度统计两个简单工具为例:

from langchain.tools import tool
import os
from dotenv import load_dotenv

load_dotenv()

# ---------- 工具1:计算器 ----------
@tool
def calculator(expression: str) -> str:
    """
    计算数学表达式的结果。
    
    Args:
        expression: 要计算的数学表达式,如 "2 + 3 * 4"
    """
    try:
        # 安全起见,只允许基本运算
        allowed_chars = set("0123456789+-*/(). ")
        if not all(c in allowed_chars for c in expression):
            return "错误:表达式包含非法字符"
        result = eval(expression)
        return f"计算结果:{result}"
    except Exception as e:
        return f"计算错误:{str(e)}"

# ---------- 工具2:文本长度统计 ----------
@tool
def get_text_length(text: str) -> str:
    """
    统计文本的字符长度(不含空格)。
    
    Args:
        text: 需要统计长度的文本
    """
    length = len(text.replace(" ", ""))
    return f"文本长度(不含空格):{length} 个字符"

# ---------- 工具3:获取当前时间 ----------
from datetime import datetime

@tool
def get_current_time() -> str:
    """获取当前的日期和时间。"""
    now = datetime.now()
    return f"当前时间:{now.strftime('%Y年%m月%d日 %H:%M:%S')}"

💡 新手说明@tool 是LangChain提供的装饰器,它能把一个普通函数变成Agent可调用的工具。函数名和文档字符串会被LLM读取,用来决定何时调用这个工具。

3.2 构建ReAct提示模板

ReAct Agent需要一个特定的提示模板,告诉LLM如何按照"思考→行动→观察"的格式输出:

from langchain.prompts import PromptTemplate

react_prompt_template = """Answer the following questions as best you can. You have access to the following tools:

{tools}

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: {input}
Thought: {agent_scratchpad}"""

prompt = PromptTemplate.from_template(react_prompt_template)

这个模板是ReAct Agent的核心。它通过Few-shot示例的方式,告诉LLM应该如何组织自己的"思考过程":先想(Thought),再做(Action),然后看结果(Observation),循环直到得出最终答案。

3.3 创建Agent

from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent

# 1. 初始化LLM
llm = ChatOpenAI(
    model="gpt-4o-mini",  # 也可以使用 gpt-3.5-turbo
    temperature=0,        # 温度设为0让输出更确定
)

# 2. 收集工具
tools = [calculator, get_text_length, get_current_time]

# 3. 创建ReAct Agent
agent = create_react_agent(
    llm=llm,
    tools=tools,
    prompt=prompt,
)

# 4. 创建Agent执行器
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,  # 打印详细的思考过程
    max_iterations=5,  # 防止无限循环
    handle_parsing_errors=True,
)

💡 新手说明

  • create_react_agent 是LangChain提供的ReAct Agent工厂函数
  • AgentExecutor 负责运行Agent,管理循环逻辑
  • verbose=True 会打印出Agent每一步的"思考"过程,方便调试和理解
  • max_iterations 是安全阀,防止Agent陷入死循环

3.4 运行Agent

# ---------- 测试:多步推理 ----------
def run_agent(query: str):
    print("\n" + "="*60)
    print(f"用户问题: {query}")
    print("="*60)
    
    response = agent_executor.invoke({"input": query})
    print(f"\n最终回答: {response['output']}")

# 测试1:简单计算
run_agent("请计算 25 * 17 等于多少?")

# 测试2:需要组合工具的任务
run_agent("请计算 2024 年 7 月 16 日距离今天有多少天?提示:先获取当前时间。")

# 测试3:文本分析
run_agent("请告诉我 'Hello ReAct Agent' 这个字符串有多少个字符(不含空格)?")

3.5 输出示例

运行后,你会看到类似这样的输出:

============================================================
用户问题: 请计算 25 * 17 等于多少?
============================================================

> Entering new AgentExecutor chain...

Thought: 我需要使用计算器工具来计算25乘以17。
Action: calculator
Action Input: 25 * 17
Observation: 计算结果:425
Thought: 我已经得到了计算结果,可以给出最终答案了。
Final Answer: 25 * 17 等于 425。

> Finished chain.

最终回答: 25 * 17 等于 425。

这就是ReAct Agent的精髓:它把推理过程(Thought)和行动(Action)暴露出来了,让我们能清楚地看到"AI是怎么想的"。


四、进阶:带搜索能力的Agent

让Agent具备网络搜索能力,才能真正发挥ReAct的价值。

4.1 添加搜索工具

from langchain_community.tools import DuckDuckGoSearchRun

# 使用DuckDuckGo搜索(免费,无需API Key)
search = DuckDuckGoSearchRun()

@tool
def web_search(query: str) -> str:
    """
    在互联网上搜索信息。
    
    Args:
        query: 搜索关键词
    """
    try:
        results = search.invoke(query)
        # 截断过长的结果
        if len(results) > 2000:
            results = results[:2000] + "...(结果已截断)"
        return results
    except Exception as e:
        return f"搜索失败:{str(e)}"

4.2 构建多工具Agent

# 扩展工具列表
advanced_tools = [
    calculator,
    get_text_length,
    get_current_time,
    web_search,  # 新增搜索能力
]

# 创建新的Agent
advanced_agent = create_react_agent(
    llm=llm,
    tools=advanced_tools,
    prompt=prompt,
)

advanced_executor = AgentExecutor(
    agent=advanced_agent,
    tools=advanced_tools,
    verbose=True,
    max_iterations=10,  # 搜索需要更多步骤
    handle_parsing_errors=True,
)

# 测试复杂问题
run_agent("2024年巴黎奥运会的开幕日期是哪天?中国代表团获得了多少枚金牌?")

4.3 完整的Agent执行流程

下面是一个完整的ReAct Agent执行轨迹示例,它展示了多轮思考-行动-观察的循环:

用户问题: 2024年巴黎奥运会的开幕日期是哪天?中国代表团获得了多少枚金牌?

> Entering new AgentExecutor chain...

Thought: 我需要先查询巴黎奥运会的开幕日期,然后再查询中国代表团的奖牌数。我应该使用搜索工具。
Action: web_search
Action Input: 2024年巴黎奥运会开幕日期
Observation: 2024年巴黎奥运会于2024年7月26日开幕,8月11日闭幕...
(搜索结果内容)

Thought: 现在我有了开幕日期,还需要查询中国代表团的奖牌信息。
Action: web_search
Action Input: 2024年巴黎奥运会中国代表团金牌数
Observation: 中国代表团在2024年巴黎奥运会上获得40枚金牌、27枚银牌、24枚铜牌...
(搜索结果内容)

Thought: 我已经收集到了所有需要的信息,可以给出完整答案了。
Final Answer: 2024年巴黎奥运会于2024年7月26日开幕,中国代表团共获得40枚金牌。

> Finished chain.

五、常见问题与最佳实践

5.1 防止无限循环

ReAct Agent可能会陷入"思考→行动→观察→再思考→再行动..."的死循环。解决办法:

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    max_iterations=5,  # 限制最大迭代次数
    early_stopping_method="generate",  # 达到最大迭代时强制生成答案
)

5.2 工具描述要清晰

工具的描述(docstring)非常重要,LLM就是靠它来判断"什么时候该用哪个工具"。描述要包含:

  • 工具的用途
  • 输入参数的含义
  • 输出格式
@tool
def fetch_weather(city: str) -> str:
    """
    获取指定城市的实时天气信息。
    
    使用场景:当用户询问天气情况时调用此工具。
    
    Args:
        city: 城市名称,如 "北京"、"上海"
    """
    # 实现...

5.3 处理工具调用失败

工具可能因为各种原因失败(API超时、网络错误、参数错误等),Agent需要能优雅处理:

@tool
def risky_api_call(endpoint: str) -> str:
    """调用外部API,可能失败。"""
    try:
        response = requests.get(endpoint, timeout=5)
        return response.text
    except requests.Timeout:
        return "错误:API请求超时,请稍后重试"
    except Exception as e:
        return f"错误:{str(e)}"

5.4 ReAct Agent vs Tool Calling Agent

LangChain支持两种主要的Agent模式:

ReAct Agent Tool Calling Agent
原理 通过Prompt模板引导LLM输出指定格式 利用LLM原生的Function Calling能力
兼容性 兼容几乎所有LLM 仅支持支持Function Calling的模型
可解释性 思考过程完整可见 思考过程不直接暴露
稳定性 依赖LLM对格式的理解,有时会出错 更稳定,由模型原生支持

推荐:如果使用GPT-4、Claude等支持Function Calling的模型,优先使用Tool Calling Agent;如果使用开源模型或不支持Function Calling的模型,ReAct Agent是更好的选择。


六、总结

核心要点

  1. ReAct的核心:思考(Reasoning)和行动(Acting)交替进行,形成"思考→行动→观察→再思考"的循环

  2. 三大组件

    • LLM:负责推理和决策的"大脑"
    • 工具(Tools):执行具体操作的"手脚"
    • 循环机制:维护历史上下文,迭代推进任务
  3. 关键优势

    • 能处理复杂、动态的多步任务
    • 思考过程可见,可调试
    • 遇到错误可以重新规划

完整代码清单

# react_agent_demo.py
import os
from datetime import datetime
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain.agents import AgentExecutor, create_react_agent
from langchain.prompts import PromptTemplate

load_dotenv()

# ---------- 1. 定义工具 ----------
@tool
def calculator(expression: str) -> str:
    """计算数学表达式。"""
    try:
        return f"结果:{eval(expression)}"
    except Exception as e:
        return f"错误:{e}"

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

# ---------- 2. 创建ReAct提示模板 ----------
template = """用中文回答。你有以下工具可用:

{tools}

请按以下格式输出:

Question: 用户的问题
Thought: 思考下一步该做什么
Action: 要使用的工具名称 [{tool_names}]
Action Input: 工具的输入参数
Observation: 工具返回的结果
... (可以重复多次)
Thought: 我现在知道最终答案了
Final Answer: 最终答案

开始!

Question: {input}
Thought: {agent_scratchpad}"""

prompt = PromptTemplate.from_template(template)

# ---------- 3. 创建Agent ----------
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [calculator, get_current_time]
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    max_iterations=5,
)

# ---------- 4. 运行 ----------
if __name__ == "__main__":
    response = executor.invoke({
        "input": "今天是几号?请计算 100 天后的日期是几月几号?"
    })
    print(response["output"])

💡 下一步:可以尝试为Agent添加更多工具,比如文件读写、数据库查询、发送邮件等,让Agent完成更复杂的自动化任务!

本文首发于 [你的技术博客名称],欢迎交流讨论! 😄

posted @ 2026-07-16 20:38  yuangu  阅读(28)  评论(0)    收藏  举报