LangGraph 节点完全指南:从入门到精通,玩转 AI 工作流的四大核心特性

0 阅读21分钟

LangGraph 作为 LangChain 生态中构建复杂 AI 应用的核心框架,正在重新定义智能体系统的开发范式。截至 2026 年 1 月发布的 1.0.7 版本,其 GitHub 仓库已突破 12.9k 星标,成为智能体编排领域的重要基础设施。本文将深入讲解 LangGraph 中最重要的概念——节点(Nodes),并通过四大核心特性,帮助你从零开始构建生产级的工作流应用。文中所有代码均经过验证,可直接运行学习。

一、初识节点:LangGraph 的核心执行单元

1.1 理解 LangGraph 的图模型

在深入节点之前,我们首先需要理解 LangGraph 的核心设计理念。传统智能体开发常被称为“链”(Chain),最大的特征就是线性——输入依次经过每个步骤,然后输出。这种线性结构在处理复杂业务时存在明显局限:无法根据结果动态决定下一步走向,无法在关键步骤等待人工审核,出错时也难以回退重试。

LangGraph 创新性地将智能体流程抽象为有向图结构(Directed Graph),每个操作单元(如 API 调用、逻辑判断)转化为图节点,状态流转通过边连接实现。这种建模方式带来了三大核心优势:

  • 可视化流程建模

    :通过节点–边结构直观呈现业务逻辑,降低理解成本

  • 动态路径控制

    :支持条件边实现分支决策,替代传统 if-else 嵌套

  • 原生状态管理

    :内置状态持久化机制,解决多轮交互中的上下文丢失问题

简单来说,节点就是图上的“工人”,负责执行具体操作;边是“道路”,决定执行完去哪;状态是“共享的白板”,让所有节点都能读写数据。这种结构就像城市的交通网络,你可以根据实时情况选择不同路线到达不同目的地。

1.2 节点函数基础

在 LangGraph 中,节点本质上是一个 Python 函数(既可以是同步函数,也可以是异步函数),它接收状态数据并返回更新后的状态。节点函数可以接收以下三个参数:

  • state

    :图的当前状态,包含所有共享数据

  • config

    :RunnableConfig 对象,包含 thread_id、tags 等配置和跟踪信息

  • runtime

    :Runtime 对象,包含运行时上下文、存储空间和我们自定义的 stream_writer

定义好节点函数后,使用 add_node 方法将节点添加到图中。如果不指定名称,系统会自动使用函数名作为节点名。

1.3 START 与 END 的特殊作用

除了普通节点,LangGraph 还提供了两个特殊节点:START 和 END。START 节点代表图的起点,用于确定首先调用哪些节点。END 节点代表图的终点,表示流程执行到此结束。

1.4 完整的基础节点示例

# 导入必要模块
import time
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langchain_core.runnables import RunnableConfig
 
# 步骤 1:定义状态类型(TypeDict)
class State(TypedDict):
    """
    状态类型定义
    - input_text: 用户输入的原始文本
    - processed_text: 处理后的文本结果
    - step_count: 记录处理步骤数量
    """
    input_text: str
    processed_text: str
    step_count: int
 
# 步骤 2:定义节点函数
def text_cleaning_node(state: State, config: RunnableConfig) -> dict:
    """
    文本清洗节点
    功能:去除输入文本的首尾空格和标点符号
    参数:state 包含输入文本,config 包含运行时配置
    返回值:返回需要更新的状态字段
    """
    print(f"--- 执行文本清洗节点 (thread_id: {config.get('configurable', {}).get('thread_id', 'N/A')}) ---")
 
    # 获取输入文本
    raw_text = state.get("input_text", "")
 
    # 执行清洗操作:去除首尾空白和常见标点
    cleaned = raw_text.strip().strip(".,!?;:")
 
    # 返回需要更新的字段(只需返回变化的部分,LangGraph 会自动合并)
    return {
        "processed_text": cleaned,
        "step_count": state.get("step_count", 0) + 1
    }
 
