# LangGraph 介绍与基础组件上手

# 01. LangGraph 介绍与功能

LangGraph 是一个构建具有 状态 、 多角色 应用程序的库,用于创建智能体和多智能体工作流,与其他 LLM 框架相比,LangGraph 提供了以下核心优势:循环、可控制性和持久性。 LangGraph 允许定义设计循环、条件判断的流程 ,这对于高级 Agent 非常重要,这和传统的有向无换图(DAG)解决方案区分开。

因为 LangGraph 作为最底层的框架,所以涉及的组件都是最基础的,并没有过度封装,允许 开发者实现对应用程序流程和状态的精细控制(自由度极高) ,而且 LangGraph 可以便捷集成持久化方案、任意节点中断交互、修改状态等特性,该框架虽然由 LangChain 团队构建,但是却可以在没有 LangChain 的情况下单独使用。

对比 LCEL 表达式,LangGraph 的主要功能:

  • 循环和分支 :在 LLM/Agent 应用程序中使用便捷的方式实现循环和条件语句,而无需配置额外的程序。

  • 持久化 :在图的每个步骤之后自动保存状态,在运行的任意一个阶段都支持暂停和恢复图执行,以支持错误恢复、人机交互工作流、时间旅行等。

  • 人机交互 :中断图的执行以批准或者编辑状态计划去执行下一计划。

  • 流支持 :图结构的每个节点都支持流式输出(包括 token 流式输出)。

  • 与LangChain集成 :LangGraph 可以和 LangChain 和 LangSmith 无缝集成。

图片描述

在一个最基础 LangGraph 应用程序中,涵盖了 3 种基本组件:

  • 状态 :状态是图应用程序处理与交互的基础,是图中所有 节点 和 边 的输入,它可以是一个 dict(字典) 或者 Pydantic模型 ,在 LangGraph 中,状态包括 图的模式(数据结构) 以及如何更新状态的 归纳函数 ,如果没有设置 归纳函数 ,则每次节点都会覆盖 状态 的原始数据。

  • 节点 :节点通常是 Python 函数(同步或异步),其中节点函数的第一个参数是 state(状态) ,第二个参数是 config(Runnable运行的配置) ,节点函数的返回值一般都是 状态 ,整个图的起点被称为开始节点,最后的终点被称为结束节点。

  • 边 :边在图中定义了路由逻辑,即不同节点之间是如何传递的,传递给谁,以及图节点从哪里开始,从哪里结束,并且一个节点可以设置多条边,如果有多条边,则下一条边连接的所有节点都会并行运行。

由于 LangGraph 是一个独立的框架,使用前必须先安装,命令如下:

pip install -U langgraph
1

# 02. LangGraph 基础组件使用

通过上面的图示例,其实可以很容易知道一个 LangGraph 应用程序的组成部分: 节点 、 边 、 状态 ,接下来我们来利用这 3 个组件来构建一个最最基础的 图架构应用 ,仅包含 开始节点 、 中间节点 和 结束节点 的聊天机器人应用,其图结构如下:

图片描述

在这个 图架构 应用中,开始节点连接 LLM大语言模型 节点,并且该节点接收 状态数据 ,并生成对应内容,随后传递给 结束节点 。简单来说, 节点完成工作。边指示下一步要做什么 。

在 LangGraph 中, 开始/结束节点 作为特殊节点,并预定义了,导入后即可使用:

from langgraph.graph import START, END
1

对于状态的声明,LangGraph 推荐使用的是 TypedDict 或者 Pydantic模型 ,并且在 LangGraph 中,还支持对声明的状态使用 归纳函数 ,归纳函数可以修改数据的更新方式,例如下方是一个没有为任何键添加归纳函数的状态:

from typing import TypedDict

class State(TypedDict):
    foo: int
    bar: list[str]
1
2
3
4
5

假设在上述图结构中,输入是 {"foo": 1, "bar": ["hi"]} ,并且第一个节点返回 {"foo": 2} ,LangGraph 会根据返回的数据对状态进行更新,并且节点不需要返回所有数据,这样下一个节点接收到的状态数据为 {"foo": 2, "bar": ["hi"]} ,可以看到默认更新方式是覆盖更新。

如果要使用 归纳函数 ,我们可以使用 Annotated 类型来为特定的键添加,例如为第二个键添加 operator.add 函数,代码更新如下:

from typing import TypedDict, Annotated
from operator import add

class State(TypedDict):
    foo: int
    bar: Annotated[list[str], add]
1
2
3
4
5
6

这个时候,假设图的输入是 {"foo": 1, "bar": ["hi"]} ,然后,第一个 节点 返回 {"bar": ["bye"]} ,此时 状态数据 变为 {"foo": 2, "bar": ["hi", "bye"]} ,即数据会使用 Annotated 标注的函数来进行更新(add 函数会将两个列表的数据相加)。

创建好 状态 后,就可以根据状态来实例化 图结构 了,在 LangGraph 中有两种类型的 图 ,一种是 StateGraph ,另外一种是 MessageGraph ,前者的状态是 自定义字典 ,后者的状态是 消息列表 ,使用示例:

from langgraph.graph import StateGraph

graph_builder = StateGraph(State)
1
2
3

创建好 图结构 后,即可为图添加 节点 与 边 ,使用的函数也非常简单:

  • add_node(节点名称, 节点对应函数) :将一个函数添加到图上,并设置对应的节点名称。

  • add_edge(开始节点名称, 结束节点名称) :将两个节点进行拼接,第一个参数是拼接的开始节点,第二个参数是拼接的结束节点。

