目 录CONTENT

文章目录

LangGraph 快速上手:从零构建你的第一个 Agent

PySuper
2025-09-27 / 0 评论 / 0 点赞 / 11 阅读 / 0 字
温馨提示:
本文最后更新于2026-05-28,若内容或图片失效,请留言反馈。 所有牛逼的人都有一段苦逼的岁月。 但是你只要像SB一样去坚持,终将牛逼!!! ✊✊✊

一张图了解全貌

┌─────────────────────────────────────────────────────────────┐
│                    LangGraph 全局视角                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌──────────┐    ┌──────────┐    ┌──────────┐             │
│   │  State    │    │  Node    │    │  Edge    │             │
│   │  共享数据  │───▶│  处理步骤  │───▶│  跳转逻辑  │             │
│   └──────────┘    └──────────┘    └──────────┘             │
│        │               │               │                    │
│        ▼               ▼               ▼                    │
│   TypedDict        def node()     add_edge()               │
│   MessagesState    接收state      add_conditional_edges()   │
│   Annotated+Reducer 返回更新                               │
│                                                             │
│   ┌──────────────────────────────────────────────┐         │
│   │           StateGraph → compile() → invoke()  │         │
│   │           定义图    编译图     运行图           │         │
│   └──────────────────────────────────────────────┘         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

一、是什么

一句话定义

LangGraph = 用有向图构建有状态的 AI Agent 工作流。

拆开看三个关键词:

  • 有向图:节点 + 边,数据沿边流动,不会乱跑

  • 有状态:所有节点共享同一份 State,每一步都能读到之前的结果

  • Agent 工作流:不是简单的链式调用,支持循环、分支、条件路由

核心三要素

┌────────────┐       ┌────────────┐       ┌────────────┐
│   State    │       │    Node    │       │    Edge    │
│   状态     │       │    节点     │       │    边      │
├────────────┤       ├────────────┤       ├────────────┤
│ 图执行过程中 │       │ 一个Python  │       │ 节点之间的  │
│ 的共享数据   │       │ 函数,接收   │       │ 跳转逻辑   │
│            │       │ state,返回  │       │            │
│ TypedDict  │       │ 更新        │       │ 固定跳转 or │
│ 定义结构    │       │            │       │ 条件路由    │
└────────────┘       └────────────┘       └────────────┘

对比 LangChain

维度

LangChain

LangGraph

拓扑结构

链(一条线)

图(有向图)

状态

无状态/隐式传递

显式共享 State

循环

不支持

原生支持

条件分支

需要额外处理

add_conditional_edges

适用场景

简单线性流程

复杂多步决策

打个比方:Chain 是单行道,LangGraph 是立交桥。 单行道只能直走,立交桥有岔路、有环路、有条件转向。

Chain(链):  A ──▶ B ──▶ C ──▶ 输出

LangGraph(图):
              ┌──▶ 退款处理 ──▶ END
              │
  START ──▶ 意图分类 ──▶ 查询处理 ──▶ END
              │
              └──▶ 转人工   ──▶ END

二、怎么用

安装

pip install langgraph langchain-openai

就这么简单。langgraph 是核心库,langchain-openai 提供 LLM 接入。

最低依赖说明:Python >= 3.10,无需数据库、无需 Docker,装完就能跑。

环境变量

export OPENAI_API_KEY="sk-你的密钥"

如果用其他兼容 OpenAI 接口的模型(如 DeepSeek),也可以:

export OPENAI_API_KEY="sk-你的密钥"
export OPENAI_BASE_URL="https://api.deepseek.com"

第一个验证脚本

装好了没?跑一下这个,能输出就说明环境 OK:

from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict


class State(TypedDict):
    text: str


def node_a(state: State) -> dict:
    return {"text": state["text"] + "Hello "}


def node_b(state: State) -> dict:
    return {"text": state["text"] + "LangGraph!"}


graph = StateGraph(State)
graph.add_node("node_a", node_a)
graph.add_node("node_b", node_b)
graph.add_edge(START, "node_a")
graph.add_edge("node_a", "node_b")
graph.add_edge("node_b", END)

app = graph.compile()
result = app.invoke({"text": ""})
print(result["text"])  # Hello LangGraph!

看到 Hello LangGraph! 就成功了!

三、核心概念

3.1 State(状态)

State 是什么? 图执行过程中的共享数据。所有节点都能读,都能写。

你可以把它想象成一张"共享便签纸"——每个节点往上面写内容,下一个节点能看到前面写的所有东西。

TypedDict

from typing import TypedDict


