LangGraph-agent 系列之3——ReAct

LangGraph-agent之ReAct的原理与实践

ReAct: 《ReAct: Synergizing Reasoning and Acting in Language Models》这是一篇出自谷歌团队在 2022 年的论文,核心思想是让模型像人类一样 “边想边做”,通过自然语言生成推理步骤,再结合外部工具(如搜索引擎、数据库)获取信息,最终完成任务。

1. ReAct 的核心逻辑

ReAct 流程可拆解为四步循环,形成一个动态决策闭环:

  1. 思考(Reason)
    模型针对当前任务或问题,生成自然语言推理过程(类似 “自言自语”),梳理思路、明确下一步目标。例如:“用户问巴黎现在的气温,我需要先查今天的天气数据。”

  2. 行动(Act)
    根据推理结果,调用外部工具(如 API、数据库、搜索引擎)执行具体操作,获取必要信息。例如:调用天气 API 查询巴黎的实时气温。

  3. 观察(Observe)
    接收工具返回的结果(即 “观察到的信息”),并整合到当前语境中。例如:“工具返回巴黎现在气温 22℃,多云。”

  4. 迭代(Iterate)
    基于新信息继续推理,判断是否需要进一步行动(如补充查询),或直接生成最终答案。若信息不足,则重复 “思考 - 行动 - 观察” 循环;若信息足够,则输出结果。

2. 需要用到的包

from typing import TypedDict,Annotated,Sequence
from dotenv import load_dotenv
from langchain_core.messages import BaseMessage
from langchain_core.messages import ToolMessage
from langchain_core.messages import SystemMessage

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langgraph.graph.message import add_messages

1. from typing import Annotated,Sequence

Annotated

  • 作用:为类型添加元数据(附加信息),这些信息不会影响 Python 解释器的运行逻辑,但能被类型检查工具(如 mypy)、IDE 或框架(如 LangChain、Pydantic)识别和利用。

  • 语法Annotated[type, metadata1, metadata2, ...],其中:

    • type 是基础数据类型(如 intstr 等)。
    • 后续参数是元数据,可以是任意类型(字符串、数字、类实例等),用于补充描述该类型的约束、含义或用途。
  • 适用场景

    • 标记参数的业务含义(如单位、取值范围)。
    • 为框架提供额外配置(如 LangChain 中标记工具参数的说明)。
    • 生成文档时自动提取注释信息。
from typing import Annotated

# 表示“年龄”是整数类型,且元数据说明其范围是0-120
Age = Annotated[int, "年龄范围:0-120岁"]

def register(age: Age) -> None:
    print(f"注册年龄:{age}")

Sequence

  • 作用:表示有序的可迭代序列,是一种抽象的类型注解,涵盖了所有支持下标访问([])和长度计算(len())的序列类型,如 listtuplestrbytes 等。

  • 特点

    • 比具体的序列类型(如 list)更通用,适合需要兼容多种序列的场景。
    • 强调 “有序性” 和 “可迭代性”,但不强制要求是可变类型(如 tuple 是不可变的,也属于 Sequence)。
  • 适用场景

    • 函数参数需要接收列表、元组等多种序列类型时,用 Sequence 提高灵活性。
    • 明确表示 “需要一个有序的、可通过下标访问的集合”,而非无序的 set 或 dict
from typing import Sequence

# 函数接受任何字符串序列(list、tuple等)
def print_items(items: Sequence[str]) -> None:
    for i, item in enumerate(items):
        print(f"第{i+1}项:{item}")

print_items(["苹果", "香蕉"])  # 合法(list[str] 是 Sequence[str] 的子类)
print_items(("猫", "狗"))     # 合法(tuple[str] 是 Sequence[str] 的子类)

在 LangChain 等框架中,Annotated:为工具函数的参数添加描述,帮助智能体理解参数含义(例如在 @tool 装饰器中,元数据会被解析为工具调用的说明)。

from langchain_core.tools import tool
from typing import Annotated

@tool
def get_weather(
    city: Annotated[str, "需要查询天气的城市名称,如'北京'"]
) -> str:
    """查询指定城市的天气"""
    return f"{city}的天气是晴朗"

        Sequence:标注消息列表等有序数据,例如对话历史(兼容 listtuple 等存储形式)

from langchain_core.tools import tool
from typing import Annotated

@tool
def get_weather(
    city: Annotated[str, "需要查询天气的城市名称,如'北京'"]
) -> str:
    """查询指定城市的天气"""
    return f"{city}的天气是晴朗"

2. from langchain_core.messages import BaseMessage,ToolMessage,SystemMessage

