LangGraph1.0速通指南(二)—— LangGraph1.0 条件边、记忆、人在回路

前言

上篇内容《LangGraph1.0速通指南(一)------ 核心概念、点、边》中笔者介绍了LangGraph 的基本概念,以及如何通过定义节点与边来构建多种图结构。有了这些基础,大家已经能够搭建起一个可运行的智能体工作流。

在本文中笔者将进一步展开 LangGraph 更为灵活和强大的能力。你将学习到:

  • 条件边(Conditional Edges) :除条件函数外,如何让图结构根据运行时的状态动态选择下一步路径,实现分支与循环逻辑;
  • 记忆(Memory) :如何让 LangGraph 在多次调用中保留和更新状态,构建LangGraph持续运行的能力
  • 人在回路(Human-in-the-loop) :如何在关键节点引入人工确认或干预,使LangGraph图更加可控、可靠。

LangGraph1.0 与先前版本变动不大,预计花费三节左右的时间讲解,同时也要说明笔者的专栏《深入浅出LangChain&LangGraph AI Agent 智能体开发》适合所有对 LangChain 感兴趣的学习者,无论之前是否接触过 LangChain。该专栏基于笔者在实际项目中的深度使用经验,系统讲解了使用LangChain/LangGraph如何开发智能体,目前已更新 32 讲,并持续补充实战与拓展内容。欢迎感兴趣的同学关注笔者的掘金账号与专栏,也可关注笔者的同名微信公众号 大模型真好玩 ,每期分享涉及的代码均可在公众号私信: LangChain智能体开发免费获取。

一、使用Command对象:边定义的第二种方法

上篇内容《LangGraph1.0速通指南(一)------ 核心概念、点、边》中笔者介绍了通过 add_conditional_edges 配合条件路由函数来定义边的方法。该函数根据图的状态返回下一个节点的名称,从而实现动态路由。

python 复制代码
...
def conditional_edge(state: State) -> Literal['b', 'c', END]:
    select = state["nList"][-1]
    if select == "b":
        return 'b'
    elif select == 'c':
        return 'c'
    elif select == 'q':
        return END
    else:
        return END

...


builder.add_conditional_edges("a", conditional_edge)

这是一种将路由逻辑 (边)与节点执行逻辑 (点)分离的清晰模式。然而,LangGraph 提供了另一种更为直接的控制流方式:允许节点函数在返回时,不仅更新状态,还能显式指定接下来要执行的节点 。这是通过返回一个特殊的 langgraph.types.Command 对象来实现的。

1.1 Command 对象:将路由逻辑内置于节点

Command 对象主要有两个关键指令:

  • update: 用于更新图的状态。
  • goto: 用于指定下一个要执行的节点。

这意味着可以在节点的处理函数内部,直接决定工作流的下一步走向,无需提前在图中定义好所有条件边。对于下图中的节点A,笔者希望在执行其逻辑后,根据当前状态选择跳转节点B或节点C:

这就需要重写node_a函数

python 复制代码
def node_a(state: State) -> Command[Literal['b','c', END]]:
    select = state['nList'][-1]
    if select == 'b':
        next_node = 'b'
    elif select == 'c':
        next_node = 'c'
    elif select == 'q':
        next_node = END
    else:
        next_node = END

    return Command(
        update=State(nList=[select]),
        goto=next_node
    )

1.2 完整示例与执行过程分析

  1. 将这种思路应用到上篇文章的条件图结构中,完整的代码如下:

    python 复制代码
    from langgraph.graph import START, END, StateGraph
    import operator
    from typing import TypedDict, List, Annotated, Literal
    
    from langgraph.types import Command
    
    
    class State(TypedDict):
        nList: Annotated[List[str], operator.add]
    
    def node_a(state: State) -> Command[Literal['b','c', END]]:
        select = state['nList'][-1]
        if select == 'b':
            next_node = 'b'
        elif select == 'c':
            next_node = 'c'
        elif select == 'q':
            next_node = END
        else:
            next_node = END
    
        return Command(
            update=State(nList=[select]),
            goto=next_node
        )
    
    def node_b(state: State):
        return Command(
            goto=END
        )
    
    def node_c(state: State):
        return Command(
            goto=END
        )
    
    
    
    builder = StateGraph(State)
    builder.add_node("a", node_a)
    builder.add_node("b", node_b)
    builder.add_node("c", node_c)
    
    builder.add_edge(START, "a")
    
    graph = builder.compile()
    
    user = input('b, c or q to quit:')
    input_state = State(
        nList=[user]
    )
    print(graph.invoke(input_state))
  2. 运行测试,分析输入 'b' 时的执行过程:

    1. 初始状态State(nList=['b'])

    2. 执行节点 A

      • 读取状态 nList[-1],得到 'b'
      • 逻辑判断 next_node = 'b'
      • 返回 Command(update=State(nList=['b']), goto='b')
      • 状态更新update 指令将 ['b'] 追加到原状态。由于 reduceroperator.add,状态变为 nList=['b', 'b']
    3. 跳转与执行 :根据 goto='b',图跳转到节点 B。

    4. 执行节点 B :节点 B 返回 Command(goto=END),图运行结束。

    5. 最终输出状态{'nList': ['b', 'b']}

    6. 输入 'c' 或其它字符(如 'q')的逻辑类似,最终状态会分别为 ['c', 'c']['q']