class AgentState(TypedDict):
    messages: list  # 对话历史
    intent: str  # 意图分类结果
    count: int  # 计数器

这是最基础的写法,每个字段就是一个普通类型。

MessagesState

如果你的 State 主要就是聊天消息,LangGraph 提供了内置的 MessagesState,省得自己写:

from langgraph.graph import MessagesState

# MessagesState 等价于:
# class MessagesState(TypedDict):
#     messages: Annotated[list[AnyMessage], add_messages]

它已经帮你定义好了 messages 字段,并且内置了 add_messages 合并策略。

Annotated + Reducer

(add_messages)合并策略

这是 State 最关键的机制:当多个节点更新同一个字段时,怎么合并?

┌─────────────────────────────────────────────────────┐
│              Reducer 合并策略                        │
├─────────────────────────────────────────────────────┤
│                                                     │
│  没有 Reducer(默认):新值 直接覆盖 旧值            │
│  ┌─────────┐     ┌─────────┐     ┌─────────┐       │
│  │  count=1 │ ──▶ │ count=2 │ ──▶ │ count=2 │      │
│  └─────────┘     └─────────┘     └─────────┘       │
│  节点A设置        节点B覆盖        最终值=2          │
│                                                     │
│  有 Reducer(如 add_messages):新值 追加到 旧值     │
│  ┌────────────┐   ┌──────────────┐   ┌───────────┐  │
│  │ msgs=[A]   │─▶ │ msgs=[A,B]  │─▶ │ msgs=[A,B]│  │
│  └────────────┘   └──────────────┘   └───────────┘  │
│  节点A追加         节点B追加          最终=A+B        │
│                                                     │
└─────────────────────────────────────────────────────┘

from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import add_messages
from langchain_core.messages import AnyMessage

class AgentState(TypedDict):
    # messages 字段用 add_messages reducer:追加而非覆盖
    messages: Annotated[list[AnyMessage], add_messages]
    # 普通字段:新值覆盖旧值
    intent: str
    # 计数器用 operator.add 做累加
    count: Annotated[int, operator.add]

add_messages 做了什么?

  • 新消息没有 ID → 直接追加到列表末尾

  • 新消息有 ID 且已存在 → 替换同 ID 的旧消息(更新)

  • 新消息有 ID 且不存在 → 追加

这个设计非常贴合聊天场景:对话历史是追加的,但如果 AI 修正了之前的回复,可以通过相同 ID 覆盖。

3.2 Node(节点)

Node 是什么? 一个 Python 函数,接收当前 state,返回部分 state 更新。

节点函数

def my_node(state: AgentState) -> dict:
    # 1. 读取 state
    current_messages = state["messages"]
    
    # 2. 做点什么
    result = do_something(current_messages)
    
    # 3. 返回要更新的字段(不需要返回全部字段!)
    return {"messages": [result], "count": 1}

关键点

  • 只返回你想更新的字段,LangGraph 会自动合并

  • 如果字段有 Reducer,按 Reducer 规则合并

  • 如果没有 Reducer,新值直接覆盖旧值

START 和 END

LangGraph 有两个特殊的"虚拟节点",用来标记图的入口和出口:

START ──▶ 第一个节点
最后一个节点 ──▶ END

from langgraph.graph import START, END

graph.add_edge(START, "first_node")   # 图的入口
graph.add_edge("last_node", END)      # 图的出口

示例

"""
写 3 个节点函数
"""

from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage

llm = ChatOpenAI(model="gpt-4o-mini")


def greet_node(state: AgentState) -> dict:
    """打招呼节点:根据意图生成问候语"""
    return {"messages": [AIMessage(content="你好!有什么可以帮你的?")]}


def chat_node(state: AgentState) -> dict:
    """聊天节点:调用 LLM 回复用户"""
    response = llm.invoke(state["messages"])
    return {"messages": [response]}


def goodbye_node(state: AgentState) -> dict:
    """告别节点:生成结束语"""
    return {"messages": [AIMessage(content="再见!祝你好运!")]}

3.3 Edge(边)

Edge 是什么? 节点之间的跳转逻辑。分为两种:普通边和条件边。

普通边

add_edge(固定跳转)

A 完了永远去 B,没有商量余地:

graph.add_edge(START, "greet")      # 开始 → 打招呼
graph.add_edge("greet", "chat")     # 打招呼 → 聊天
graph.add_edge("chat", END)         # 聊天 → 结束

条件边

add_conditional_edges(根据 state 动态路由)

A 完了去哪?看情况!这是 LangGraph 最强大的地方:

              ┌──▶ 退款处理
              │
  意图分类 ───┼──▶ 查询处理
              │
              └──▶ 转人工

