前言

大语言模型已经不只是“一问一答”的玩具了。我们越来越需要它完成多步骤的任务:一边搜索资料一边思考、调用工具获取实时数据、在输出前经过人工审核……这些场景中,应用不再是单根直线,而是一张有分支、有循环、有记忆的网。

LangChain 的链式调用(Chain)把多个步骤串成一条流水线,但它难以优雅地处理循环、条件跳转和状态持久化。为此,LangChain 团队推出了 LangGraph —— 一个基于有向图全局状态的大模型应用编排框架。

今天这篇博客,我们就从最核心的三个概念出发,带你写出第一个 LangGraph 程序,让图“跑”起来。

1. 为什么需要 LangGraph

假设我们要做一个智能客服:

  • 用户提问后,模型先判断意图。
  • 如果是“咨询”,需要去知识库搜索再回答。
  • 如果是“投诉”,需要生成回复草稿,必须经人工审核才能发送。
  • 对话结束前,模型可能需要反复确认信息。

如果用传统链式调用,你可能会写一堆 if-else,在链的中间插入人工交互也非常困难。LangGraph 的设计正是为了这些场景:把应用的流程定义成一张图,节点是具体的处理逻辑,边是流转的规则,而所有信息都存放在一个贯穿始终的“状态”对象里。

2. 核心概念一:状态(State)

状态是整个图执行期间共享的数据结构。LangGraph 图在每一步都会接收当前状态,节点处理完后返回一个部分状态更新,框架再把这些更新合并回全局状态。

通常用 Python 的 TypedDict 定义状态,并为字段指定一个合并策略(Reducer)。最常用的合并策略是给消息列表用的 add_messages

from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import HumanMessage, AIMessage

class State(TypedDict):
    messages: Annotated[list, add_messages]   # 新消息会追加到现有列表末尾
    counter: int                              # 普通字段,直接覆盖

Annotated[list, add_messages] 的意思是:当节点返回 {"messages": [new_msg]} 时,不直接替换整个列表,而是把 new_msg 追加到原来的消息列表中。这非常适合记录聊天历史,我们后面会看到它的效果。

counter 没有用 Annotated,则默认是直接覆盖:节点返回 {"counter": 2},全局状态里的 counter 就会直接变成 2。

3. 核心概念二:图与节点

图是一个 StateGraph 对象,节点就是图中的执行单元——其实就是一个普通的 Python 函数。

节点函数必须遵守一个约定:

  • 接收一个参数:当前全局状态(字典形式)。
  • 返回一个字典,包含想要更新的字段和值。如果某些字段没变,可以不写。

下面定义一个最简单的节点,它模拟一个“智能体”,每被调用一次就让 counter 加 1,并生成一条 AI 消息。

def agent_node(state: State):
    count = state.get("counter", 0)
    new_count = count + 1
    # 假装这是 LLM 生成的回复
    reply = f"收到,这是第 {new_count} 次回复。"
    return {
        "messages": [AIMessage(content=reply)],
        "counter": new_count
    }

然后,我们创建一个 StateGraph,把节点添加进去,并告诉图“从哪里开始”。

from langgraph.graph import StateGraph, END

graph = StateGraph(State)               # 用我们定义的 State 创建图
graph.add_node("agent", agent_node)     # 添加名为 "agent" 的节点
graph.set_entry_point("agent")          # 设置入口节点

现在图上只有一个孤零零的节点,还没办法跑——我们需要用把它连起来。

4. 核心概念三:边

边决定了节点执行完后下一步去哪里。LangGraph 中有两种边:

  • 普通边:无条件地从一个节点走到另一个节点(或直接到 END)。
  • 条件边:根据节点返回的结果动态选择下一个节点。

在我们这个简单例子里,希望“agent”节点被执行最多 3 次,然后结束。所以要定义一个路由函数,根据当前 counter 的值决定是继续循环回 agent 节点,还是走向结束。

def should_continue(state: State):
    if state["counter"] >= 3:
        return "end"         # 结束的信号
    else:
        return "continue"    # 继续循环的信号

# 给 "agent" 节点添加条件边
graph.add_conditional_edges(
    "agent",                # 从哪个节点出发
    should_continue,        # 路由函数
    {
        "continue": "agent",  # 如果路由返回 "continue",就回到 agent 节点
        "end": END            # 如果路由返回 "end",整个图执行结束
    }
)

至此,我们的图形成了一个循环agent -> agent -> agent ... -> END。只要 counter 小于 3,就会不断回到 agent 节点。

5. 最小可运行示例

把所有零件组装起来,编译并执行。

app = graph.compile()

# 准备初始状态:一条用户消息和计数器初值 0
initial_state = {
    "messages": [HumanMessage(content="你好,请开始工作")],
    "counter": 0
}

# 执行图
final_state = app.invoke(initial_state)

# 打印最终的消息列表
for msg in final_state["messages"]:
    print(f"[{msg.type.upper()}] {msg.content}")

输出将会是:

[HUMAN] 你好,请开始工作
[AI] 收到,这是第 1 次回复。
[AI] 收到,这是第 2 次回复。
[AI] 收到,这是第 3 次回复。

可以看到,每经过一次 agent 节点,消息列表就会自动追加一条 AI 消息,而且 counter 也在同步递增。这就是 add_messages 和状态合并机制在起作用——节点只需要关心自己产出的新消息,旧的历史会被框架自动保留。

如果你只放一个节点并用普通边直连 END,那就得到了一个最朴素的“链”;如果再加上条件边和循环,就得到了一个具备自主决策能力的 Agent。LangGraph 的核心就是这么简单。

6. 小结与预告

今天我们一起抓住了 LangGraph 的三个核心要素:

  • 状态(State):全局共享的数据字典,通过 Reducer 控制字段的合并方式。
  • 节点(Node):接受状态、返回部分更新的 Python 函数。
  • 边(Edge):用普通边定义固定流程,用条件边实现智能路由和循环。

用这“三板斧”,你已经能搭建出带记忆、能循环、会分岔的智能流程了。

下一篇博客我们会深入 持久化与人工介入:如何让图在任意步骤暂停、保存、恢复,甚至让人在半路改一改状态再继续执行。届时,你的 Agent 就真正有了“记忆”和“可干预”的能力。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