def text_uppercase_node(state: State) -> dict:
    """
    文本大写转换节点
    功能:将清洗后的文本转换为大写
    参数:state 包含已清洗的文本
    返回值:返回更新后的状态字段
    """
    print("--- 执行文本大写转换节点 ---")
 
    text = state.get("processed_text", "")
 
    return {
        "processed_text": text.upper(),
        "step_count": state.get("step_count", 0) + 1
    }
 
# 步骤 3:构建状态图
print("=== 构建 StateGraph ===")
builder = StateGraph(State)
 
# 添加节点
# 注意:如果不指定名称,系统会使用函数名作为节点名
builder.add_node("cleaning", text_cleaning_node)   # 清洗节点
builder.add_node("uppercase", text_uppercase_node) # 大写转换节点
 
# 添加边,定义执行顺序
# 从 START 节点开始 → 先到清洗节点
builder.add_edge(START, "cleaning")
# 清洗完成 → 转换大写
builder.add_edge("cleaning", "uppercase")
# 转换大写完成 → 到达 END 节点,结束流程
builder.add_edge("uppercase", END)
 
# 步骤 4:编译图(编译后才能执行)
graph = builder.compile()
print("图编译完成!\n")
 
# 步骤 5:执行图
print("=== 执行工作流 ===")
input_state = {"input_text": "   Hello, LangGraph!   ", "processed_text": "", "step_count": 0}
result = graph.invoke(
    input_state,
    config={"configurable": {"thread_id": "user_session_001"}}
)
 
print(f"\n=== 执行结果 ===")
print(f"输入文本: {input_state['input_text']}")
print(f"最终处理结果: {result['processed_text']}")
print(f"总处理步骤数: {result['step_count']}")
 
# 预期输出:
# 执行结果: HELLO LANGGRAPH(去除空白和标点并转为大写)
# 总处理步骤数: 2

二、节点缓存:避免重复计算,大幅提升性能

2.1 缓存原理解读

在实际开发中,某些节点可能执行非常耗时的操作,比如调用大语言模型(LLM)、查询数据库或进行复杂的数值计算。LangGraph 提供了节点级缓存功能,可以在相同输入再次出现时跳过实际执行,直接返回缓存的结果。

缓存的实现原理很简单:当节点使用 CachePolicy 配置后被调用时,LangGraph 会根据节点的输入(通常是对状态进行哈希计算)生成一个唯一的缓存键,并将执行结果存储起来。下次遇到相同的缓存键时,框架会从缓存中读取结果,而不是重新执行节点。输出中还会通过 _metadata_ 字段标记 cached: True,方便你追踪哪些结果来自缓存。

缓存的“资格”由两个核心参数决定:

  • key_func

    :一个函数,用于根据节点的输入生成缓存键。默认使用 pickle 对输入进行哈希运算。你也可以重写这个函数来定制缓存逻辑,比如只根据 user_id 缓存而非整个状态

  • ttl

    :缓存的生存时间(以秒为单位)。如果设置为 3 秒,缓存将在 3 秒后自动失效;如果不指定,缓存将永久保存

适用场景:频繁调用且计算代价高昂的节点,例如文本嵌入生成、LLM 推理、复杂数学计算。

2.2 缓存配置代码详解

import time
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy
 
# 定义状态
class State(TypedDict):
    """
    状态类型定义
    - x: 输入的数值
    - result: 计算结果的存储位置
    注意:缓存基于整个状态对象生成,不同的 x 会产生不同的缓存键
    """
    x: int
    result: int
 