路由函数的

路由函数接收 state,返回一个字符串,表示下一个要去的节点名:

def route_by_intent(state: AgentState) -> str:
    """根据意图分类结果路由到不同节点"""
    if state["intent"] == "refund":
        return "refund_node"
    elif state["intent"] == "query":
        return "query_node"
    else:
        return "human_node"

示例:条件分支

from langgraph.graph import StateGraph, START, END

graph = StateGraph(AgentState)

# 注册节点
graph.add_node("classify", classify_node)
graph.add_node("refund", refund_node)
graph.add_node("query", query_node)
graph.add_node("human", human_node)

# 入口
graph.add_edge(START, "classify")

# 条件边:classify 完了根据意图走不同分支
graph.add_conditional_edges(
    "classify",              # 源节点
    route_by_intent,         # 路由函数
    {                        # 返回值 → 目标节点映射
        "refund": "refund",
        "query": "query",
        "human": "human",
    }
)

# 各分支都到终点
graph.add_edge("refund", END)
graph.add_edge("query", END)
graph.add_edge("human", END)

提示path_map 字典是可选的。如果不传,LangGraph 会把路由函数的返回值直接当作目标节点名。

3.4 Compile(编译)

为什么必须编译? 你定义的图只是一个"蓝图",compile() 会做这些事:

┌───────────────────────────────────────────────┐
│            compile() 做了什么                  │
├───────────────────────────────────────────────┤
│                                               │
│  1. 类型检查                                   │
│     └─ State 字段类型是否一致                   │
│                                               │
│  2. 连通性验证                                  │
│     └─ 从 START 能否到达所有节点                │
│     └─ 所有节点能否到达 END                     │
│     └─ 有没有孤立节点                           │
│                                               │
│  3. 构建 Pregel 执行引擎                       │
│     └─ 编译后才能 invoke / stream              │
│                                               │
└───────────────────────────────────────────────┘

# 编译
app = graph.compile()

# 带持久化的编译(后面第39篇会讲)
from langgraph.checkpoint.memory import MemorySaver
app = graph.compile(checkpointer=MemorySaver())

编译后你才能调用 invoke()stream()

四、实战案例

ChatAgent

5 步构建

┌─────────────────────────────────────────────────────┐
│              构建 LangGraph 的 5 步                  │
├─────────────────────────────────────────────────────┤
│                                                     │
│  1️⃣  定义 State   ──  TypedDict / MessagesState     │
│  2️⃣  写 Node      ──  def node(state) -> dict       │
│  3️⃣  画 Edge      ──  add_edge / add_conditional    │
│  4️⃣  Compile      ──  graph.compile()               │
│  5️⃣  Run          ──  app.invoke() / app.stream()   │
└─────────────────────────────────────────────────────┘

完整代码

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langgraph.graph import StateGraph, START, END, MessagesState

# ── 1️⃣ 定义 State ──────────────────────────────
# MessagesState 自带 messages 字段和 add_messages reducer

# ── 2️⃣ 写 Node ─────────────────────────────────
llm = ChatOpenAI(model="gpt-4o-mini")

def chatbot(state: MessagesState) -> dict:
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

# ── 3️⃣ 画 Edge + 构建图 ────────────────────────
graph = StateGraph(MessagesState)
graph.add_node("chatbot", chatbot)
graph.add_edge(START, "chatbot")
graph.add_edge("chatbot", END)

# ── 4️⃣ Compile ─────────────────────────────────
app = graph.compile()

# ── 5️⃣ Run ─────────────────────────────────────
result = app.invoke({"messages": [HumanMessage(content="你好,介绍下你自己")]})
print(result["messages"][-1].content)

invoke vs stream

# invoke:等全部执行完,一次性返回结果
result = app.invoke({"messages": [HumanMessage(content="你好")]})

# stream:逐步返回每个节点的输出,适合实时展示
for chunk in app.stream({"messages": [HumanMessage(content="你好")]}):
    print(chunk)  # 每个节点的增量输出

什么时候用哪个?

  • invoke:后台任务、批量处理

  • stream:实时对话、前端逐字展示

Agent + Tools

真正的 Agent 不只是聊天,还得能调用工具。这一节我们构建一个能查天气 + 搜索的 Agent。

整体架构

