[Agent]AI Agent入门02——ReAct 基本理论与实战

ReAct介绍

ReAct(Reasoning and Acting)是一种通过协同推理(Reasoning)与行动(Acting)提升大语言模型(LLM)任务解决能力的技术。其核心思想是在解决复杂问题时交替生成推理和动作,形成闭环的决策流程。通过交叉推理和行动,ReAct 使智能体能够动态地在产生想法和特定于任务的行动之间交替,动态地处理复杂任务并提高决策的准确性和可靠性。

ReAct提出的动机

传统LLM在处理复杂任务时,常面临两大局限:

  • 推理孤立性:链式思维(CoT)仅依赖内部推理,易产生"幻觉"或错误传播。
  • 行动单一性:动作生成方法(API调用)缺乏动态调整能力,导致效率低下。

ReAct通过交替生成推理轨迹与任务动作,实现了两者的协同。例如,在问答任务中,模型先推理所需信息,再通过搜索API获取数据,最后结合反馈更新答案,形成"思考→行动→观察"的循环

核心流程

ReAct的核心流程为Thought → Action → Observation的迭代循环:

  • 推理(Thought) :分解任务目标,规划行动步骤。例如,在医疗诊断中,模型可能推理"需查询患者病史与近期检查结果"。
  • 行动(Action) :执行具体操作(如调用API、检索数据库)。例如,向医院系统发送数据请求。
  • 观察(Observation) :获取外部反馈(如返回的检查报告),并更新上下文信息。

**内部推理决定下一步行动的方向,外部数据修正或补充推理过程。**这一机制使模型能动态调整策略,避免CoT的静态推理局限

ReAct实战

LangGraph中实现了预构建的ReAct代理,但为了进一步理解ReAct的原理,这里我们使用 LangGraph 从零开始实现 ReAct 代理。

1. 定义模型和工具

在定义图时,首要任务是确定图的状态。状态由两部分构成,即图的模式以及 reducer 函数。

  • 图的模式涵盖了图中所有节点和边的输入模式,它可以采用 TypedDictPydantic 模型的形式来呈现。默认情况下,图将具有相同的输入和输出模式,但也可以通过自定义InputStateOutputState来为图定义不同的输入和输出模式。

  • 节点会发出对状态的更新,随后借助所指定的 reducer 函数来把这种更新应用到状态上,以此来实现对图状态的管理和调整。如果未显式指定 reducer 函数,则对该键的更新会覆盖原来的内容。

python 复制代码
from typing import Annotated,Sequence,TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]

这里用本地模型,通过ollama调用

python 复制代码
from langchain_openai import ChatOpenAI


model = ChatOpenAI(
    model="deepseek-r1:8b", 
    temperature=0.0, 
    api_key="ollama", 
    base_url="http://localhost:11434/v1"
)

2. 构建工具函数

在 LangChain 中,@tool 是一个装饰器,它标识了该函数是一个可供智能体(Agent)使用的工具,智能体会根据设置的策略来决定是否调用以及何时调用该工具。

当智能体需要完成某个任务时,会根据任务的性质和上下文,判断是否需要调用已定义的工具。

工具函数的返回值会被智能体捕获并处理,以便将其整合到最终的回答或后续的处理流程中。

python 复制代码
from langchain_core.tools import tool

@tool
def get_weather(location: str):
    """Call to get the weather from a specific location."""
    if any([city in location.lower() for city in ["sf", "san francisco"]]):
        return "It's sunny in San Francisco, but you better look out if you're a Gemini."
    else:
        return f"I am not sure what the weather is in {location}"


tools = [get_weather]

3. 构建大模型链

如果是chatgpt或claude模型,可以直接绑定工具

python 复制代码
model = model.bind_tools(tools)

但是deepseek不支持bind_tools函数,所以这里用prompt来指示大模型:

python 复制代码
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate


tool_descriptions = "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个有帮助的AI助手。当你需要信息时,使用以下工具之一:
{tool_descriptions}

要使用工具,请按以下格式回应:
ACTION: tool_name
ACTION_INPUT: 工具的输入