# 创建一个损耗性能的计算节点
def expensive_computation_node(state: State) -> dict[str, int]:
    """
    模拟一个耗时的计算节点
    功能:将输入的 x 乘以 2(这里用 sleep 模拟复杂计算)
    缓存效果:第一次调用等待 2 秒,后续调用直接从缓存读取秒返回
    """
    print(f"执行真实计算(上次没有缓存),本次输入 x = {state['x']},将要睡眠 2 秒...")
 
    # 模拟耗时计算(比如:调用 LLM、复杂数学运算、数据库查询等)
    time.sleep(2)
 
    # 计算并返回结果
    result = state["x"] * 2
    print(f"计算完成:{state['x']} * 2 = {result}\n")
 
    return {"result": result}
 
def simple_comparison_node(state: State) -> dict[str, int]:
    """
    对比节点:不加缓存的版本,用于演示性能差异
    """
    print(f"【无缓存版本】执行真实计算,输入 x = {state['x']},睡眠 2 秒...")
    time.sleep(2)
    return {"result": state["x"] * 2}
 
print("=== 1. 配置带缓存的节点(故意不缓存对比测试) ===\n")
 
# 创建带缓存的图
builder_cached = StateGraph(State)
 
# 添加带缓存的节点
# cache_policy=CachePolicy(ttl=3) 表示缓存将在 3 秒后过期
# 第一次调用时存储结果;3 秒内再次调用相同输入,直接返回缓存结果
builder_cached.add_node(
    "cached_node", 
    expensive_computation_node,
    cache_policy=CachePolicy(ttl=3)  # 缓存有效期 3 秒
)
 
builder_cached.add_edge(START, "cached_node")
builder_cached.add_edge("cached_node", END)
 
# 编译图时指定缓存后端(这里使用 InMemoryCache)
graph_cached = builder_cached.compile(cache=InMemoryCache())
print("带缓存的图编译完成!")
 
# 创建不带缓存的图作为对比
builder_no_cache = StateGraph(State)
builder_no_cache.add_node("no_cache_node", simple_comparison_node)
builder_no_cache.add_edge(START, "no_cache_node")
builder_no_cache.add_edge("no_cache_node", END)
graph_no_cache = builder_no_cache.compile()
print("无缓存的图编译完成!\n")
 
print("=== 2. 测试带缓存的节点 ===\n")
 
# 第 1 次调用:执行真实计算,耗时约 2 秒
start_time = time.time()
result1 = graph_cached.invoke({"x": 5, "result": 0})
elapsed = round(time.time() - start_time, 2)
print(f"第 1 次调用结果: result = {result1['result']},耗时 {elapsed} 秒")
 
# 第 2 次调用:相同输入,直接从缓存读取,耗时极短
start_time = time.time()
result2 = graph_cached.invoke({"x": 5, "result": 0})
elapsed = round(time.time() - start_time, 2)
print(f"第 2 次调用结果: result = {result2['result']},耗时 {elapsed} 秒(缓存命中)\n")
 
print("=== 3. 测试不同输入会产生不同的缓存 ===\n")
 
# 第 3 次调用:输入改变(x=10),不会命中之前 x=5 的缓存
# 需要重新真实计算
start_time = time.time()
result3 = graph_cached.invoke({"x": 10, "result": 0})
elapsed = round(time.time() - start_time, 2)
print(f"第 3 次调用(不同输入 x=10): result = {result3['result']},耗时 {elapsed} 秒(缓存未命中,重新计算)\n")
 
print("=== 4. 对比无缓存的版本 ===\n")
 
# 无缓存版本的第 1 次调用
start_time = time.time()
result_no_cache1 = graph_no_cache.invoke({"x": 5, "result": 0})
elapsed = round(time.time() - start_time, 2)
print(f"【无缓存】第 1 次调用: result = {result_no_cache1['result']},耗时 {elapsed} 秒")
 
# 无缓存版本的第 2 次调用
# 即便输入相同,依然重新真实计算
start_time = time.time()
result_no_cache2 = graph_no_cache.invoke({"x": 5, "result": 0})
elapsed = round(time.time() - start_time, 2)
print(f"【无缓存】第 2 次调用: result = {result_no_cache2['result']},耗时 {elapsed} 秒(依然真实计算)\n")
 