完成上述的步骤后,这个时候的 图结构 仍然不可使用,我们需要将其转换成 Runnable可运行组件 ,这个时候就可以调用 .compile() 函数进行编译,编译返回的数据就是 Runnable可运行组件 ,可以调用统一的接口,例如: invoke、stream、batch 等。

通过上述的分析,可以知道在 LangGraph 中,无论多么复杂的项目,都可以拆分成对应的 6 个步骤,只需按照这 6 个步骤来执行即可:

  • 初始化大语言模型和工具(ChatOpenAI、tools)。

  • 用状态初始化图架构(StateGraph 状态图)。

  • 为图定义每一个节点(add_node 函数为图添加节点)。

  • 定义图的起点、终点和节点边(add_edge 函数为图添加边)。

  • 编译图架构为 Runnable 可运行组件(graph.compile 函数编译图)。

  • 调用编译后的 Runnable 可运行组件执行图(graph.invoke 函数调用图)。

例如将上述的 3节点聊天机器人 实现后,示例代码如下:

from typing import TypedDict, Annotated

import dotenv
from langchain_openai import ChatOpenAI
from langgraph.graph import START, END
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages

dotenv.load_dotenv()


class GraphState(TypedDict):
    """图状态数据结构,类型为字典"""
    messages: Annotated[list, add_messages]  
    node_name: str


llm = ChatOpenAI(model="gpt-4o-mini")


def chatbot(state: GraphState) -> GraphState:
    """聊天机器人函数"""
    
    ai_message = llm.invoke(state["messages"])
    
    return {"messages": [ai_message], "node_name": "chatbot"}



graph_builder = StateGraph(GraphState)


graph_builder.add_node("llm", chatbot)


graph_builder.add_edge(START, "llm")
graph_builder.add_edge("llm", END)


graph = graph_builder.compile()


print(graph.invoke({"messages": [("human", "你好,你是?")], "node_name": "graph"}))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

输出内容:

{'messages': [HumanMessage(content='你好,你是?', id='b1bf444b-ad7f-4cf7-bccb-510c85787d32'), AIMessage(content='你好!我是一个人工智能助手,随时准备回答你的问题或帮助你解决问题。有什么我可以帮你的吗?', response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 11, 'total_tokens': 38}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_80a1bad4c7', 'finish_reason': 'stop', 'logprobs': None}, id='run-97b1f830-8b49-4531-af41-fa26f86b1ded-0', usage_metadata={'input_tokens': 11, 'output_tokens': 27, 'total_tokens': 38})], 'node_name': 'chatbot'}
1

得益于 LangSmith 日志信息的采集,并且编译后的 图架构应用 也是一个 Runnable 可运行组件,所以可以完整观察图的整个运行流程,为后续复杂 图架构应用 开发调试流程可观察提供了基础。

图片描述

# 最新版 LangChain 用法提示

  • 新版 LangChain 更推荐用 LCEL、Runnable、ChatModel.bind_tools()、结构化输出和 LangGraph 来组织复杂链路;老式 Chain、部分 AgentExecutor 写法可以读懂,但新项目应优先选择更清晰的图或 Runnable 编排。

  • 工具调用相关代码要区分两层:模型是否原生支持 tool/function calling,以及业务侧如何定义工具 schema、参数校验、错误兜底和观测日志。

  • 如果示例中的导入路径和你当前安装版本不同,优先查当前版本包内导出位置;常见迁移方向是从 langchain 拆到 langchain-core、langchain-community、langchain-openai、langgraph 等包。

# 拓展

  • 工具或插件不要只看能不能调通,更要看是否可观测、可限流、可重试、可审计。联网类工具还要处理超时、空结果、搜索噪声和结果时效性。

  • Agent 场景里,Prompt 只是调度策略的一部分;工具描述、参数 schema、历史状态、错误反馈、停止条件和人工介入点同样会影响最终稳定性。

# 常见问题

  • 为什么模型没有调用工具?常见原因是工具描述不清晰、参数 schema 过宽或过窄、用户问题不需要工具、模型本身不支持工具调用,或者工具绑定位置不对。

  • 为什么工具调用后回答仍然不准?先看工具返回是否正确,再看工具结果是否被放回模型上下文,最后检查输出解析、历史消息和异常兜底是否覆盖了真实错误。

# 面试题

  • 解释函数调用、工具调用和 Agent 的区别。

  • LangChain 中 tool schema 的作用是什么?为什么参数校验对生产环境很重要?

  • ReACT Agent 和 tool-calling Agent 的核心差异是什么?分别适合什么场景?

  • LangGraph 相比 LCEL 更适合解决哪些复杂编排问题?

# 生产问题排查

问题 常见原因 处理方式
工具没有被调用 工具描述弱、绑定失败、模型不支持 打印绑定后的模型配置,补充工具描述,换用支持工具调用的模型
参数格式错误 schema 设计不清晰,模型生成字段不稳定 使用 Pydantic/JSON Schema 校验,失败后把错误反馈给模型重试
联网结果不可用 搜索为空、接口超时、命中低质量页面 增加超时、重试、结果过滤、来源白名单和降级回答
Agent 循环不停止 缺少终止条件或工具返回被误判 设置最大迭代次数,记录每轮 thought/action/observation,增加停止规则
线上难以复现 缺少输入、工具请求和模型响应日志 给每次调用加 trace id,记录工具入参、出参、耗时和异常