1.3 方法对比选型

虽然 Command 对象提供了强大的、由节点驱动流程的能力,但在大多数智能体工作流的设计中,笔者更推荐使用 add_conditional_edges 配合条件路由函数的方式来定义边

主要原因在于"关注点分离 "和"可维护性 "。将"做什么"(节点逻辑)和"接下来去哪"(路由逻辑)分开定义,使得图的结构更加清晰、易于理解和调试。图的builder本身就能完整反映业务逻辑的流转,而不是将控制流隐藏在节点的实现细节里。

因此,Command 方案更适合作为一种高级或特定场景下的补充手段,而在构建清晰、可维护的 LangGraph 应用时,应优先考虑使用条件边函数来定义路由。

二、Memory:实现智能体的长期记忆

在 LangGraph 的工作流中,节点按顺序执行,状态随之更新。然而,这种状态默认只存在于单次调用(invoke)的生命周期内。为了让智能体像人类一样拥有"记忆",能够在多次交互中记住上下文(例如,像在 DeepSeek 网页中进行的多轮对话),LangGraph 提供了检查点(Checkpoint) 机制。

2.1 理解检查点:状态快照与线程

检查点 是 LangGraph 实现记忆功能的核心。它本质上是在图运行的关键步骤(如每个节点执行后) 对当前完整状态进行的一次快照(Snapshot),并将其持久化存储。这个过程类似于为虚拟机保存恢复点。

这些按时间顺序排列的检查点集合,构成了一个线程(Thread) 。在线程中,每次graph.invoke调用的历史记录都被完整保存。这解释了为何在 DeepSeek 中,"新对话"会开启一个没有历史的纯净会话------因为它开启了一个新的线程。

检查点的核心优势:

引入检查点机制为智能体开发带来了如下关键能力:

  1. 状态持久化:即使图停止运行,状态也能被保留,下次可从断点恢复。
  2. 错误恢复与重试:当某个节点执行失败时,可以从上一个检查点恢复状态并重试,避免丢失全部进度。
  3. 时间旅行与回滚:如果发现智能体在长期运行后"跑偏",可以回滚到任意历史检查点,并从该健康状态重新开始。
  4. 暂停与继续:可以暂停一个长时间运行的任务,并在之后准确地从中断处继续执行。

2.2 实战:使用内存检查点器

