LangGraph 状态机与 ReAct Agent

​​​1. 阶段概述

1.1 学习目标

掌握 LangGraph 1.0 的核心概念,从零构建一个生产级的 ReAct Agent:

  • 理解 LangGraph 的状态机编程模型
  • 掌握 StateGraph 的节点、边、条件边概念
  • 理解 ReAct(Reason + Act)循环的原理与实现
  • 掌握 Checkpoint 机制实现会话记忆持久化
  • 学会多工具场景下的 Agent 设计
  • 对比理解 Java 实现思路

1.2 验证结果

|-------|---------------------------------|------|
| Step | 验证内容 | 结果 |
| Step1 | (15+25)*2 = 80,ReAct 循环 | PASS |
| Step2 | Checkpoint 记忆持久化,同 session 多轮对话 | PASS |
| Step3 | 多工具(日期/天气/计算)并行调用 | PASS |
| Step4 | Java 状态机实现思路 | PASS |


2. LangGraph 核心架构

2.1 什么是 LangGraph

LangGraph 是 LangChain 生态中的图执行引擎 ,用于构建有状态的多步骤 Agent 应用。与 LangChain 0.x 的链式调用不同,LangGraph 将 Agent 建模为一个状态机

python 复制代码
LangChain 0.x:  Chain = Linear Pipeline
                 Input → LLM → Output(单次调用)

LangGraph:      Graph = State Machine
                 Nodes (Functions) + Edges (Transitions) + State
                 支持循环、条件分支、人机交互中断

2.2 核心概念

|----------------------|--------------------|---------------------|
| 概念 | 说明 | 类比 |
| State | 应用状态的快照(TypedDict) | Java Bean / POJO |
| Node | 图中的计算节点(Python 函数) | 微服务 / 函数 |
| Edge | 节点之间的有向边 | API 调用链路 |
| Conditional Edge | 条件边(根据状态决定走向) | if-else 路由 |
| Checkpoint | 状态快照持久化 | 数据库事务日志 |
| thread_id | 会话隔离标识 | HttpSession.getId() |

2.3 工作原理

复制代码
User Input
    │
    ▼
┌──────────────────────────────────────────────────────────┐
│  app.invoke(input_state, config={"thread_id": "s1"})     │
│                                                          │
│  1. 从 Checkpoint 恢复 thread_id 的历史状态              │
│  2. 状态 + 输入 → 节点函数 → 新状态                      │
│  3. 条件边判断 → 选择下一个节点                          │
│  4. 循环直到到达 END 或 remaining_steps=0                │
│  5. 保存最终状态到 Checkpoint                            │
└──────────────────────────────────────────────────────────┘
    │
    ▼
  Output / Final Response

3. LangGraph 1.0 API 变化

3.1 版本差异速查

LangGraph 从 0.x 升级到 1.0 有大量破坏性变更,学习时需注意版本:

|----------------|------------------------------------|-------------------------------------------------|
| 特性 | LangGraph 0.x | LangGraph 1.0 |
| 创建 Agent | create_react_agent(model, tools) | 手写 StateGraph 或 langchain.agents.create_agent |
| 循环控制 | max_iterations=N 参数 | remaining_steps + 条件边 |
| 状态定义 | 任意 dict | TypedDict + Annotated |
| 消息语义 | 列表替换 | add_messages 语义(追加) |
| tool_calls | tc["function"]["name"] | tc["name"] / tc["args"] |
| Checkpoint | MemorySaver() | 同上,API 稳定 |

3.2 create_react_agent 的废弃

python 复制代码
# LangGraph 0.x(旧版)
from langchain.agents import create_react_agent
agent = create_react_agent(llm, tools, messages_modifier)

# LangGraph 1.0(新版):无等价 API,需手写状态机
# 正确做法:用 StateGraph 自己搭

3.3 循环控制:remaining_steps

python 复制代码
# 旧版:用 max_iterations 控制循环次数(已废弃)
agent = create_react_agent(llm, tools, max_iterations=5)  # ❌

# 新版:在状态中手动维护 remaining_steps
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]
    remaining_steps: int  # ← 必须自己加到状态里

def model_node(state: AgentState) -> dict:
    new_steps = state["remaining_steps"] - 1
    response = llm.bind_tools(tools).invoke(state["messages"])
    return {"messages": [response], "remaining_steps": new_steps}

4. 状态定义:TypedDict 与 Annotated

4.1 基础状态定义

python 复制代码
from typing import TypedDict, Annotated

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]
    remaining_steps: int

4.2 TypedDict vs 普通 dict

