大模型之LangGraph技术体系

LangGraph 工程化实战:图状态机驱动的复杂 AI 工作流

适用版本:LangGraph 1.0+(兼容 0.3.x 核心 API)

技术栈:Python 3.10+ / LangGraph / LangChain / SQLite / Redis

定位:聚焦图结构工作流设计与生产级实践


1. LangGraph 架构全景

1.1 核心定义

LangGraph = LangChain + 图编排 + 状态机

基于 LangChain 构建的、面向智能体多轮交互 / 状态持久化 / 分支并行执行的图结构工作流框架

1.2 为什么需要 LangGraph

LangChain 生态的两个困境:

组件 痛点
Chain 线性流水线,无法优雅处理循环和条件分支。"返回上一步"或"条件跳转"需要大量胶水代码
Agent 黑箱式决策,难以精细化控制。相同问题可能走不同路径,行为不稳定,调试困难,Token 成本不可控

LangGraph 的解决方案 :将工作流抽象为有向图,节点定义操作步骤,边定义控制流,精确控制执行逻辑。

1.3 核心能力

  • 条件分支、循环、并行执行
  • 状态持久化、断点续跑
  • 时间回溯(Time-Travel)
  • 人机协作(Human-in-the-Loop)
  • 多智能体协作、层级架构
  • 节点缓存与错误重试

1.4 与 LangChain 的关系

LangGraph 是 LangChain 工作流的高级编排层。无论图结构多复杂,单条执行链路仍是线性的,底层仍然依赖 LangChain 的 Chain 来实现。两者关系:

复制代码
LangChain  → 基础能力层(模型调用、Prompt、Tool、Memory)
LangGraph  → 流程编排层(图结构、状态机、多智能体协调)

2. 核心四要素

LangGraph 的灵魂:State(状态)+ Nodes(节点)+ Edges(边)+ Graph(图)

2.1 最小可运行示例

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

# 1. 定义 State
class HelloState(TypedDict):
    name: str
    greeting: str

# 2. 定义 Nodes
def greet(state: HelloState) -> dict:
    return {"greeting": f"你好, {state['name']}"}

def add_emoji(state: HelloState) -> dict:
    return {"greeting": state["greeting"] + " 🎉"}

# 3. 构建 Graph
graph = StateGraph(HelloState)
graph.add_node("greeting", greet)
graph.add_node("add_emoji", add_emoji)
graph.add_edge(START, "greeting")
graph.add_edge("greeting", "add_emoji")
graph.add_edge("add_emoji", END)

# 4. 编译 & 运行
app = graph.compile()
result = app.invoke({"name": "z3"})
print(result["greeting"])  # "你好, z3 🎉"

2.2 图的构建流程

scss 复制代码
1. 初始化 StateGraph 实例(指定 State Schema)
2. add_node() 添加节点
3. add_edge() / add_conditional_edges() 定义边
4. 设置入口和出口(START / END)
5. compile() 编译图(校验边与节点的完整性)
6. invoke() / stream() 执行工作流

2.3 图可视化

scss 复制代码
# ASCII 可视化(控制台直接查看)
print(app.get_graph().print_ascii())

# Mermaid 代码(可粘贴到 processon.com/mermaid 编辑器查看)
print(app.get_graph().draw_mermaid())

# 生成 PNG 文件(依赖网络,稳定性一般)
png_bytes = app.get_graph().draw_mermaid_png(max_retries=3, retry_delay=2.0)
with open("graph.png", "wb") as f:
    f.write(png_bytes)

3. Graph API 深度解析

3.1 State(状态)

State 是贯穿整个工作流的共享数据结构,存储从开始到结束的所有信息(历史对话、工具结果、文档等),各节点均可读写。

3.1.1 Schema 定义
要素 说明
state_schema 图的完整内部状态,所有节点可读写的字段,必须指定
input_schema 图接受的输入,state_schema 的子集(可选,默认=state_schema)
output_schema 图返回的输出,state_schema 的子集(可选,默认=state_schema)