┌─────────────────────────────────────────────────────┐
│              带工具的 Agent 架构                      │
├─────────────────────────────────────────────────────┤
│                                                     │
│  START ──▶ agent ──┬──▶ tools ──▶ agent ──┬──▶ END │
│         (LLM决策)   │  (执行工具)  (再决策)  │        │
│                    │                       │        │
│                    └──▶ END                │        │
│                    (无需工具,直接回答)       │        │
│                                                     │
│  关键:agent 节点通过条件边决定                       │
│       "继续调工具" 还是 "结束"                        │
│                                                     │
└─────────────────────────────────────────────────────┘

定义 Tool

from langchain_core.tools import tool


@tool
def get_weather(city: str) -> str:
    """查询指定城市的天气"""
    # 模拟天气查询(实际项目可对接真实 API)
    weather_data = {
        "北京": "晴天,-2°C",
        "上海": "多云,5°C",
        "深圳": "小雨,18°C",
    }
    return weather_data.get(city, f"暂无{city}的天气数据")


@tool
def search_web(query: str) -> str:
    """搜索互联网信息"""
    # 模拟搜索(实际项目可对接搜索 API)
    return f"搜索结果:关于「{query}」的最新信息..."
 

完整代码

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.prebuilt import ToolNode, tools_condition


# ── 定义工具 ────────────────────────────────────
@tool
def get_weather(city: str) -> str:
    """查询指定城市的天气"""
    weather_data = {
        "北京": "晴天,-2°C",
        "上海": "多云,5°C",
        "深圳": "小雨,18°C",
    }
    return weather_data.get(city, f"暂无{city}的天气数据")


@tool
def search_web(query: str) -> str:
    """搜索互联网信息"""
    return f"搜索结果:关于「{query}」的最新信息..."


tools = [get_weather, search_web]

# ── 配置 LLM ───────────────────────────────────
llm = ChatOpenAI(model="gpt-4o-mini").bind_tools(tools)


# ── 定义节点 ────────────────────────────────────
def agent_node(state: MessagesState) -> dict:
    """LLM 决策节点:决定是调用工具还是直接回答"""
    system = SystemMessage(
        content="你是一个智能助手,可以查询天气和搜索信息。请用中文回答。"
    )
    messages = [system] + state["messages"]
    response = llm.invoke(messages)
    return {"messages": [response]}


# ToolNode 是 LangGraph 内置的工具执行节点
tool_node = ToolNode(tools)

# ── 构建图 ─────────────────────────────────────
graph = StateGraph(MessagesState)

graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)

graph.add_edge(START, "agent")

# 条件边:LLM 是否要调用工具?
# tools_condition 是内置的路由函数:
#   - 如果 LLM 返回了 tool_calls → 走 "tools" 节点
#   - 如果 LLM 没有返回 tool_calls → 走 END
graph.add_conditional_edges("agent", tools_condition, {"tools": "tools", END: END})

# 工具执行完回到 agent,让 LLM 再决策
graph.add_edge("tools", "agent")

# ── 编译运行 ───────────────────────────────────
app = graph.compile()

# 测试:查天气
result = app.invoke({"messages": [HumanMessage(content="北京今天天气怎么样?")]})
print(result["messages"][-1].content)

# 测试:搜索
result = app.invoke(
    {"messages": [HumanMessage(content="帮我搜一下 LangGraph 最新版本")]}
)
print(result["messages"][-1].content)

执行流程

用户: "北京今天天气怎么样?"

START → agent(LLM决策: 需要调get_weather)
      → tools(执行: get_weather("北京") → "晴天,-2°C")
      → agent(LLM决策: 信息够了,直接回答)
      → END

输出: "北京今天是晴天,气温-2°C,比较冷,注意保暖哦!"

CustomerAgent

现在来个更贴近实际业务的场景:一个智能客服,能根据用户意图走不同处理路径。

架构图

┌──────────────────────────────────────────────────────┐
│              客服 Agent 架构                          │
├──────────────────────────────────────────────────────┤
│                                                      │
│  START ──▶ classify ──┬──▶ refund ────▶ END         │
│          (意图分类)     ├──▶ query  ────▶ END         │
│                       └──▶ human  ────▶ END          │
│                                                      │
│  classify: LLM 判断意图 → refund / query / human     │
│  refund:   生成退款流程提示                           │
│  query:    回答查询问题                               │
│  human:    生成转人工话术                             │
│                                                      │
└──────────────────────────────────────────────────────┘

完整代码

from typing import Annotated
from typing_extensions import TypedDict
from langchain_openai import ChatOpenAI
from langchain_core.messages import AnyMessage, HumanMessage, SystemMessage, AIMessage
from langgraph.graph import StateGraph, START, END, add_messages