BaseMessage(基础消息类)

  • 定位:所有消息类型的抽象基类(父类),定义了消息的通用结构。
  • 核心作用:统一所有消息的基础属性和接口,确保不同类型的消息能被框架一致处理。
  • 主要属性
    • content: str:消息的文本内容(如用户提问、AI 回复、工具结果等)。
    • type: str:消息类型标识(由子类定义,如 "human""ai""tool" 等),用于区分消息来源。
    • additional_kwargs: dict:存储附加元信息(如消息 ID、时间戳等)。
  • 特点:无法直接实例化,只能通过其子类(如 ToolMessageSystemMessage、HumanMessage)使用

ToolMessage(工具消息类)

  • 定位:继承自 BaseMessage,专门用于封装外部工具的调用结果
  • 核心作用:在智能体调用工具(如 API、数据库、搜索引擎)后,将工具返回的结果标准化,以便智能体识别和解析。
  • 独有属性
    • tool_call_id: str:工具调用的唯一标识符,用于将结果与对应的工具调用指令关联(解决多工具并行调用时的匹配问题)。
  • 典型场景
    智能体调用天气 API 后,API 返回的气温数据会被包装成 ToolMessage,通过 tool_call_id 绑定到对应的调用指令,确保智能体能正确匹配 “调用→结果”。

SystemMessage(系统消息类)

3. from langchain_core.tools import tool

tool 装饰器的核心作用

  • 定位:继承自 BaseMessage,用于传递系统级指令或配置
  • 核心作用:向大语言模型(LLM)传递背景信息、角色设定或行为规则,指导模型的回答风格和逻辑(类似 “给 AI 的隐形指令”)。
  • 典型场景
    • 定义 AI 角色:SystemMessage(content="你是专业的数学老师,用通俗语言讲解公式")
    • 设定回答约束:SystemMessage(content="仅用中文回复,拒绝讨论无关话题")

在 LangChain 中,“工具” 指的是智能体可以调用的外部功能(如查询数据库、调用 API、执行代码等)。tool 装饰器的作用是:
给普通函数添加标准化的元数据(如名称、描述、参数信息等),让智能体能够理解 “这个工具能做什么”“需要传入什么参数”,从而实现自动调用。

基本使用方法

使用 tool 装饰器定义工具的步骤非常简单:

  1. 定义一个普通 Python 函数,实现具体功能(如查询天气、计算数值等)。
  2. 用 @tool 装饰该函数,并通过参数或文档字符串描述工具的功能和参数。
  3. 智能体即可识别该工具,并在需要时自动调用。
from langchain_core.tools import tool