选型建议

  • TypedDict:轻量、无运行时开销,适合字段简单的场景
  • pydantic.BaseModel:自动校验、默认值、嵌套结构、字段描述,适合复杂场景
python 复制代码
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict

# 输入/输出分离设计
class InputState(TypedDict):
    question: str

class OutputState(TypedDict):
    answer: str

class OverallState(InputState, OutputState):
    internal_data: str  # 私有中间状态

builder = StateGraph(OverallState,
    input_schema=InputState,
    output_schema=OutputState)
3.1.2 Reducer(状态合并策略)

Reducer 决定节点返回的增量如何合并到全局 State。每个字段可独立配置 Reducer。

Reducer 行为 适用场景
默认(无) 直接覆盖旧值 普通字段更新
add_messages 追加消息到列表(消息专用) 多轮对话历史
operator.add 列表追加 / 字符串拼接 / 数值累加 收集多路结果
operator.mul 数值相乘(注意初始值陷阱) 概率计算
自定义函数 完全自定义合并逻辑 复杂业务规则
python 复制代码
from typing import Annotated, List
from langgraph.graph.message import add_messages
import operator

class ChatState(TypedDict):
    # 消息历史:追加而非覆盖
    messages: Annotated[list, add_messages]
    # 标签列表:追加合并
    tags: Annotated[List[str], operator.add]
    # 累计分数
    score: Annotated[float, operator.add]

关键陷阱:operator.mul 初始值问题

LangGraph 在执行第一个节点前会调用一次 Reducer,用类型默认值(float0.0)与 invoke 传入值运算。对于乘法:0.0 * 5.0 = 0.0,导致结果始终为 0。

解决方案:自定义 Reducer

sql 复制代码
def safe_mul(current: float, update: float) -> float:
    if current == 0.0:  # 首次调用,用乘法恒等元 1.0
        return 1.0 * update
    return current * update

class MultiplyState(TypedDict):
    factor: Annotated[float, safe_mul]

3.2 Node(节点)

Node 是 LangGraph 中的基本处理单元,绑定一个 Python 函数,可以是 Agent、LLM 调用、工具执行或任意逻辑。

3.2.1 设计原则
  • 单一职责:每个节点只负责一项任务
  • 无状态设计:节点不保存状态,所有数据通过 State 传递
  • 幂等性:相同输入产生相同输出,确保可重试
  • 可测试性:节点逻辑应易于单元测试
3.2.2 节点缓存

基于节点输入做缓存,跳过重复计算:

python 复制代码
from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy

# 编译时指定缓存策略
app = builder.compile(cache=InMemoryCache())

# 添加节点时指定缓存策略(ttl 单位:秒)
builder.add_node(
    "expensive_node", expensive_func,
    cache_policy=CachePolicy(ttl=60)  # 60秒内相同输入直接返回缓存
)
3.2.3 错误重试
python 复制代码
from langgraph.types import RetryPolicy

# 默认重试策略:max_attempts=5,对 Exception 重试,
# 不对 ValueError/TypeError/ImportError 等重试
builder.add_node("api_node", api_func,
    retry_policy=RetryPolicy(max_attempts=5))

# 自定义重试条件
def should_retry(exc: Exception) -> bool:
    return "API 调用失败" in str(exc)

builder.add_node("api_node", api_func,
    retry_policy=RetryPolicy(max_attempts=5, retry_on=should_retry))

3.3 Edge(边)

Edge 定义节点之间的连接和执行顺序。

3.3.1 普通边(Normal Edge)

无条件从一个节点跳转到另一个节点:

sql 复制代码
builder.add_edge(START, "node_a")
builder.add_edge("node_a", "node_b")
builder.add_edge("node_b", END)
3.3.2 条件边(Conditional Edge)