|-------------|----------------|---------|
| 特性 | TypedDict | 普通 dict |
| 类型提示 | 有(IDE 友好) | 无 |
| 运行时检查 | 无 | 无 |
| Pydantic 验证 | 无 | 无 |
| 使用场景 | LangGraph 状态定义 | 通用数据容器 |

4.3 Annotated 与 Reducer 语义

Annotated[list, add_messages] 的含义:

复制代码
Annotated[list, add_messages]
         │         │
         │         └── Reducer 函数:定义如何合并两次状态
         │             add_messages = 追加语义(list.extend)
         │             效果:messages 永远只追加,不覆盖
         │
         └── 被标注的类型

Reducer 函数对比:

|----------------|----------|---------------|
| Reducer | 效果 | 类比 |
| add_messages | 新消息追加到列表 | List.add() |
| operator.add | 数值相加 | int + int |
| operator.or_ | 集合并集 | Set.union() |


5. 节点函数设计

5.1 节点函数的签名规范

LangGraph 节点函数接收 state(当前状态),返回 dict(状态更新):

python 复制代码
def model_node(state: AgentState) -> dict:
    # state: 当前完整状态
    # 返回: 要更新的字段(dict),其他字段不变
    return {"messages": [new_message], "remaining_steps": new_steps}

5.2 model_node:LLM 决策节点

python 复制代码
def model_node(state: AgentState) -> dict:
    """LLM 节点:调用模型,决定是否需要调用工具"""
    new_steps = state["remaining_steps"] - 1
    # bind_tools 让 LLM 知道有哪些工具可用
    response = llm.bind_tools(tools).invoke(state["messages"])
    return {
        "messages": [response],
        "remaining_steps": new_steps
    }

5.3 tool_node:工具执行节点

python 复制代码
def tool_node(state: AgentState) -> dict:
    """工具节点:执行 LLM 请求的每个工具调用"""
    messages = state["messages"]
    last_msg = messages[-1]
    
    if not isinstance(last_msg, AIMessage) or not last_msg.tool_calls:
        return {"messages": []}
    
    results = []
    for tc in last_msg.tool_calls:
        tool_name = tc["name"]          # LangGraph 1.0 格式
        tool_args = tc["args"]          # LangGraph 1.0 格式
        
        matched_tool = next((t for t in tools if t.name == tool_name), None)
        if matched_tool:
            result = matched_tool.invoke(tool_args)
        
        results.append(ToolMessage(
            content=str(result),
            tool_call_id=tc["id"],
            name=tool_name
        ))
    
    return {"messages": results}

5.4 should_continue:条件边函数

python 复制代码
def should_continue(state: AgentState) -> str:
    """条件边:决定 model 节点后下一步走哪里"""
    if state["remaining_steps"] <= 0:
        return "end"
    last_msg = state["messages"][-1]
    if isinstance(last_msg, AIMessage) and last_msg.tool_calls:
        return "continue"  # 有工具调用 → 继续
    return "end"          # 无工具调用 → 结束

6. 图的构建:StateGraph API

6.1 构建流程

python 复制代码
StateGraph(AgentState)
    │
    ├── add_node("model", model_node)       # 注册节点
    ├── add_node("tools", tool_node)
    │
    ├── add_edge(START, "model")            # 入口边
    │
    ├── add_conditional_edges(              # 条件边
    │     "model",
    │     should_continue,
    │     {"continue": "tools", "end": END}
    │ )
    │
    └── add_edge("tools", "model")          # 循环边
            │
            ▼
      builder.compile(checkpointer=...)

6.2 完整图结构

复制代码
                    ┌─────────────────┐
                    │      START      │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │     model      │ ◄── LLM 决策节点
                    └────────┬────────┘
                             │
              ┌──────────────┴──────────────┐
              │   should_continue() 条件判断  │
              └──────────────┬──────────────┘
                    ┌────────┴────────┐
                    │               │
                "continue"       "end"
                    │               │
                    ▼               ▼
            ┌───────────┐      ┌───────┐
            │   tools   │      │  END  │
            └─────┬─────┘      └───────┘
                  │
                  ▼
            ┌───────────┐
            │   model   │ ──────► (循环直到无 tool_calls)
            └───────────┘

6.3 特殊节点:START 和 END

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

print(START)  # "__start__"  入口标记
print(END)     # "__end__"    结束标记

# START 是虚拟入口,不需要定义,但必须有一条边从 START 出发
# END 是虚拟终点,所有路径最终都应到达 END

7. Checkpoint 机制

7.1 概念

Checkpoint 是 LangGraph 的状态快照持久化机制,类似于数据库的 WAL(Write-Ahead Log):

