[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
函数。
-
图的模式涵盖了图中所有节点和边的输入模式,它可以采用
TypedDict
或Pydantic
模型的形式来呈现。默认情况下,图将具有相同的输入和输出模式,但也可以通过自定义InputState
和OutputState
来为图定义不同的输入和输出模式。 -
节点会发出对状态的更新,随后借助所指定的
reducer
函数来把这种更新应用到状态上,以此来实现对图状态的管理和调整。如果未显式指定reducer
函数,则对该键的更新会覆盖原来的内容。
from typing import Annotated,Sequence,TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messagesclass AgentState(TypedDict):messages: Annotated[Sequence[BaseMessage], add_messages]
这里用本地模型,通过ollama调用
from langchain_openai import ChatOpenAImodel = ChatOpenAI(model="deepseek-r1:8b", temperature=0.0, api_key="ollama", base_url="http://localhost:11434/v1"
)
2. 构建工具函数
在 LangChain 中,@tool
是一个装饰器,它标识了该函数是一个可供智能体(Agent)使用的工具,智能体会根据设置的策略来决定是否调用以及何时调用该工具。
当智能体需要完成某个任务时,会根据任务的性质和上下文,判断是否需要调用已定义的工具。
工具函数的返回值会被智能体捕获并处理,以便将其整合到最终的回答或后续的处理流程中。
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模型,可以直接绑定工具
model = model.bind_tools(tools)
但是deepseek不支持bind_tools
函数,所以这里用prompt来指示大模型:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplatetool_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 代理中,只有两个节点,一个用于调用模型,另一个用于使用工具。
import json
from langchain_core.messages import ToolMessage, SystemMessage
from langchain_core.runnables import RunnableConfigtools_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_INPUTif "ACTION:" in content and "ACTION_INPUT:" in content:action_line = content.split("ACTION:")[1].split("\n")[0].strip()tool_name = action_lineinput_line = content.split("ACTION_INPUT:")[1].split("\n")[0].strip()tool_input = input_linetool_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 AIMessageai_message = AIMessage(content=response)return {"messages": [ai_message]}
should_continue
函数是一个判断智能体对话是否继续的函数,它的作用是根据智能体当前的状态来决定是否继续对话,或者结束对话
这个函数在多轮对话的智能体中起到关键的控制作用,帮助智能体根据自身的状态和对话进程来决定是否继续与用户进行对话,从而合理地管理和引导对话的走向,避免不必要的对话延续或者过早地结束对话。
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
这个状态类来构建的,表示智能体的工作流程。这个状态图定义了智能体在执行任务时的不同状态以及状态之间的转移条件和路径。
from langgraph.graph import StateGraph, ENDworkflow = 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.查看流程图
from IPython.display import Image, displaytry:display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:pass
生成的流程图如下:
7. 调用模型
定义了一个函数进行流式调用
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"))
完整代码
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 ChatPromptTemplateclass AgentState(TypedDict):messages: Annotated[Sequence[BaseMessage], add_messages]from langchain_openai import ChatOpenAI
from langchain_core.tools import toolmodel = 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 RunnableConfigtools_by_name = {tool.name: tool for tool in tools}def tool_node(state: AgentState):outputs = []last_message = state["messages"][-1]content = last_message.contentif "ACTION:" in content and "ACTION_INPUT:" in content:action_line = content.split("ACTION:")[1].split("\n")[0].strip()tool_name = action_lineinput_line = content.split("ACTION_INPUT:")[1].split("\n")[0].strip()tool_input = input_linetool_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 AIMessageai_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, ENDworkflow = 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, displaytry:display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:passdef 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"))