如果你不需要使用工具,直接回应用户即可。"""),
    ("user", "{input}")
])

chain = prompt | model | StrOutputParser()

4. 构建节点

在基本的 ReAct 代理中,只有两个节点,一个用于调用模型,另一个用于使用工具。

python 复制代码
import json
from langchain_core.messages import ToolMessage, SystemMessage
from langchain_core.runnables import RunnableConfig

tools_by_name = {tool.name: tool for tool in tools}

def tool_node(state: AgentState):
    outputs = []
    last_message = state["messages"][-1]
    content = last_message.content
    
    # 解析模型输出中的ACTION和ACTION_INPUT
    if "ACTION:" in content and "ACTION_INPUT:" in content:
        action_line = content.split("ACTION:")[1].split("\n")[0].strip()
        tool_name = action_line
        
        input_line = content.split("ACTION_INPUT:")[1].split("\n")[0].strip()
        tool_input = input_line
        
        tool_result = tools_by_name[tool_name].invoke(tool_input)
        outputs.append(
            ToolMessage(
                content=json.dumps(tool_result),
                name=tool_name,
                tool_call_id="1",  
            )
        )
    return {"messages": outputs}


def call_model(state: AgentState, config: RunnableConfig):
    user_input = state["messages"][-1].content if hasattr(state["messages"][-1], "content") else state["messages"][-1][1]
    
    response = chain.invoke({"input": user_input, "tool_descriptions": tool_descriptions})
    
    from langchain_core.messages import AIMessage
    ai_message = AIMessage(content=response)
    
    return {"messages": [ai_message]}

should_continue 函数是一个判断智能体对话是否继续的函数,它的作用是根据智能体当前的状态来决定是否继续对话,或者结束对话

这个函数在多轮对话的智能体中起到关键的控制作用,帮助智能体根据自身的状态和对话进程来决定是否继续与用户进行对话,从而合理地管理和引导对话的走向,避免不必要的对话延续或者过早地结束对话。

python 复制代码
def should_continue(state: AgentState):
    messages = state["messages"]
    last_message = messages[-1]
    content = last_message.content if hasattr(last_message, "content") else ""
    
    if "ACTION:" in content and "ACTION_INPUT:" in content:
        return "continue"
    else:
        return "end"

5. 构建图

StateGraph(AgentState) 表示这个状态图是基于 AgentState 这个状态类来构建的,表示智能体的工作流程。这个状态图定义了智能体在执行任务时的不同状态以及状态之间的转移条件和路径。

python 复制代码
from langgraph.graph import StateGraph, END

workflow = StateGraph(AgentState)

workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)

workflow.set_entry_point("agent")

workflow.add_conditional_edges(
    "agent",
    should_continue,
    {
        "continue": "tools",
        "end": END,
    },
)
workflow.add_edge("tools", "agent")

graph = workflow.compile()

6.查看流程图

python 复制代码
from IPython.display import Image, display

try:
    display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
    pass

生成的流程图如下:

7. 调用模型

定义了一个函数进行流式调用

python 复制代码
def print_stream(stream):
    for s in stream:
        message = s["messages"][-1]
        if isinstance(message, tuple):
            print(message)
        else:
            message.pretty_print()


inputs = {"messages": [("user", "what is the weather in sf")]}
print_stream(graph.stream(inputs, stream_mode="values"))

完整代码

python 复制代码
from typing import Annotated,Sequence,TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate


class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]

from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

model = ChatOpenAI(
    model="qwen2.5:latest", 
    temperature=0.0, 
    api_key="ollama", 
    base_url="http://localhost:11434/v1"
)

@tool
def get_weather(location: str):
    """Call to get the weather from a specific location."""
    if any([city in location.lower() for city in ["sf", "san francisco"]]):
        return "It's sunny in San Francisco, but you better look out if you're a Gemini."
    else:
        return f"I am not sure what the weather is in {location}"


tools = [get_weather]


tool_descriptions = "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个有帮助的AI助手。当你需要信息时,使用以下工具之一:
{tool_descriptions}

要使用工具,请按以下格式回应:
ACTION: tool_name
ACTION_INPUT: 工具的输入

如果你不需要使用工具,直接回应用户即可。"""),
    ("user", "{input}")
])