|------------------|-----------------------|
| 组件 | 作用 |
| Checkpoint | 某时刻的完整状态快照 |
| thread_id | 会话隔离标识,类似 HttpSession |
| Checkpointer | 持久化策略(内存/文件/数据库) |

7.2 MemorySaver

python 复制代码
from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()
app = builder.compile(checkpointer=checkpointer)

# 调用时指定 thread_id
result = app.invoke(
    input_state,
    config={"configurable": {"thread_id": "user_001"}}
)

7.3 Checkpoint 存储层级

复制代码
app.invoke(state, config={"configurable": {"thread_id": "s1"}})

     thread_id="s1"
          │
          ▼
┌─────────────────────────────────────────┐
│  Checkpointer                            │
│  ┌─────────────────────────────────┐   │
│  │ thread_id: "s1"                 │   │
│  │ checkpoint: {messages:[...],    │   │
│  │              remaining_steps:8} │   │
│  └─────────────────────────────────┘   │
│  ┌─────────────────────────────────┐   │
│  │ thread_id: "s2"                 │   │
│  │ checkpoint: {...}               │   │
│  └─────────────────────────────────┘   │
└─────────────────────────────────────────┘

8. 多工具调用

8.1 工具定义

python 复制代码
from langchain_core.tools import tool

@tool
def calculator(expression: str) -> str:
    """数学计算器"""
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return f"计算结果:{result}"
    except Exception as e:
        return f"计算错误:{e}"

@tool
def get_weather(city: str) -> str:
    """获取城市天气(模拟数据)"""
    weather_db = {
        "Hangzhou": "Sunny, 25C",
        "Beijing": "Cloudy, 22C",
    }
    return weather_db.get(city, f"{city} 天气数据不可用")

tools = [calculator, get_weather]

8.2 @tool 装饰器做了什么

复制代码
Python 函数 ──@tool──► StructuredTool 对象
                              │
                              ├── name: 函数名
                              ├── description: 文档字符串
                              └── args_schema: Pydantic Model(从类型注解生成)

8.3 多工具调用执行路径

python 复制代码
User: "今天日期?杭州天气?12+34?"
          │
          ▼