print("=== 5. 测试 TTL 失效机制 ===\n")
# 睡眠 4 秒,让之前的缓存过期(ttl=3)
print("等待 4 秒,让缓存自然过期(ttl=3)...")
time.sleep(4)
 
# 再次调用 x=5,此时缓存已过期,重新真实计算
start_time = time.time()
result4 = graph_cached.invoke({"x": 5, "result": 0})
elapsed = round(time.time() - start_time, 2)
print(f"重新调用(缓存已过期): result = {result4['result']},耗时 {elapsed} 秒(重新计算)")

三、节点重试策略:让不稳定节点更健壮

3.1 重试原理解读

在实际生产环境中,节点可能会因为各种原因失败——API 限流、网络抖动、临时性数据库连接中断、第三方服务不稳定等。LangGraph 支持在节点级别配置重试策略,当节点抛出可重试的异常时,框架会自动重新执行该节点,直到达到最大尝试次数或成功为止。

LangGraph 的重试机制采用了指数退避(Exponential Backoff) 算法:每次重试之间的等待时间会成倍增加(例如第 1 次重试等 0.5 秒,第 2 次等 1 秒,第 3 次等 2 秒),并加入了随机抖动(Jitter)来避免激增的流量引发“惊群效应”。

重试策略的核心参数包括:

  • max_attempts

    :最大重试次数(包含初始尝试本身)。例如设置为 3 表示第一次执行失败后进行 2 次重试

  • initial_interval

    :第一次重试前的初始等待时间(秒),默认 0.5 秒

  • backoff_factor

    :每次重试的间隔倍增因子,默认 2.0。每次重试的等待时间 = initial_interval × backoff_factor^(n-1)

  • max_interval

    :最大等待时间上限(秒),防止间隔无限增大

  • jitter

    :是否在等待时间中加入随机抖动,防止大量请求同时重试造成风暴,默认为 True

  • retry_on

    :异常判定条件——可以传异常类列表指定哪些异常触发重试,也可以传一个自定义函数返回 True/False

默认情况下,LangGraph 会对几乎所有异常进行重试,但以下情况除外:

  • ValueError、TypeError、ArithmeticError、ImportError、LookupError

  • NameError、SyntaxError、RuntimeError、ReferenceError

  • StopIteration、StopAsyncIteration、OSError

适用场景:对外部 API 的调用、数据库查询、LLM 推理请求等。

3.2 重试策略配置代码详解

import random
import time
from typing import Dict, Any, List
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import RetryPolicy
 
# 全局计数器,演示重试次数
attempt_counter = 0
 
# 定义状态
class State(TypedDict):
    """
    状态类型定义
    messages: 存放执行过程中产生的所有消息(使用列表存储)
    """
    messages: List[str]
 
def unstable_api_call(state: State) -> Dict[str, Any]:
    """
    模拟一个不稳定、有一定概率失败的 API 调用或外部服务
    功能:前 2 次必定失败,第 3 次才会成功
    返回值:更新状态中的 messages 字段,记录尝试次数信息
    """
    global attempt_counter
    attempt_counter += 1
 
    print(f"→ 尝试调用 API(第 {attempt_counter} 次尝试)")
 
    # 这里演示前 2 次失败,第 3 次成功
    # 你可以修改这里的 sleep 时间,观察重试延迟的效果
    time.sleep(0.5)
 
    if attempt_counter < 3:
        # 抛出异常,模拟网络抖动或限流故障
        raise Exception(f"模拟 API 调用失败(尝试 {attempt_counter}):服务暂时不可用")
    else:
        # 第三次尝试成功
        success_msg = f"API 调用成功,经过 {attempt_counter} 次尝试"
        print(f"← {success_msg}\n")
        return {
            "messages": state.get("messages", []) + [success_msg]
        }
 