LangGraph 提供了多种检查点存储后端,包括内存 (InMemorySaver)、Postgres数据库等。本篇分享笔者以最简单的内存检查点器为例进行演示。其他存储器的用法类似,大家可以在笔者的文章《深入浅出LangGraph AI Agent智能体开发教程(九)---LangGraph长短期记忆管理》中查看详细示例。

  1. 构建基础图: 复用上一节中基于 Command 对象的路由图结构。

    python 复制代码
    from langgraph.graph import START, END, StateGraph
    import operator
    from typing import TypedDict, List, Annotated, Literal
    from langgraph.types import Command
    
    
    class State(TypedDict):
        nList: Annotated[List[str], operator.add]
    
    def node_a(state: State) -> Command[Literal['b','c', END]]:
        select = state['nList'][-1]
        if select == 'b':
            next_node = 'b'
        elif select == 'c':
            next_node = 'c'
        elif select == 'q':
            next_node = END
        else:
            next_node = END
    
        return Command(
            update=State(nList=[select]),
            goto=next_node
        )
    
    def node_b(state: State):
        return Command(
            goto=END
        )
    
    def node_c(state: State):
        return Command(
            goto=END
        )
    
    
    
    builder = StateGraph(State)
    builder.add_node("a", node_a)
    builder.add_node("b", node_b)
    builder.add_node("c", node_c)
    
    builder.add_edge(START, "a")
  2. 引入内存检查点器:langgraph.checkpoint.memory 导入 InMemorySaver,并实例化。config 中的 thread_id 是关键,它标识了会话线程。相同 thread_id 下的所有调用将共享状态历史。

    python 复制代码
    from langgraph.checkpoint.memory import InMemorySaver
    
    memory = InMemorySaver()
    
    config = {
        "configurable":{
            "thread_id", "1"
        }
    }
  3. 编译时启用检查点: 在编译图时,通过 checkpointer 参数传入检查点器实例。

    python 复制代码
    graph = builder.compile(checkpointer=memory)
  4. 在循环调用中体验记忆: 现在笔者通过一个循环来模拟多轮交互。关键在于,每次调用 graph.invoke 时都传入相同的 config,这样 LangGraph 就会自动加载该线程 (thread_id='1') 的上一次状态,并在执行后保存新状态。

    python 复制代码
    while True:
        user = input('b, c or q to quit:')
        input_state = State(
            nList=[user]
        )
        result = graph.invoke(input_state, config)
        print(result)
        if result['nList'][-1] == 'q':
            print('quit')
            break
  5. 运行测试: 输入序列:b → c → b → q执行过程解析:

    • 第一轮 (输入b) :初始状态 ['b']。节点A执行后更新状态为 ['b', 'b'] 并跳转到节点B,最终状态为 ['b', 'b']
    • 第二轮 (输入c) :由于检查点存在,初始状态并非空列表,而是上一轮的最终状态 ['b', 'b'] 。输入c后,状态会累积到['b','b','c','c']
    • 第三轮 (输入b) :状态继续累积。
    • 第四轮 (输入q) :触发退出。
  6. 通过简单的几步配置,LangGraph图即可具备"记忆"能力。thread_id 是管理不同记忆会话的钥匙。只要使用相同的 thread_id,智能体就能在多次调用间保持并累积状态。对于生产环境,只需将 InMemorySaver 替换为 PostgresSaver 或自定义的持久化检查点器,即可实现稳定可靠的长期记忆。更详细的关于LangGraph记忆的内容大家可参考笔者的文章: 深入浅出LangGraph AI Agent智能体开发教程(九)---LangGraph长短期记忆管理

三、中断与人在回路

在许多实际场景中需要在智能体工作流的关键节点引入人工判断。例如,在大模型调用工具执行数据库写操作、发送邮件或进行其他高风险动作前,必须由人工确认。LangGraph 通过中断(Interrupt) 机制优雅地支持了这种"人在回路(Human-in-the-loop)"模式。

中断允许一个正在运行的图在特定节点暂停,将控制权交还给外部程序(通常是等待用户输入),然后根据外部输入的结果决定如何恢复执行。这个强大的功能同样建立在之前介绍的检查点(Checkpoint) 机制之上。

3.1 核心概念:interruptCommand(resume=...)

继续改造之前使用的条件路由图。核心改动在于,当节点 A 遇到无法处理的输入时,不再直接结束,而是主动引发一个中断,等待外部干预。

实现中断需要两个关键操作:

  1. 在节点内引发中断 :使用 interrupt() 函数。这会抛出一个特殊信号,使图立即暂停运行,并将包含中断信息的快照保存到检查点中。
  2. 从外部恢复执行 :向图中发送一个带有 resume 指令的 Command 对象。这个指令的值会传递回引发中断的节点,作为其 interrupt() 调用的返回值,从而使节点得以继续执行。