根据路由函数的返回值动态决定下一个节点:

python 复制代码
def route_by_type(state: MyState) -> str:
    if state["x"] == 1: return "condition_1"
    elif state["x"] == 2: return "condition_2"
    else: return "condition_3"

builder.add_conditional_edges(
    "router_node",    # 源节点
    route_by_type,    # 路由函数
    {
        "condition_1": "node_1",
        "condition_2": "node_2",
        "condition_3": "node_3"
    }
)
3.3.3 入口点 & 条件入口点
sql 复制代码
# 固定入口(语法糖,等价于 add_edge(START, "node_a"))
builder.set_entry_point("node_a")
builder.set_finish_point("node_b")

# 条件入口:根据输入动态选择起始节点
builder.add_conditional_edges(
    START,
    route_function,
    {"greeting": "greeting_node", "question": "question_node"}
)

4. 高级控制机制

4.1 Send:Map-Reduce 并行

场景:动态创建多个执行分支,并行处理,最终汇总结果。

python 复制代码
from langgraph.types import Send
from typing import Sequence

# 条件边函数:为每个主题创建一个并行任务
def map_tasks(state: OverallState) -> Sequence[Send]:
    return [
        Send("process_node", {"subject": subject})
        for subject in state["subjects"]
    ]

# 注册条件边
builder.add_conditional_edges("generator", map_tasks)
builder.add_edge("process_node", END)  # 所有并行任务完成后结束

Send 参数:第一个是目标节点名,第二个是传递给该节点的 State。

4.2 Command:状态更新 + 路由一体

在同一个节点中既更新状态,又决定下一步路由,常用于多智能体交接(Handoffs)和人机协作(HITL):

ini 复制代码
from langgraph.types import Command

def decision_agent(state: AgentState) -> Command[AgentState]:
    if state["task_completed"]:
        return Command(
            update={"messages": [("system", "任务完成")]},
            goto=END
        )
    if "数学" in last_message:
        return Command(
            update={"current_agent": "math_agent"},
            goto="math_agent"
        )
    return Command(
        update={"task_completed": True},
        goto=END
    )

Command vs 条件边

维度 条件边 Command
路由 只路由下一个节点 路由 + 状态更新
适用 纯流程控制 需要同时更新状态和路由
场景 简单分支 多智能体交接、HITL

4.3 Runtime Context:运行时上下文

不属于图状态的配置信息(模型名称、数据库连接、API Key 等)注入节点,实现配置与代码分离:

python 复制代码
from dataclasses import dataclass
from langgraph.runtime import Runtime

@dataclass
class ContextSchema:
    model_name: str
    db_connection: str
    api_key: str

# 节点函数通过 runtime 参数访问上下文
def process_node(state: State, runtime: Runtime[ContextSchema]) -> dict:
    model = runtime.context.model_name
    db = runtime.context.db_connection
    return {"response": f"使用 {model} 处理完成"}

# 创建图时指定 context_schema
builder = StateGraph(State, context_schema=ContextSchema)

# 执行时传入 context
context = ContextSchema(model_name="gpt-4", db_connection="...", api_key="sk-xxx")
result = graph.invoke(initial_state, context=context)

5. 流式处理 Streaming

LangGraph 的流式传输比 LangChain 更丰富------不仅能看 LLM 逐字输出,还能实时看到流程状态

5.1 流模式一览

stream_mode 输出内容 场景
values 每步执行后的完整状态 监控全局进度
updates 每步执行后的增量更新 只看变化
messages LLM 逐 token 输出 + 元数据 打字机效果
custom 自定义数据(进度提示等) 业务级进度推送
debug 所有细节(调试用) 开发调试

5.2 核心用法

ini 复制代码
# 单模式
for chunk in graph.stream(input_state, stream_mode="updates"):
    print(chunk)

# 多模式并存
for mode, chunk in graph.stream(input_state,
    stream_mode=["values", "updates"]):
    print(f"[{mode}]: {chunk}")