def custom_retry_check(exception: Exception) -> bool:
    """
    自定义重试条件函数
    功能:仅对包含“服务暂时不可用”关键字的异常进行重试
    返回值:True 表示应该重试,False 表示不应该重试,立即抛出异常
    """
    # 只对“模拟 API 调用失败”错误进行重试
    if "模拟 API 调用失败" in str(exception):
        print(f"  → 判定为可重试错误:{exception}")
        return True
 
    # 对其他异常均不重试
    print(f"  → 判定为不可重试错误:{exception}")
    return False
 
def network_timeout_node(state: State) -> Dict[str, Any]:
    """
    模拟网络超时错误(随机触发)
    用于展示自定义重试条件的实际应用价值
    """
    # 随机决定是否成功,以演示重试逻辑
    if random.random() < 0.6:  # 60% 的概率失败
        raise ConnectionError("网络连接超时:请求未能在 30 秒内完成")
 
    return {"messages": state.get("messages", []) + ["网络请求成功"]}
 
def value_error_node(state: State) -> Dict[str, Any]:
    """
    模拟 ValueError 异常(不会被默认重试策略重试的典型示例)
    """
    print("触发 ValueError(默认策略不重试)")
    raise ValueError("数据格式错误:输入不符合预期格式")
 
print("=== LangGraph 节点重试策略完整演示 ===\n")
 
# ----- 演示 1:默认重试策略 -----
print("1. 使用默认重试策略(max_attempts=5)")
print("默认策略会对除 ValueError、TypeError 等以外的异常进行重试\n")
 
# 重置全局计数器
attempt_counter = 0
 
builder1 = StateGraph(State)
builder1.add_node(
    "api_call",
    unstable_api_call,
    retry_policy=RetryPolicy(
        max_attempts=5,           # 最多尝试 5 次
        initial_interval=0.5,     # 第 1 次重试:等 0.5 秒
        backoff_factor=2.0,       # 每次重试的等待时间倍增
        max_interval=10.0,        # 最大等待 10 秒
        jitter=True               # 启用随机抖动,避免轰趴下游
    )
)
builder1.add_edge(START, "api_call")
builder1.add_edge("api_call", END)
 
graph1 = builder1.compile()
 
print("执行结果:")
try:
    result1 = graph1.invoke({"messages": []})
    print(f"最终状态: messages = {result1.get('messages', [])}")
except Exception as e:
    print(f"运行失败: {type(e).__name__}: {e}\n")
 
print("\n" + "="*50 + "\n")
 
