LangGraph 入门:StateGraph、条件路由与 Agent 的工具调用循环

4 阅读5分钟

LangGraph 入门:StateGraph、条件路由与 Agent 的工具调用循环

脱敏说明:本文为框架教学项目的知识点总结,所有模型名称均以 OpenAI 兼容协议的通用写法呈现,不绑定任何厂商。

一、为什么 Chain 不够,需要 Graph

链式调用(prompt → model → parser)只能表达"一条直线"。但 Agent 的真实运行路径是环形且带分支的:模型可能直接回答,也可能要求调工具,调完工具还要回到模型,是否结束取决于模型上一轮的输出。下一步走哪条边,是运行时根据状态决定的——这正是图编排框架解决的问题。

LangGraph 的三个核心抽象:

  • State:贯穿全图的共享状态(通常是一个带类型的字典)
  • Node:接收 state、返回 state 更新片段的普通函数
  • Edge:固定边,或根据 state 动态决定下一个节点的条件边

二、最小 StateGraph

from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    question: str
    answer: str

def generate(state: State):
    return {"answer": llm.invoke(state["question"])}

graph_builder = StateGraph(State)
graph_builder.add_node("generate", generate)
graph_builder.add_edge(START, "generate")
graph_builder.add_edge("generate", END)

graph = graph_builder.compile()
result = graph.invoke({"question": "解释一下什么是向量检索"})

注意节点函数的返回值是状态更新片段,框架负责把它合并进全局 state——节点不直接改写整个状态,这保证了多节点协作时的状态一致性。

三、条件路由:让 Agent 形成工具调用环

Agent 的标准结构是"模型节点 ↔ 工具节点"的循环,由条件边判断出口:

from langgraph.graph import MessagesState
from langgraph.prebuilt import ToolNode

tools = [search_tool, calculator_tool]
llm_with_tools = llm.bind_tools(tools)

def llm_call(state: MessagesState):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

def should_continue(state: MessagesState):
    last_message = state["messages"][-1]
    # 模型这一轮请求了工具 → 去工具节点;否则 → 结束
    if last_message.tool_calls:
        return "tools"
    return END

builder = StateGraph(MessagesState)
builder.add_node("llm_call", llm_call)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "llm_call")
builder.add_conditional_edges("llm_call", should_continue, ["tools", END])
builder.add_edge("tools", "llm_call")   # 工具执行完回到模型,形成环
agent = builder.compile()

这张图就是 Agent 运行时最本质的样子:

START → llm_call ──(有 tool_calls)──► tools ──► llm_call (循环)
                  └────(无 tool_calls)──────────────────► END

bind_tools 把工具的 JSON Schema 绑定给模型,模型据此输出结构化的工具调用请求;工具节点执行后,结果以工具消息追加回消息列表。条件路由函数是唯一的"交通指挥员",它只读取状态、不产生副作用。

四、消息状态与归并语义

直接用 TypedDict 时一个字段会被新值覆盖;而消息列表需要的是追加语义。框架提供的消息状态内置了归并器(reducer):每次节点返回新消息,框架自动 append 而不是覆盖。自定义状态也可以声明归并函数,例如用 Annotated[list, add] 表达"这个字段做累加"。理解 reducer,是理解"状态如何在节点间流转"的关键。

五、中断恢复:状态机天然支持审批

在图里插入人工审批节点不需要新机制,就是一个条件边 + checkpoint:

builder.add_node("wait_approval", lambda s: interrupt(s["task"]))
builder.add_conditional_edges(
    "process_task",
    lambda s: "wait_approval" if s.get("need_approval") else END,
)
builder.add_edge("wait_approval", "after_approval")

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

编译时挂上 checkpointer,每个超步执行完自动存快照;恢复时用 Command(resume=...) 从断点继续。状态机 + checkpoint 的组合让"暂停—审批—恢复"成为框架能力,而不是业务代码(上一篇已详述)。

六、调试与可视化

编译后的图可以直接输出结构图(graph.get_graph().draw_mermaid()),对教学和 Code Review 极其友好——流程对不对、环在哪里,一眼可见。配合 checkpointer,可以按 thread_id 逐步回放历史状态,排查"Agent 为什么走了这条路"。

七、技术演进与最新差异(2025—2026)

  1. LangGraph 1.0 已 GA(2025-10-22),且是 LTS 版本。 API 在 1.x 全系列保持稳定,小版本升级无破坏性变更。项目教学脚本若基于 0.x 编写,升级时主要注意导入路径和部分预构建组件的位置调整。
  2. 持久化执行成为核心卖点。 早期 LangGraph 主要被当作"图编排库";1.0 之后定位升级为持久化 Agent 运行时——每步 checkpoint、任务级 pending write 持久化,崩溃与部署中断后精确续跑。新项目设计时应从一开始就接入 checkpointer,而不是事后补。
  3. create_agent 成为更高层标准。 LangChain 1.0(2025-10 GA)提供的 create_agent 底层就是本文这张图,但把"模型—工具循环"封装为开箱即用,并支持中间件。需要标准 Agent 时直接用 create_agent;需要自定义拓扑(子图、多 Agent、专门审批流)时才手搭 StateGraph——两者是上下层关系,不是替代关系。
  4. 生态信号。 选型时注意,部分 2024 年流行的多 Agent 框架已进入维护模式(官方 2025 年 10 月公告),新用户被引导迁移;图式工作流模型已成为业界事实标准。

八、小结

LangGraph 的心智模型就一句话:状态在节点间流动,边决定下一站,条件边让分支运行时生效。必练的三张图:最小直线图、"模型—工具"循环图、带中断的审批图。把这三张图手写一遍,再去用高层 create_agent,你对 Agent 的理解会从"调库"升级为"看得懂运行时"。