# LLM token 流式输出
for chunk, metadata in graph.stream(input_state, stream_mode="messages"):
    print(chunk.content, end="")  # metadata 包含节点名和 LLM 调用详情

5.3 自定义数据流

在节点内部通过 get_stream_writer 发送自定义数据:

arduino 复制代码
from langgraph.config import get_stream_writer

def my_node(state: State):
    writer = get_stream_writer()
    writer({"progress": "步骤 1: 分析查询", "status": "running"})
    # ... 业务处理 ...
    writer({"progress": "步骤 2: 生成结果", "status": "completed"})
    return {"answer": result}

# 消费自定义流
for chunk in graph.stream(inputs, stream_mode="custom"):
    print(chunk)  # {"progress": "...", "status": "..."}

6. 状态持久化 Persistence

6.1 核心概念

每个工作流步骤执行后,系统将完整状态保存为检查点(Checkpoint) ,通过 thread_id(会话ID,非操作系统线程)区分不同会话。

两种记忆机制

类型 载体 作用 生命周期
短期记忆 Checkpointer(MemorySaver / RedisSaver / PostgresSaver) 保存图的运行状态,按 thread_id 续写 同一会话内
长期记忆 BaseStore(InMemoryStore / RedisStore / PostgresStore) 保存用户偏好、背景事实,支持向量检索 跨会话

6.2 内存版(开发测试)

ini 复制代码
from langgraph.checkpoint.memory import InMemorySaver

graph = builder.compile(checkpointer=InMemorySaver())

config = {"configurable": {"thread_id": "user-001"}}
result = graph.invoke({"messages": ["开始"]}, config)

# 查看保存的状态
saved_state = graph.get_state(config)
print(saved_state.values)
print(saved_state.next)  # 下一个待执行节点

# 获取完整执行历史
for checkpoint in graph.get_state_history(config):
    print(checkpoint.values)

6.3 SQLite 版(本地生产)

复制代码
pip install langgraph-checkpoint-sqlite
ini 复制代码
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver

conn = sqlite3.connect("checkpoints.db", check_same_thread=False)
graph = builder.compile(checkpointer=SqliteSaver(conn=conn))

config = {"configurable": {"thread_id": "user-001"}}
result = graph.invoke({"messages": []}, config)

6.4 持久化后端选型

后端 包名 适用场景
InMemorySaver langgraph(内置) 本地测试
SqliteSaver langgraph-checkpoint-sqlite 单机生产
PostgresSaver langgraph-checkpoint-postgres 分布式生产
RedisSaver langgraph-checkpoint-redis 高性能需求

6.5 预构建 Agent + 记忆

arduino 复制代码
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(
    model=llm,
    checkpointer=InMemorySaver()  # 生产换 RedisSaver/PostgresSaver
)

config = {"configurable": {"thread_id": "user-001"}}
agent.invoke({"messages": [("user", "我叫张三")]}, config)
agent.invoke({"messages": [("user", "我叫什么?")]}, config)  # 能记住

7. 时间回溯 Time-Travel

7.1 核心价值

在非确定性系统中,回溯、检查、修改历史状态并从某个历史节点重新执行:

  • 调试:查看 agent 在某个历史状态下的决策
  • 修复:发现错误后回到该步骤,走另一条路径
  • 探索:从同一历史点分叉出多个可能结果(what-if 实验)
  • 人类反馈(HITL) :用户拒绝工具调用后,退回到之前状态重新走

7.2 实现步骤

ini 复制代码
# 1. 运行图,生成检查点
config = {"configurable": {"thread_id": str(uuid.uuid4())}}
story = graph.invoke({}, config)

# 2. 查看历史状态,找到目标检查点
history = list(graph.get_state_history(config))
target_checkpoint = history[2]  # 选择特定历史节点