# ── 1️⃣ 定义 State ──────────────────────────────
class ServiceState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]
    intent: str  # 意图: refund / query / human
    response: str  # 最终回复


# ── 2️⃣ 写 Node ─────────────────────────────────
llm = ChatOpenAI(model="gpt-4o-mini")


def classify_node(state: ServiceState) -> dict:
    """意图分类节点:判断用户是退款、查询还是需要转人工"""
    last_msg = state["messages"][-1].content
    prompt = [
        SystemMessage(
            content=(
                "你是一个意图分类器。根据用户消息判断意图,只回复以下之一:\n"
                "- refund:用户要求退款、退货、退钱\n"
                "- query:用户查询订单、物流、商品信息\n"
                "- human:用户投诉、要求转人工、情绪激动\n"
                "只回复一个词,不要解释。"
            )
        ),
        HumanMessage(content=last_msg),
    ]
    intent = llm.invoke(prompt).content.strip().lower()
    # 确保意图在合法范围内
    if intent not in ("refund", "query", "human"):
        intent = "human"
    return {"intent": intent}


def refund_node(state: ServiceState) -> dict:
    """退款处理节点"""
    last_msg = state["messages"][-1].content
    prompt = [
        SystemMessage(
            content=(
                "你是退款客服。根据用户描述,给出退款指引。格式:\n"
                "1. 确认退款原因\n"
                "2. 告知退款流程\n"
                "3. 预计到账时间\n"
                "语气友好、专业。"
            )
        ),
        HumanMessage(content=last_msg),
    ]
    response = llm.invoke(prompt).content
    return {"response": response, "messages": [AIMessage(content=response)]}


def query_node(state: ServiceState) -> dict:
    """查询处理节点"""
    last_msg = state["messages"][-1].content
    prompt = [
        SystemMessage(
            content=(
                "你是查询客服。回答用户关于订单、物流、商品的问题。"
                "如果无法确认具体信息,建议用户提供订单号。语气友好。"
            )
        ),
        HumanMessage(content=last_msg),
    ]
    response = llm.invoke(prompt).content
    return {"response": response, "messages": [AIMessage(content=response)]}


def human_node(state: ServiceState) -> dict:
    """转人工节点"""
    last_msg = state["messages"][-1].content
    response = (
        "我理解您的需求,已为您转接人工客服。\n"
        "当前排队人数:3人,预计等待时间:2分钟。\n"
        "请稍候,人工客服将很快为您服务。"
    )
    return {"response": response, "messages": [AIMessage(content=response)]}


# ── 3️⃣ 路由函数 ────────────────────────────────
def route_by_intent(state: ServiceState) -> str:
    """根据意图分类结果路由"""
    return state["intent"]


# ── 4️⃣ 构建图 ─────────────────────────────────
graph = StateGraph(ServiceState)

graph.add_node("classify", classify_node)
graph.add_node("refund", refund_node)
graph.add_node("query", query_node)
graph.add_node("human", human_node)

graph.add_edge(START, "classify")
graph.add_conditional_edges(
    "classify",
    route_by_intent,
    {"refund": "refund", "query": "query", "human": "human"},
)
graph.add_edge("refund", END)
graph.add_edge("query", END)
graph.add_edge("human", END)

# ── 5️⃣ 编译运行 ───────────────────────────────
app = graph.compile()

# 测试:退款
result = app.invoke(
    {"messages": [HumanMessage(content="我要退款!昨天买的东西还没到,不想要了")]}
)
print("【退款场景】", result["response"])

# 测试:查询
result = app.invoke(
    {"messages": [HumanMessage(content="我的订单到哪了?订单号 ORD-20260220-001")]}
)
print("【查询场景】", result["response"])

# 测试:转人工
result = app.invoke(
    {"messages": [HumanMessage(content="你们这是什么服务!我要投诉!给我转人工!")]}
)
print("【转人工场景】", result["response"])

五、概念速查

概念

类比

代码

说明

State

共享数据库表

TypedDict / MessagesState

所有节点共享的数据

Node

函数/处理步骤

def node(state) -> dict

接收 state,返回更新

Edge

跳转逻辑

add_edge / add_conditional_edges

固定跳转 or 条件路由

Reducer

合并策略

Annotated[list, add_messages]

多节点更新同一字段时怎么合并

Checkpoint

游戏存档点

MemorySaver / SqliteSaver

保存图执行到某一步的状态

Thread

存档槽

thread_id="session-1"

区分不同会话的存档

Compile

编译打包

graph.compile()

类型检查+连通性验证,才能运行

ToolNode

工具执行器

ToolNode(tools)

自动执行 LLM 选择的工具

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区