LangGraph 入门
前面几章我们用 while 循环手写多轮对话与 ReAct(第 6~8 章),流程藏在代码里。一旦叠加"多轮状态、工具循环、会话恢复、危险操作人工审批"这些需求,手写分支会越来越散。LangGraph 是 LangChain 官方的智能体编排框架:把运行过程画成一张"状态图"——节点是处理函数、边是流转规则,循环、状态合并、持久化、人工介入全部由框架托管。本章先装好环境、跑通一个最小图,感受它的写法。
安装与环境变量
基于 Python 3.10+。模型客户端沿用全书约定,从三个环境变量读取配置(示例模型 deepseek-chat 可换成任意 OpenAI 兼容模型):
pip install langgraph langchain-openai
import os
from langchain_openai import ChatOpenAI
# 读取 LLM_API_KEY / LLM_BASE_URL / LLM_MODEL(同前面章节)
model = ChatOpenAI(
model=os.getenv("LLM_MODEL", "deepseek-chat"),
api_key=os.getenv("LLM_API_KEY"),
base_url=os.getenv("LLM_BASE_URL"),
)
最小图:两个节点一条边
把"写初稿 → 润色"拆成两个节点,节点就是普通 Python 函数:
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict): # 状态:贯穿全图的"共享黑板"
topic: str
essay: str
def draft(state): # 节点 1:写初稿,返回部分更新
reply = model.invoke(f"用 80 字介绍:{state['topic']}")
return {"essay": reply.content}
def polish(state): # 节点 2:润色
reply = model.invoke(f"把下面文字改通顺:\n{state['essay']}")
return {"essay": reply.content}
builder = StateGraph(State) # 1. 用状态类型建图
builder.add_node("draft", draft) # 2. 注册节点
builder.add_node("polish", polish)
builder.add_edge(START, "draft") # 3. 连边:入口→写稿
builder.add_edge("draft", "polish") # 写稿→润色
builder.add_edge("polish", END) # 润色→出口
graph = builder.compile() # 4. 编译成可调用对象
result = graph.invoke({"topic": "什么是大模型"})
print(result["essay"])
# 输出:一段润色后的介绍文字(内容由模型生成,每次可能不同)
要点:节点函数签名统一是 f(state) -> dict,收到的是完整状态,返回的是"要改动哪些字段"的部分字典,框架自动合并后再交给下一个节点。执行顺序完全由边决定,draft 一定先于 polish。
四个核心名词
- State:用 TypedDict 声明的状态结构,所有节点共享读写(下一章重点)。
- 节点 node:一个处理步骤,本质是普通函数。
- 边 edge:节点间的流转;条件边按返回值选择下一步去哪。
- compile():把定义好的图编译为可运行对象,之后可 invoke/stream。
与手写循环对比
| 需求 | 手写循环(第 6~8 章) | LangGraph |
|---|---|---|
| 运行流程 | 隐式写在 while 里 | 显式节点+边,可读可画 |
| 历史消息 | 自己维护 messages 列表 | 状态字段+Reducer 自动累积 |
| 循环退出 | 手动 break | 条件边决定去留 |
| 会话恢复 | 自己落库再读回 | checkpointer 按 thread_id 恢复 |
| 人工把关 | 手写 if 拦截 | interrupt() 挂起,批准后继续 |
一句话:手写方案适合一两个工具的简单循环;当"循环 + 状态 + 持久化 + 人工介入"同时出现时,把它们变成图的显式要素,代码反而更短、更好维护。
何时用 LangGraph
推荐在以下场景使用:需要多轮工具调用循环(第 18 章)、需要恢复历史会话(第 20 章)、需要危险操作审批(第 19 章)、需要流式输出(第 21 章)、需要把多个子流程组合并行(第 22 章)。只调一次模型、无状态、无工具的简单问答,用第 2~3 章的直接调用即可,不必上框架。
16~22 章内容地图
| 章 | 主题 | 关键 API |
|---|---|---|
| 16 | 入门与最小图 | StateGraph、add_node、add_edge、compile、invoke |
| 17 | 状态、Reducer 与条件边 | TypedDict、add_messages、add_conditional_edges |
| 18 | 用图写 ReAct | bind_tools、ToolNode |
| 19 | 人工审批 | interrupt、Command |
| 20 | 持久化与会话 | checkpointer、thread_id |
| 21 | 流式输出 | stream、stream_mode |
| 22 | 子图与并行 | Send、编译子图当节点 |
常见问题
- 编译好的图能复用吗?能。builder.compile() 只需一次,之后可反复 invoke/stream,多次调用互不影响。
- 节点能访问全局变量或读写文件吗?可以,但状态字段应尽量只放可序列化数据(字符串、数字、列表、字典),为后续接 checkpointer 做准备。
- 节点返回的键没在 State 里定义会怎样?该键会被忽略(部分版本会给出告警),写代码时让返回键与 TypedDict 字段保持一致。
- 忘了填必填字段就 invoke?框架会报缺字段错误,按 TypedDict 逐项提供输入即可。
小结:LangGraph 用"状态图"显式描述智能体流程——add_node 注册函数、add_edge 连流转、条件边实现分支循环、compile() 编译后 invoke 执行;循环、状态、持久化、人工介入都变成图的零件,接下来几章逐个拆开讲解。