Agent教程15:认识LangChain(中),状态机思维

上一节我们列举了LangGraph的基本节点类型,但是只是知道,并不知道怎么组装和使用,先列举下来,后面讲如何组装:

  • Node:一个函数(LLM调用、工具、判断)。
  • Edge:跳转规则(普通边 + 条件边)。
  • State:共享状态(情绪、记忆、亲密度等),支持Reducer自动合并。
  • Checkpoint:自动持久化(内存/SQLite/Postgres),支持崩溃恢复 + 时间旅行调试。
  • Interrupt:原生Human-in-the-loop(用户中途纠正、审批、回滚)。

一、状态机思维

在 LangChain 早期版本(包括 LCEL)中,绝大多数工作流本质上是 有向无环图(DAG) 或简单的线性流水线(Pipeline)。数据像流水一样从 A 流到 B 再流到 C,但很难 "回头"。

LangGraph 最核心的突破就是它是支持环的有向图,相比于LangChain古早的链式版本极大的增强了灵活性。

假设,我们现在有这样一个业务,理想状态下希望LLM依次执行:

  1. 调用工具查询数据
  2. 格式化数据
  3. 打印结果

对,这就是早期的链式思维,一旦LLM因为其随机性出错,整个流程就会轰然倒塌。

因此,事实证明,链式操作救不了Agent开发。

一种更稳健,更方便影对随机性的设计哲学被LangGraph选中:状态机。

状态:可以理解为一组能被所有节点读取和更新的变量。

对状态的使用通常遵循这样的规则:

  • 在Node中更新状态
  • 在选择连线时读取状态

以上图为例,如果格式化失败,那么state='fail',而在选择连线时,因为 state == 'fail',因此进入了格式化自循环的连线。

在正式生产中,我们还可以加入 fail_count 这样的状态,来保证自循环的上限次数。

二、定义状态

你可以这样定义状态:

python 复制代码
from typing import TypedDict, Annotated
class AgentState(TypedDict):
    input_text: str
    messages: Annotated[list[BaseMessage], operator.add]
    result: dict
    status: str  # 状态标识"success" 或 "error"

这个定义很好解释,AgentState 继承自 TypedDict,有4个基本属性。

其中i比较难理解的是这一行:

python 复制代码
messages: Annotated[list[BaseMessage], operator.add] 

简单来说,这是python的注解写法,它规定了 messages的类型是list[BaseMessage],并且注明其更新逻辑只能是add,而不能是覆盖。

后续LangChain在操作该属性时,必须遵守以下方法:

csharp 复制代码
state["messages"] = operator.add(旧的messages列表, 新返回的messages列表)

这就保证了老的聊天历史不会被覆盖。

如果你希望messages只保留最新的5条,可以这样自定义一个合并逻辑:

python 复制代码
# 1. 自己写一个合并逻辑(Reducer)
def keep_last_5_messages(old_messages: list, new_messages: list) -> list:
    """这是一个自定义的 Reducer 函数:合并消息,但只保留最后 5 条"""
    # 先把旧的和新的加起来
    combined = old_messages + new_messages
    # 然后切片,只取最后 5 个元素
    return combined[-5:]

# 2. 把你的函数塞进 Annotated 里!
class AgentState(TypedDict):
    input_text: str
    # 告诉 LangGraph,更新 messages 时用我的函数!
    messages: Annotated[list[BaseMessage], keep_last_5_messages]
    status: str

三、构建节点 Node

本文开头陈列了,Node的本质就是一个函数。

如果我们想按上面梳理的结果,实现一个最简单的例子:

那我们至少需要构建三个函数:

  • node_a_input:返回一段文本
  • node_b_llm:用LLM格式化文本,但有概率失败
  • node_c_print:打印状态中的result值

在langgraph中,一个节点Node大概长这样:

python 复制代码
def node_a_input(state: AgentState):
    """节点A:负责接收初始输入"""
    text = "张三18岁。"
    print(f"\n▶ [Node A] 初始化输入: {text}")
    
    # 将初始数据写到状态黑板上
    return {"input_text": text}
  1. 它可以读取入参 state 上的状态值
  2. 它的return值对象可以更新 state的最新信息

非常好理解,我们可以根据这个来撰写核心的node_b_llm的逻辑:

四、构建有向图

尝试以下代码:

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

workflow = StateGraph(AgentState)

# 添加所有节点
workflow.add_node("node_a", node_a_input)
workflow.add_node("node_b", node_b_llm)
workflow.add_node("node_c", node_c_print)

# 定义流转规则
workflow.add_edge(START, "node_a")
workflow.add_edge("node_a", "node_b")

def router_edge(state: AgentState):
    """条件边:读取 status 字段决定去向"""
    if state["status"] == "error":
        print("  ↳ [Edge 路由] 发现错误状态,打回 Node B 重试!")
        return "retry"
    else:
        print("  ↳ [Edge 路由] 状态成功,放行到 Node C。")
        return "continue"
# 添加条件边:离开 node_b 时,用 router_edge 进行判断
workflow.add_conditional_edges(
    "node_b",
    router_edge,
    {
        "retry": "node_b",     # 如果 router_edge 返回 "retry",跳回 node_b
        "continue": "node_c"   # 如果 router_edge 返回 "continue",跳到 node_c
    }
)

workflow.add_edge("node_c", END)

这部分代码其他部分都非常好理解,无需多言,只有关于边的条件判断的部分需要解释下:

python 复制代码
workflow.add_conditional_edges(
    "node_b",
    router_edge,
    {
        "retry": "node_b",
        "continue": "node_c"
    }
)

node_b 的出口增加择线逻辑 router_edge,如果它返回 retry回到 node_b 进行自循环,如果返回 continue 则跳转到 node_c

router_edge 也是个和 Node类似的方法,可以有效消费 state进行状态机的流转判断。

五、执行脚本

在本系列课程的开源demo项目里,你可以拿到本节课我们讲解的 demo 源码,地址:

github.com/zhangshichu...

让我们尝试执行以下命令:

bash 复制代码
python .\lesson_16\02_state_demo.py

哪怕第一次出错了,整个程序依然可以自动完成重试,并输出期望中的结构。

没错,这就是新版本LangChain的核心思维转变:

  • 忘记链式
  • 拥抱状态机思维

你需要不停地维护一组全局状态,直到你放弃任务或者完成任务。

六、小结

本章,我们学习了新版LangChain和LangGraph的状态机思维。

接下来,我们还需要了解一下langchain的其他基本能力。

敬请期待!

相关推荐
明月_清风2 小时前
告别遮挡:用 scroll-padding 实现优雅的锚点跳转
前端·javascript
明月_清风2 小时前
原生 JS 侧边栏缩放:从 DOM 监听到底层优化
前端·javascript
风象南8 小时前
我把大脑开源给了AI
人工智能·后端
万少11 小时前
HarmonyOS 开发必会 5 种 Builder 详解
前端·harmonyos
橙序员小站13 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
怒放吧德德13 小时前
Netty 4.2 入门指南:从概念到第一个程序
java·后端·netty
雨中飘荡的记忆15 小时前
大流量下库存扣减的数据库瓶颈:Redis分片缓存解决方案
java·redis·后端
炫饭第一名15 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员
王晓枫16 小时前
flutter接入三方库运行报错:Error running pod install
前端·flutter