# 3.(可选)修改状态,创建分支
new_config = graph.update_state(
    target_checkpoint.config,
    values={"character": "新角色"}  # 修改历史状态
)

# 4. 从检查点恢复执行(产生新分支)
new_story = graph.invoke(None, new_config)

8. 子图 Subgraphs

8.1 核心价值

将一个完整的图作为另一个图的节点,适合:

  • 复杂任务拆解为多个专业智能体
  • 各子图独立开发、测试、复用
  • 子图可拥有私有数据,也可与父图共享数据

8.2 方式一:直接添加子图为节点(共享 State 字段)

makefile 复制代码
# 子图
sub_builder = StateGraph(SubState)
sub_builder.add_node("sub_node", sub_func)
sub_builder.add_edge(START, "sub_node")
sub_builder.add_edge("sub_node", END)
subgraph = sub_builder.compile()

# 父图:直接将子图添加为一个节点
parent_builder = StateGraph(ParentState)
parent_builder.add_node("subgraph_node", subgraph)  # 子图作为节点
parent_builder.add_edge(START, "subgraph_node")
parent_builder.add_edge("subgraph_node", END)

状态合并行为 :子图执行时会触发两次 Reducer 合并(父→子、子→父),使用 operator.add 类 Reducer 时需注意重复数据。

8.3 方式二:代理节点模式(父子图 State 不同时,推荐)

当父子图状态结构完全不同时,通过代理节点手动转换:

python 复制代码
# 父图状态
class ParentState(TypedDict):
    user_query: str
    final_answer: str | None

# 子图状态(完全不同的字段)
class SubgraphState(TypedDict):
    analysis_input: str
    analysis_result: str

# 代理节点:手动完成 父→子转换 + 调用子图 + 子→父映射
def call_subgraph_proxy(state: ParentState) -> ParentState:
    # 步骤1:父状态 → 子输入
    sub_input = {"analysis_input": state["user_query"], "analysis_result": ""}
    # 步骤2:手动调用子图
    sub_result = compiled_subgraph.invoke(sub_input)
    # 步骤3:子输出 → 父状态
    return {"final_answer": sub_result["analysis_result"]}

# 父图添加代理节点(而非直接添加子图)
parent_builder.add_node("proxy", call_subgraph_proxy)

9. 多智能体架构 A2A

9.1 MCP vs A2A:两个维度的协议

协议 本质 类比
MCP 工具访问协议:LLM 如何调用工具/数据 工具车间------让工人知道每个工具的位置和用法
A2A 代理协作协议:智能体之间如何发现、交流、合作 会议室------让不同专家坐在一起协调任务

两者互补,共同构成现代 AI 系统的通信基础设施。

9.2 多智能体连接方式

架构 结构 适用场景
Network 去中心化,多 Agent 平等通信 多视角协作、头脑风暴、并行搜索
Supervisor 中央主管 Agent 调度子 Agent 企业助手(IT/HR/财务多领域)、智能客服
Supervisor as Tools LLM 将子 Agent 作为工具调用 单一核心 + 插件系统
Hierarchical 多层监督者,顶层分配给子 Supervisor 大型项目管理、复杂管道任务
Custom 自由组合上述模式 高度定制的企业级应用

9.3 Supervisor 实战(v1.0)

复制代码
pip install langgraph-supervisor
python 复制代码
import os
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langgraph_supervisor import create_supervisor

# 1. 初始化模型
def init_llm():
    return ChatOpenAI(
        model="qwen-plus",
        api_key=os.getenv("QWEN_API_KEY"),
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
        temperature=0.1
    )

# 2. 定义业务工具
def book_flight(from_airport: str, to_airport: str) -> str:
    """预订航班工具"""
    return f"成功预订了从 {from_airport} 到 {to_airport} 的航班"

def book_hotel(hotel_name: str) -> str:
    """预订酒店工具"""
    return f"成功预订了 {hotel_name} 的住宿"