3.2 示例:中断代码实战

  1. 笔者对之前的 node_a 进行修改。当用户输入不是预期的 'b''c''q' 时,触发中断。

    python 复制代码
    def node_a(state: State) -> Command[Literal['b', 'c', END]]:
        print('进入 A 节点')
        select = state['nList'][-1]
        if select == 'b':
            next_node = 'b'
        elif select == 'c':
            next_node = 'c'
        elif select == 'q':
            next_node = END
        else:
            admin = interrupt(f"未期望的输出 {select}")
            print('用户重新输入是:',admin)
            if admin == 'continue':
                next_node = 'b'
                select = 'b'
            else:
                next_node = END
                select = 'q'
    
        return Command(
            update=State(nList=[select]),
            goto=next_node
        )
  2. 构建图并启用检查点,中断依赖检查点来保存和恢复状态,因此必须配置检查点器。

    python 复制代码
    from langgraph.graph import START, END, StateGraph
    from langgraph.checkpoint.memory import InMemorySaver
    
    # 构建图
    builder = StateGraph(State)
    builder.add_node("a", node_a)
    builder.add_node("b", node_b)
    builder.add_node("c", node_c)
    builder.add_edge(START, "a")
    
    # 配置内存检查点器和线程
    memory = InMemorySaver()
    config = {"configurable": {"thread_id": "1"}}
    
    # 编译时传入检查点器
    graph = builder.compile(checkpointer=memory)
  3. 实现外部中断处理循环:

    python 复制代码
    while True:
        user = input('b, c or q to quit:')
        input_state = State(
            nList=[user]
        )
        result = graph.invoke(input_state, config)
        print(result)
    
        if '__interrupt__' in result:
            print(f'Interrupt:{result}')
            msg = result['__interrupt__'][-1].value
            print(msg)
            human = input(f"\n{msg}, 重新输入: ")
            human_response = Command(
                resume=human
            )
            result = graph.invoke(human_response, config)
    
        if result['nList'][-1] == 'q':
            print('quit')
            break
  4. 运行结果分析:假设输入 p(一个非法值),流程如下:

    • 触发中断 :节点A识别到 p,调用 interrupt("未预期的输入..."。图在此刻立即暂停 ,并将当前状态(包括中断信息)保存为检查点。graph.invoke 返回的结果中会包含一个特殊的 __interrupt__ 字段。
    • 人工干预 :外部循环检测到中断,打印信息并等待用户输入。用户输入 continue
    • 恢复执行 :外部程序构建一个 Command(resume='continue') 并再次调用 graph.invoke关键点 :因为使用了相同的 thread_id,图会从上一个检查点(即中断处)恢复。
    • 节点继续运行 :恢复后,节点A中 interrupt() 调用处将返回 admin_decision = 'continue'。程序流程进入 if admin_decision == 'continue' 分支,最终前往节点B并结束。

    重要说明 :当图从中断点恢复时,整个图会重新执行。但由于检查点保存了中断前的状态(包括 nList),并且 interrupt() 会返回新的 resume 值,因此节点能基于新信息做出不同的决策。__interrupt__ 是一个列表,因为一个图可以在不同节点触发多个中断,LangGraph 会跟踪所有中断的上下文。

以上完整代码均可关注笔者同名微信公众号:大模型真好玩 ,并私信 LangChain智能体开发 获得

四、总结

本期文章深入讲解了 LangGraph 1.0 的三大进阶功能:条件边 (除条件函数外,还能通过 Command 对象实现节点内路由)、记忆 (利用检查点机制实现状态持久化与多轮对话能力)以及人在回路(通过中断机制在关键节点引入人工干预)。现在大家就学习到了LangGraph的基本技能,但目前这些知识点还比较零碎,下篇分享笔者将结合大模型使用LangGraph编写一个智能体,帮助大家全局掌握这些知识点的用法,掌握LangGraph智能体开发的基本技能!

《深入浅出LangChain&LangGraph AI Agent 智能体开发》专栏内容源自笔者在实际学习和工作中对 LangChain 与 LangGraph 的深度使用经验,旨在帮助大家系统性地、高效地掌握 AI Agent 的开发方法,在各大技术平台获得了不少关注与支持。目前已更新32讲,正在更新LangGraph1.0速通指南,并随时补充笔者在实际工作中总结的拓展知识点。如果大家感兴趣,欢迎关注笔者的掘金账号与专栏,也可关注笔者的同名微信公众号 大模型真好玩 ,每期分享涉及的代码均可在公众号私信: LangChain智能体开发免费获取。

相关推荐
*星星之火*2 小时前
【大白话 AI 答疑】第8篇 BERT与传统机器学习(如贝叶斯)在文本分类中的区别及效果对比
人工智能·机器学习·bert
蜂蜜黄油呀土豆2 小时前
RAG 的基石:文本嵌入模型与向量数据库
langchain·大语言模型·embedding·向量数据库·rag
安徽正LU o561-6o623o72 小时前
露-数显式脑立体定位仪 大动物定位仪 小动物脑定位仪
人工智能
andwhataboutit?2 小时前
pytorch-CycleGAN-and-pix2pix学习
人工智能·pytorch·学习
渡我白衣2 小时前
计算机组成原理(7):定点数的编码表示
汇编·人工智能·嵌入式硬件·网络协议·机器学习·硬件工程
vv_5012 小时前
大模型 langchain-组件学习(中)
人工智能·学习·langchain·大模型
╭⌒若隐_RowYet——大数据2 小时前
AI Agent(智能体)简介
人工智能·ai·agent
Evand J2 小时前
【课题推荐】基于视觉(像素坐标)与 IMU 的目标/自身运动估计(Visual-Inertial Odometry, VIO),课题介绍与算法示例
人工智能·算法·计算机视觉
麦麦大数据2 小时前
F051-vue+flask企业债务舆情风险预测分析系统
前端·vue.js·人工智能·flask·知识图谱·企业信息·债务分析