# 用@tool装饰器定义一个计算加法的工具
@tool
def add(a: int, b: int) -> int:
    """计算两个整数的和。
    
    Args:
        a: 第一个整数
        b: 第二个整数
    """
    return a + b

    4. from langgraph.graph.message import add_message

    add_messages 的核心功能

    在多轮对话或状态流转场景中,我们经常需要将新消息(如用户输入、AI 回复、工具结果)添加到已有的对话历史中。add_messages 的作用就是规范化这一过程,确保消息列表的合并逻辑安全、一致,避免因重复添加、类型错误或状态覆盖导致的问题。

    主要特性与行为

    add_messages 通常接收两个参数:

    1. existing_messages:已有的消息列表(可能为空,或包含 HumanMessageAIMessage 等类型)。
    2. new_messages:要添加的新消息(可以是单个消息对象,也可以是消息列表)。

    它的核心行为是:

    • 若 existing_messages 为空(None 或空列表),则直接返回 new_messages 作为初始列表。
    • 若 existing_messages 已存在,则将 new_messages 中的所有消息追加到 existing_messages 末尾,返回合并后的新列表(不会修改原列表,避免副作用)。
    • 自动处理 new_messages 的格式:无论传入的是单个消息还是列表,都会统一转换为列表后再合并。

    3. 定义状态字典

    class State(TypedDict):
        message:Annotated[Sequence[BaseMessage],add_messages]

    字段message的类型为序列类型(如:List, tuple等),元数据为add_message, 元数据可以是操作,也可以是字符串,用于补充描述该类型的约束、含义或用途,这里使用add_message操作,指明将将新消息(如用户输入、AI 回复、工具结果)添加到已有的对话历史中。

    4. 定义tool

    @tool
    def add(a:int,b:int)->int:
        """这是一个加法函数,将两个数加起来"""
        return a+b
    @tool
    def subtract(a:int,b:int)->int:
        """这是一个加法操作"""
        return a-b
    @tool
    def multiply(a:int,b:int):
        """这是一个乘法操作"""
        return a*b

    其中 @tool 是工具修饰符,下面就是定义一个函数,函数中的"""说明""" 是不可或缺的,它是让大模型选择该工具的重要指示。

    5. 给大模型装上工具

    tools = [add,subtract,multiply]
    #让大模型能够使用这些工具,相当于给大模型装上了手脚
    llm = init_chat_model("deepseek-chat").bind_tools(tools)

    6. 定义graph中的节点函数和工具节点

    def model_call(x:State)->State:
        Sys_prompt = SystemMessage(content="你是我的AI助手,请尽你所能回答我的问题")
        response = llm.invoke([Sys_prompt] + x["message"])
        x["message"] = [response]
        return x
    
    def should_continue(y:State)->str:
        message = y["message"]
        last_msg = message[-1]
        if not last_msg.tool_calls:
            return "end"
        else:
            return "continue"
    
    tool_node = ToolNode(tools=tools)

    其中SystemMessage用于设置系统提示词,llm.invoke触发大模型根据“系统提示词和参数中的状态消息”进行思考和反应,x['message']=[response] 让x记住更新后的消息。

    message=y['message']读取参数中的消息,last_msg取出最后一条对话,如果last_msg中有tool_calls操作,说明这是一条AIMessage, 其中AIMessage是有tool_calls操作的,否则就不是AIMessage

    tool_node = ToolNode(tools=tools), 将所有的工具封装成一个工具节点。

    7. 创建graph, 给graph添加node 和 edge, 编译graph得到可执行任务的agent

    graph = StateGraph(State)
    graph.add_node("model",model_call)
    graph.add_node("tool",tool_node)
    
    graph.add_edge(START,"model")
    graph.add_conditional_edges(
        "model",
        should_continue,
        {
            "continue":"tool", #继续依赖工具去解决
            "end":END,
        },
    )
    
    graph.add_edge("tool","model")
    app = graph.compile()
    图的结构:

    8. 使用流式方式触发agent 工作

    def print_stream(stream):
        for s in stream:
            # 引用状态中的messages字段(复数)
            msg = s["messages"][-1]  # 统一为message
            msg.pretty_print()
    
    # 输入参数使用(复数)messages(与状态定义一致)
    inputs = {"message": [("user", "40 + 12 将求和结果与 6 相乘. 接下来再将一个笑话")]}
    # inputs = {"messages": [HumanMessage(content=" 40 + 12 将求和结果与 6 相乘. 接下来再将一个笑话")]}  # 统一为message
    strm = app.stream(inputs, stream_mode="values")
    print_stream(strm)

    定义了一个print_stream函数,该函数的功能是输出参数stream中的信息,

    • for s in stream:迭代智能体产生的每一个状态更新(流式输出)。
    • s["message"][-1]:获取当前状态中最新的一条消息("message" 字段存储对话历史)。
    • 条件判断:如果消息是元组(特殊格式)则直接打印,否则调用 pretty_print() 格式化输出(LangChain 消息对象的内置方法,会显示消息类型和内容)。

    app.stream(...):以流式方式运行智能体,stream_mode="values" 表示返回简洁的状态值(仅包含 message 等核心字段)。

    inputs:用户请求包含两个任务 —— 计算 (40 + 12) × 6,并讲一个笑话。消息格式是 ("user", "内容")(会被自动转换为 HumanMessage):HumanMessage(content="xxxx")

    这里的user 角色还可以换成ai, 那么消息格式会自动转换成AIMessage : AIMessage(content="xxxx")

    除此之外还可以换成system, tool , 自动转换成消息类型:SystemMessage, ToolMessage

    最后的运行结果:

    ================================ Human Message 

     40 + 12 将求和结果与 6 相乘. 接下来再将一个笑话
    ================================== Ai Message 
    Tool Calls:
      add (call_0_bf1be394-9817-43d5-bbb8-0a5fed71e1ac)
     Call ID: call_0_bf1be394-9817-43d5-bbb8-0a5fed71e1ac
      Args:
        a: 40
        b: 12
    ================================= Tool Message 
    Name: add

    52
    ================================== Ai Message 
    Tool Calls:
      multiply (call_0_a8af7994-43ea-4e9f-aca8-591c4b3c3c3e)
     Call ID: call_0_a8af7994-43ea-4e9f-aca8-591c4b3c3c3e
      Args:
        a: 52
        b: 6
    ================================= Tool Message 
    Name: multiply

    312
    ================================== Ai Message 

    40 + 12 的结果是 52,再乘以 6 得到 312。

    现在讲一个笑话:

    **笑话:**
    有一天,数学老师问小明:“如果你有5个苹果,我拿走3个,你还剩几个?”  
    小明回答:“我不知道,但我知道你肯定没拿走我的香蕉!”

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值