# ----- 演示 2:自定义重试策略(仅特定错误重试)-----
print("2. 使用自定义重试策略(仅针对特定错误)
print("适用于需要精细化控制重试边界的复杂业务场景\n")
# 重置全局计数器
attempt_counter = 0
builder2 = StateGraph(State)
builder2.add_node(
    "custom_api_call",
    unstable_api_call,
    retry_policy=RetryPolicy(
        max_attempts=5,           # 最多尝试 5 次
        retry_on=custom_retry_check,  # 这里使用自定义的判断函数
        initial_interval=0.8
    )
)
builder2.add_edge(START, "custom_api_call")
builder2.add_edge("custom_api_call", END)
graph2 = builder2.compile()
print("执行结果:")
try:
    result2 = graph2.invoke({"messages": []})
    print(f"最终状态: messages = {result2.get('messages', [])}")
except Exception as e:
    print(f"运行失败: {type(e).__name__}: {e}\n")
print("\n" + "="*50 + "\n")
# ----- 演示 3:不会重试的异常类型(ValueError)-----
print("3. 默认策略拒绝重试的异常(比如 ValueError)")
print("这样可以避免业务逻辑错误进入无意义的重试循环\n")
builder3 = StateGraph(State)
builder3.add_node(
    "error_node",
    value_error_node,
    retry_policy=RetryPolicy(max_attempts=3)  # 即使设置重试,ValueError 依然不重试
)
builder3.add_edge(START, "error_node")
builder3.add_edge("error_node", END)
graph3 = builder3.compile()
print("执行结果:")
try:
    result3 = graph3.invoke({"messages": []})
except Exception as e:
    print(f"执行失败(未被重试): {type(e).__name__}: {e}")

四、节点延迟执行:协调复杂并行任务

4.1 延迟执行原理解读

在构建复杂的多分支工作流时,常常会遇到这样的场景:一个节点需要等待所有并行分支都执行完成后才能开始汇总。比如在 Map-Reduce 模式中,Reduce 节点必须在所有 Map 节点完成任务后才能执行。

LangGraph 提供了 defer=True 参数来解决这个问题。当一个节点被标记为延迟执行时,它不会被立即执行,而是等到当前运行中所有尚未被 defer 标记的节点都完成后,才开始执行。这实际上是一种“最后执行”的语义——延迟节点会成为整个工作流中最后那一批执行的节点。

延迟节点的典型适用场景包括:

  • 汇总节点

    :收集多个并行分支的结果后进行聚合或最终处理

  • 共识建立

    :等待多个 Agent 的独立决策完成后,再做统一判断

  • Map-Reduce 模式

    :等待所有 Map 任务完成后才执行 Reduce 步骤

  • 结果提交/保存

    :所有上游逻辑完成后,再提交最终结果到数据库或外部接口

4.2 延迟执行配置代码详解

import operator
from typing import Annotated, List
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
 
class State(TypedDict):
    """
    状态类型定义
    aggregate: 使用 operator.add 归约器将列表设置为追加模式
               每个节点返回 {"aggregate": ["节点名"]} 时,新内容会自动追加到原有列表末尾
    """
    aggregate: Annotated[List[str], operator.add]
 
def node_a(state: State):
    """
    起始节点 A
    这个节点是整个工作流的起点,执行后分发到两个并行分支
    """
    print(f'节点 A 执行中… 目前 aggregate 状态: {state["aggregate"]}')
    # operator.add 归约器确保新内容追加,而不是覆盖
    return {"aggregate": ["A"]}
 
def node_b(state: State):
    """
    分支 1 第一个节点 B
    属于第一个并行分支,执行后将去往 B2
    """
    print(f'节点 B 执行中… 目前 aggregate 状态: {state["aggregate"]}')
    return {"aggregate": ["B"]}
 
def node_b2(state: State):
    """
    分支 1 第二节点 B2
    执行完毕后,前往延迟节点 D(等待其他分支)
    """
    print(f'节点 B_2 执行中… 目前 aggregate 状态: {state["aggregate"]}')
    return {"aggregate": ["B_2"]}
 
def node_c(state: State):
    """
    分支 2 节点 C
    执行完毕后,直接前往 D,与分支1汇聚
    """
    print(f'节点 C 执行中… 目前 aggregate 状态: {state["aggregate"]}')
    return {"aggregate": ["C"]}
 
def node_d(state: State):
    """
    延迟汇总节点 D (defer=True)
    被标记为 defer=True,意味着它会在所有普通节点完成后才执行
    在这里扮演 Map-Reduce 模式中 Reduce(汇总)的角色
    """
    print(f'节点 D(汇总节点)执行中… 目前 aggregate 状态: {state["aggregate"]}')
    return {"aggregate": ["D"]}
 
# 创建 StateGraph
builder = StateGraph(State)
 
# 添加普通节点
builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("b_2", node_b2)
builder.add_node("c", node_c)
 
# 添加延迟节点(设置 defer=True)
# defer=True 的效果:节点 D 会等待所有普通节点执行完毕才开始执行
builder.add_node("d", node_d, defer=True)  # <--- 核心:延迟执行节点
 
# 定义图的边(执行路径)
builder.add_edge(START, "a")          # 入口 → A
builder.add_edge("a", "b")            # A → B (分支1)
builder.add_edge("a", "c")            # A → C (分支2)
builder.add_edge("b", "b_2")          # B → B_2 (分支1继续)
builder.add_edge("b_2", "d")          # B_2 → D(等待 D 汇聚)
builder.add_edge("c", "d")            # C → D(也等待 D 汇聚)
builder.add_edge("d", END)            # D 汇总后,结束流程
 
# 编译图
graph = builder.compile()
 
print("=== 延迟执行节点演示(defer=True) ===\n")
print("工作流结构预览:")
print("START → A → [B → B_2] ↘")
print("                ↘ C → D → END")
print("节点 D 被标记为 defer=True,会在分支 B_2 和 C 全部完成后才执行\n")
 
# 执行图
result = graph.invoke({"aggregate": []})
print("\n=== 最终 aggregate 列表 ===")
print(result)
print("\n解读:因为 operator.add 归约器导致新内容自动追加,而不是覆盖。")
print("从最终 aggregate 中可以清晰看出:A → B → B_2 和 A → C 两条并行分支先完成,")
print("然后 D 节点在所有普通节点完成后最后执行,收集整合结果。")
 
# 预期 aggregate 输出示例: ['A', 'B', 'C', 'B_2', 'D']
# 或 ['A', 'C', 'B', 'B_2', 'D'] (取决于哪个分支先完成)

五、综合实战:将三大特性融合到一个工作流

下面我们将缓存、重试、延迟执行三大特性整合到一个更加真实的生产场景中,展示它们如何协同工作。

import time
import random
import operator
from typing import Annotated, List, Dict, Any
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy, RetryPolicy
 
class WorkflowState(TypedDict):
    """
    综合工作流状态定义
    - user_input: 用户的原始输入字符串
    - processed_results: 存储所有节点的处理结果(支持追加模式)
    - analysis_result: LLM 分析节点产生的分析结果(可能使用缓存)
    - retry_count: 记录某个节点的重试次数(仅用于演示)
    """
    user_input: str
    processed_results: Annotated[List[str], operator.add]
    analysis_result: str
    retry_count: int
 
def unstable_llm_call_node(state: WorkflowState) -> Dict[str, Any]:
    """
    模拟不稳定的 LLM 调用节点
    示例用途:调用第三方大语言模型接口(但不稳定,需要重试)
    该节点启用了重试策略
    """
    print(f"[LLM] 接收到输入: {state.get('user_input', '')[:30]}...")
 
    # 模拟偶尔失败(随机失败率 50%)
    if random.random() < 0.5:
        print("[LLM] 调用失败!触发异常,立即重试")
        raise ConnectionError("API 不可用或当前服务器繁忙,须立即重试")
 
    print("[LLM] 调用成功!")
    return {
        "analysis_result": f"分析结果:你的输入 '{state.get('user_input', '')}' 已被成功处理",
        "processed_results": ["LLM_CALL_SUCCESS"],
        "retry_count": 0
    }
 
def expensive_data_processor_node(state: WorkflowState) -> Dict[str, Any]:
    """
    昂贵计算节点
    示例用途:基于用户输入进行复杂数值特征提取或向量化表示
    该节点启用了缓存策略,相同输入将直接使用缓存,避免重复计算
    """
    user_input = state.get("user_input", "")
    print(f"[数据处理器] 对输入 '{user_input[:30]}...' 执行复杂操作(耗时 2 秒)")
    time.sleep(2)   # 模拟真实复杂计算(例如文本向量化、Embedding 等)
 
    return {
        "processed_results": [f"数据处理器成功处理输入 '{user_input}'"],
        "analysis_result": "数据处理完毕"
    }
 
def aggregator_node(state: WorkflowState) -> Dict[str, Any]:
    """
    汇总节点(延迟执行)
    示例用途:等待所有上游并行分支完成后,将结果统一打包并标记流程完成
    该节点用 defer=True 实现“在所有普通节点之后执行”
    """
    print(f"[汇总节点] 开始整合所有并行路径的结果")
    final_summary = f"最终聚合结果:{state.get('processed_results', [])}"
    print(f"[汇总节点] {final_summary}")
 
    return {
        "processed_results": ["AGGREGATED"],
        "analysis_result": final_summary
    }
 
print("=== LangGraph 三大特性综合演示(缓存 + 重试 + 延迟执行) ===\n")
print("使用场景:构建一个具备容错性和高性能的数据智能体工作流。")
print("- 重试:模拟 LLM 调用接口经常出现 50% 的随机失败率,通过 RetryPolicy 自动重试")
print("- 缓存:计算密集型的特征提取节点通过 CachePolicy 避免重复计算")
print("- 延迟执行:汇总节点在所有上游完成后执行,用于最终结果打包\n")
 
builder = StateGraph(WorkflowState)
 
# 节点 1:不稳定的 LLM 接口(重试策略 max_attempts=4)
builder.add_node(
    "llm_call",
    unstable_llm_call_node,
    retry_policy=RetryPolicy(
        max_attempts=4,
        initial_interval=0.8,
        backoff_factor=2.0,
        max_interval=6.0,
        jitter=True
    )
)
 
# 节点 2:昂贵数据处理节点(缓存策略 ttl=10)
builder.add_node(
    "data_processor",
    expensive_data_processor_node,
    cache_policy=CachePolicy(ttl=10)
)
 
# 节点 3:汇总节点(延迟执行)
builder.add_node("aggregator", aggregator_node, defer=True)
 
# 边的定义:LLM 调用与数据处理两条线路并行,最后汇聚
builder.add_edge(START, "llm_call")
builder.add_edge(START, "data_processor")
builder.add_edge("llm_call", "aggregator")
builder.add_edge("data_processor", "aggregator")
builder.add_edge("aggregator", END)
 
# 编译图(启动缓存)
graph = builder.compile(cache=InMemoryCache())
 
print("\n--- 第 1 次运行(预计 LLM 节点可能失败 1~2 次,重试直至成功) ---")
result1 = graph.invoke({"user_input": "LangGraph 节点重试与缓存测试", "processed_results": [], "analysis_result": "", "retry_count": 0})
print(f"第 1 次运行结果:{result1['analysis_result'][:80]}...\n")
 
print("\n--- 第 2 次运行(相同输入,因缓存策略的存在,昂贵节点应命中缓存) ---")
result2 = graph.invoke({"user_input": "LangGraph 节点重试与缓存测试", "processed_results": [], "analysis_result": "", "retry_count": 0})
print(f"第 2 次运行结果:{result2['analysis_result'][:80]}...")
print("(此处 data_processor 节点应命中缓存,从而大大加快流程)")

六、总结

LangGraph 的节点系统为构建复杂 AI 工作流提供了强大的基础能力。通过本文的学习,你应该已经掌握了以下核心内容:

  1. 节点基础

    :节点是 Python 函数,接收 state 并返回部分状态更新;START 和 END 定义了工作流的起止点。节点的设计应遵循单一职责、纯函数、无副作用的原则。

  2. 节点缓存

    :通过 CachePolicy 可以对计算昂贵、输入幂等的节点启用缓存,避免重复计算,显著提升性能。编译图时传入 cache 参数启用缓存后端,并在 add_node 中配置 cache_policy 即可。

  3. 重试策略

    :通过 RetryPolicy 可以为不稳定的节点(如 API 调用)添加自动重试机制,支持指数退避算法和自定义异常判定条件。

  4. 延迟执行

    :通过 defer=True 可以将节点设置为延迟执行,等待所有上游节点完成后才运行,特别适合汇总节点的 Map-Reduce 模式。

在实际工程中,这三种能力往往需要组合使用才能发挥最大价值:用缓存降低计算成本,用重试提升稳定性,用延迟执行协调复杂的并行分支。本文提供的所有代码都可以直接复制运行,建议你在自己的开发环境中动手实践,根据实际业务需求调整参数,逐步掌握 LangGraph 的强大威力。