┌──────────────────────────────────────────┐
│ model_node():                            │
│   LLM 决定并行调用 3 个工具               │
│   输出: AIMessage(tool_calls=[            │
│     {name:"date_today", args:{}},        │
│     {name:"get_weather", args:{"city":   │
│       "Hangzhou"}},                      │
│     {name:"calculator", args:{"expressi- │
│       on":"12+34"}}                      │
│   ])                                      │
└──────────────────┬───────────────────────┘
                   │ tool_calls
                   ▼
┌──────────────────────────────────────────┐
│ tool_node():                             │
│   遍历 3 个 tool_call                    │
│   → date_today.invoke({}) → "2026-05-21"│
│   → get_weather.invoke({city:"..."})    │
│   → calculator.invoke({expression:"12+34"})
│                                           │
│   输出: [ToolMessage, ToolMessage, Tool- │
│   Message]                                │
└──────────────────┬───────────────────────┘
                   │ ToolMessages
                   ▼
            回到 model_node()
            LLM 整合结果,返回最终回答

9. Java 对比实现

9.1 核心思路

Java 没有 LangGraph,但可以用设计模式实现类似的状态机:

|-----------------------|----------------------|
| LangGraph | Java 实现 |
| StateGraph | 自定义 StateMachine 类 |
| Node 函数 | Command / Handler 接口 |
| add_edge | 状态转换表(Map) |
| add_conditional_edges | if-else 或策略模式 |
| remaining_steps | AtomicInteger 递减计数器 |
| Checkpoint | Redis Hash / 数据库 |
| thread_id | HttpSession.getId() |

9.2 简化实现思路

java 复制代码
// 状态
public class AgentState {
    public List<Message> messages;
    public int remainingSteps;
}

// 节点接口
public interface Node {
    AgentState process(AgentState state);
}

// LLM 节点(类似 model_node)
public class ModelNode implements Node {
    @Override
    public AgentState process(AgentState state) {
        // 1. 构建消息历史
        // 2. 调用 LLM API(HTTP)
        // 3. 解析响应中的 tool_calls
        // 4. remainingSteps--
        return newState;
    }
}

// 状态机
public class AgentStateMachine {
    private Map<String, Node> nodes = new HashMap<>();
    private Map<String, Function<AgentState, String>> transitions = new HashMap<>();
    
    public void addNode(String name, Node node) { ... }
    public void addEdge(String from, String to) { ... }
    public void addConditionalEdge(String from, Function<AgentState, String> condition) { ... }
    
    public AgentState run(AgentState initial, String threadId) {
        // 1. 从 Redis/H2 恢复 checkpoint(threadId)
        // 2. 循环直到 terminal 状态
        // 3. 保存 checkpoint
        return result;
    }
}

10. 生产环境持久化方案

10.1 方案对比

|---------------------|-------|---------|---------|-------|
| 方案 | 存储 | 优点 | 缺点 | 推荐场景 |
| MemorySaver | 内存 | 零配置、极快 | 进程重启丢失 | 开发调试 |
| SQLiteSaver | 文件 | 零依赖、持久化 | 并发差 | 单机项目 |
| PostgreSQLSaver | 数据库 | 生产级、并发 | 需数据库 | 生产环境 |
| RedisSaver | Redis | 高性能、分布式 | 需 Redis | 高并发项目 |

10.2 PostgreSQL 连接池版本(推荐生产)

python 复制代码
import psycopg2
from psycopg2 import pool
from langgraph.checkpoint.postgres import PostgresSaver

class PostgresConnectionManager:
    _instance = None
    _connection_pool = None
    
    def initialize(self, host, port, database, user, password,
                   min_connections=5, max_connections=20):
        self._connection_pool = psycopg2.pool.ThreadedConnectionPool(
            minconn=min_connections,
            maxconn=max_connections,
            host=host, port=port, database=database,
            user=user, password=password
        )
    
    def get_pool(self):
        return self._connection_pool

# 使用
DB_CONFIG = {
    "host": "localhost",
    "port": 5432,
    "database": "langgraph_db",
    "user": "postgres",
    "password": "your_password",
    "min_connections": 5,
    "max_connections": 20
}
pool_manager = PostgresConnectionManager()
pool_manager.initialize(**DB_CONFIG)
checkpointer = PostgresSaver(pool_manager.get_pool())
checkpointer.setup()  # 首次建表
app = builder.compile(checkpointer=checkpointer)

11. 常见错误与教训

11.1 LangGraph 1.0 API 破坏性变更

错误 : 使用 create_react_agent(已废弃)

复制代码
# 报错:AttributeError 或参数不存在
agent = create_react_agent(model, tools, max_iterations=5)  # ❌

# 正确:手写 StateGraph
builder = StateGraph(AgentState)  # ✅

11.2 忘记添加 remaining_steps

复制代码
# 报错:节点返回的 state 缺少 remaining_steps
app.invoke({"messages": [...], "remaining_steps": 10})  # ✅
app.invoke({"messages": [...]})  # ❌ 无 remaining_steps

11.3 tool_calls 格式变更

复制代码
# LangGraph 0.x(旧格式)
tc["function"]["name"]   # ❌
tc["function"]["arguments"]

# LangGraph 1.0(新格式)
tc["name"]               # ✅
tc["args"]               # ✅

11.4 Windows emoji 打印问题

复制代码
# Windows cmd 下 LLM 输出可能含 emoji,打印时报错
# UnicodeEncodeError: 'gbk' codec can't encode character

# 解决:过滤非 ASCII 字符
import re
def safe_print(text):
    text = re.sub(r'[\U0001F300-\U0001F9FF\U00002600-\U000026FF]', '', str(text))
    print(text)

11.5 消息历史遗漏

相关推荐
烤代码的吐司君14 小时前
面向对象编程(OOP)在 Python 中的实现——类、继承与特殊方法
开发语言·python
小龙报14 小时前
【优选算法】双指针专项:1.移动零 2. 复写零 3.快乐数
java·c语言·数据结构·c++·python·算法·面试
IT策士14 小时前
Django 从 0 到 1 打造完整电商平台:我的订单列表与订单详情
后端·python·django
AI行业学习14 小时前
CC-Switch Windows + macOS 下载安装配置全流程
java·开发语言·人工智能·python
布吉岛的石头14 小时前
Java 程序员第 22 阶段:Function Call 工具调用实战,Java 封装大模型外部能力
java·人工智能·python
l1t14 小时前
DeepSeek总结的使用实体-组件-系统和基于存在性处理进行Python编程简介
开发语言·python
曲幽14 小时前
FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示?
python·vue3·api·fastapi·web·ant design·view·menu·frontend
rayyy915 小时前
神经网络模型的外推性验证
pytorch·python·深度学习
小哈里15 小时前
【Agent】AI编程工程化实践 —— Context上下文工程,SDD规范驱动开发,MCP&Skills
驱动开发·agent·ai编程·上下文工程·规范驱动开发