# 3. 创建子 Agent
flight_agent = create_agent(
    model=init_llm(), tools=[book_flight], name="flight_assistant")
hotel_agent = create_agent(
    model=init_llm(), tools=[book_hotel], name="hotel_assistant")

# 4. 创建 Supervisor 协调者
supervisor = create_supervisor(
    agents=[flight_agent, hotel_agent],
    model=init_llm(),
    prompt=(
        "你是旅行预订系统的调度主管。\n"
        "1. 分析用户需求,确定需要航班/酒店/两者\n"
        "2. 分别调用对应助手\n"
        "3. 汇总结果后向用户报告"
    )
).compile()

# 5. 流式执行
for chunk in supervisor.stream({
    "messages": [{"role": "user", "content": "帮我预订北京到深圳的机票和如家酒店"}]
}):
    print(chunk)

9.4 Handoffs(交接)实战

Agent 之间通过 Command + Send 实现控制权交接:

ini 复制代码
from langgraph.types import Command, Send
from langgraph.prebuilt.tool_node import InjectedState

# 通用 Handoff 工具工厂
def create_handoff_tool(*, agent_name: str, description: str):
    @tool(f"transfer_to_{agent_name}", description=description)
    def handoff_tool(
        task_description: Annotated[str, "描述下一个 Agent 该做什么"],
        state: Annotated[MessagesState, InjectedState],
    ) -> Command:
        return Command(
            goto=[Send(agent_name, {**state, "messages": [{"role": "user", "content": task_description}]}]),
            graph=Command.PARENT,
        )
    return handoff_tool

# 各 Agent 持有对方的 handoff 工具
flight_agent = create_agent(
    model=model,
    tools=[book_flight, create_handoff_tool(agent_name="hotel_assistant", description="移交给酒店助手")],
    name="flight_assistant"
)
hotel_agent = create_agent(
    model=model,
    tools=[book_hotel, create_handoff_tool(agent_name="flight_assistant", description="移交给航班助手")],
    name="hotel_assistant"
)

# 构建多 Agent 图
multi_agent_graph = (
    StateGraph(MessagesState)
    .add_node(flight_agent)
    .add_node(hotel_agent)
    .add_edge(START, "flight_assistant")
    .compile()
)

附录:API 速查表

功能 API
添加节点 graph.add_node("name", func, cache_policy=..., retry_policy=...)
普通边 graph.add_edge("from_node", "to_node")
条件边 graph.add_conditional_edges("from_node", route_func, mapping_dict)
入口/出口 graph.set_entry_point("node") / graph.set_finish_point("node")
编译 graph.compile(checkpointer=..., cache=..., context_schema=...)
执行 graph.invoke(state, config, context=...)
流式 graph.stream(state, stream_mode="updates/values/messages/custom/debug")
查看状态 graph.get_state(config)
历史 graph.get_state_history(config)
更新状态 graph.update_state(config, values={...})

作者正在寻找 AI 工程方向的机会,欢迎交流。

相关推荐
不好听6131 小时前
Tool:让大模型长出手脚
llm·agent
Lei活在当下12 小时前
【AI手记系列-2026/6/18】iSparto & Harness,Caveman 以及AI时代的生存指南
人工智能·llm·openai
冬奇Lab14 小时前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm
hboot14 小时前
AI工程师第二课 - 数据处理
人工智能·python·数据分析
用户83562907805119 小时前
使用 Python 自动化 PowerPoint 形状布局与格式设置
后端·python
用户83562907805120 小时前
用 Python 自动化 PowerPoint 演讲者备注添加
后端·python
得物技术1 天前
从埋点需求到规则资产:Hermes Agent 重构得物数仓工作流
大数据·llm·ai编程
黄忠1 天前
01-系统架构设计-LangGraph状态机与多源异构RAG
python
柒和远方1 天前
LangGraph 深度解析:从增强型 LLM 到生产级 Agent
langchain·llm·agent