chain = prompt | model | StrOutputParser()


import json
from langchain_core.messages import ToolMessage, SystemMessage
from langchain_core.runnables import RunnableConfig

tools_by_name = {tool.name: tool for tool in tools}

def tool_node(state: AgentState):
    outputs = []
    last_message = state["messages"][-1]
    content = last_message.content

    if "ACTION:" in content and "ACTION_INPUT:" in content:
        action_line = content.split("ACTION:")[1].split("\n")[0].strip()
        tool_name = action_line
        
        input_line = content.split("ACTION_INPUT:")[1].split("\n")[0].strip()
        tool_input = input_line
        
        tool_result = tools_by_name[tool_name].invoke(tool_input)
        outputs.append(
            ToolMessage(
                content=json.dumps(tool_result),
                name=tool_name,
                tool_call_id="1",  
            )
        )
    return {"messages": outputs}


def call_model(state: AgentState, config: RunnableConfig):

    user_input = state["messages"][-1].content if hasattr(state["messages"][-1], "content") else state["messages"][-1][1]
    response = chain.invoke({"input": user_input, "tool_descriptions": tool_descriptions})
    

    from langchain_core.messages import AIMessage
    ai_message = AIMessage(content=response)
    
    return {"messages": [ai_message]}

def should_continue(state: AgentState):
    messages = state["messages"]
    last_message = messages[-1]
    content = last_message.content if hasattr(last_message, "content") else ""
    

    if "ACTION:" in content and "ACTION_INPUT:" in content:
        return "continue"
    else:
        return "end"
    
from langgraph.graph import StateGraph, END

workflow = StateGraph(AgentState)

workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)

workflow.set_entry_point("agent")

workflow.add_conditional_edges(
    "agent",
    should_continue,
    {
        "continue": "tools",
        "end": END,
    },
)
workflow.add_edge("tools", "agent")

graph = workflow.compile()

from IPython.display import Image, display

try:
    display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
    pass

def print_stream(stream):
    for s in stream:
        message = s["messages"][-1]
        if isinstance(message, tuple):
            print(message)
        else:
            message.pretty_print()


inputs = {"messages": [("user", "what is the weather in sf")]}
print_stream(graph.stream(inputs, stream_mode="values"))
相关推荐
AI technophile15 分钟前
OpenCV计算机视觉实战(2)——环境搭建与OpenCV简介
人工智能·opencv·计算机视觉
carpell41 分钟前
【深度学习基础】:VGG实战篇(图像风格迁移)
人工智能·python·深度学习·计算机视觉
geneculture43 分钟前
邹晓辉教授十余年前关于围棋程序与融智学的思考,体现了对复杂系统本质的深刻洞察,其观点在人工智能发展历程中具有前瞻性意义。我们可以从以下三个维度进行深入解析:
人工智能·数学建模·融智学的重要应用·协同智能计算系统·间接形式化方法·系统工程融智学·间接计算模型
数据与人工智能律师1 小时前
数字时代,如何为个人信息与隐私筑牢安全防线?
大数据·网络·人工智能·云计算·区块链
lilye661 小时前
精益数据分析(38/126):SaaS模式的流失率计算优化与定价策略案例
前端·人工智能·数据分析
数据与后端架构提升之路1 小时前
在自动驾驶数据闭环中的特征工程应用
人工智能·机器学习·自动驾驶
江鸟19981 小时前
AI日报 · 2025年04月30日|OpenAI 回滚 GPT-4o 更新以解决“谄媚”问题
人工智能·gpt·大模型·agent·智能体
南玖yy2 小时前
解锁 C++26 的未来:从语言标准演进到实战突破
开发语言·数据库·c++·人工智能·c++23·c++基础语法
Nowl2 小时前
基于深度学习的毒蘑菇检测
人工智能·深度学习
说私域2 小时前
定制开发开源AI智能名片S2B2C商城小程序驱动的无界零售基础设施变革研究——基于京东模式的技术解构与商业重构
人工智能·小程序·开源·零售