LangGraph 系列 · 第 1 讲:从 0 搭建你的第一个智能体

198 阅读9分钟

1. LangGraph 介绍

本文是 LangGraph 系列的第 1 讲,目标是带你从 0 搭建一个能做四则运算的有状态智能体,理解图、节点、边和状态这几个核心概念,为后续 Memory、RAG、多代理等高级用法打下基础。

1.1 基本概述

LangGraph 是由 LangChain 团队开发的底层编排框架和运行时,旨在帮助开发者构建基于大型语言模型(LLM)的复杂、有状态、多主体应用。它将工作流表示为有向图(graph),提供更高的灵活性和控制能力,特别适合循环逻辑、状态管理以及多主体协作的场景(如智能代理、多代理工作流)。

适合读者:已具备基础Python / LangChain知识,想要实现“有状态、可循环、可调试”工作流的开发者。

本讲你将学会:

  • 说清LangGraph的基本概念:图、节点、边、状态。
  • Graph API从零构建一个带工具调用的有状态计算器智能体。
  • 看懂START / END / 条件边在工作流中的作用,并能本地运行一个完整示例。

运行环境与版本:

  • Python 3.10+
  • 推荐:pip install -U langgraph langchain-core(本文示例基于 LangGraph / LangChain v0.2+,消息类型从langchain_core.messages导入)
  • 可选:pip install ipython用于可视化。模型默认使用DeepSeek,也可以替换为 OpenAI / 本地模型,需自行准备 API Key。

官方文档:langchain-ai.github.io/langgraph/

1.2 简单示例

首先安装 LangGraph:

pip install -U langgraph
# Requires Python 3.10+

创建一个简单的“Hello World”示例(可直接运行):

from langgraph.graph import StateGraph, MessagesState, START, END

def mock_llm(state: MessagesState):
    return {"messages": [{"role": "ai", "content": "hello world"}]}

graph = StateGraph(MessagesState)
graph.add_node("mock_llm", mock_llm)
graph.add_edge(START, "mock_llm")
graph.add_edge("mock_llm", END)
graph = graph.compile()

graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})

1.3 核心概念:图、节点、边、状态

图结构(Graph Structure)

LangGraph 将应用逻辑组织成一个有向图,其中:

  • 节点(Nodes):代表具体的操作或计算步骤,可以是调用语言模型、执行函数或与外部工具交互等
  • 边(Edges):定义节点之间的连接和执行顺序,支持普通边(直接连接)和条件边(基于条件动态选择下一步)

状态管理(State Management)

LangGraph 的核心特点是自动维护和管理状态

状态(State)是一个贯穿整个图的共享数据结构,记录了应用运行过程中的上下文信息

每个节点可以根据当前状态执行任务并更新状态,确保系统在多步骤或多主体交互中保持一致性

循环能力(Cyclical Workflows)

与传统的线性工作流(如 LangChain 的 LCEL)不同,LangGraph 支持循环逻辑,这使得它非常适合需要反复推理、决策或与用户交互的代理应用。例如,一个代理可以在循环中不断调用语言模型,直到达成目标。

1.4 核心优势

**持久执行:**内置支持状态的保存和恢复,便于错误恢复和长时间运行的任务

**人机交互:**支持“人在回路”(human-in-the-loop)功能,让人类在关键步骤参与决策

综合记忆:创建有状态的智能体,既有用于持续推理的短期记忆,又具有跨会话的长期记忆。

LangSmith调试:可视化跟踪执行路径、捕获状态转换并提供详细的运行时指标。

**灵活性:**开发者可以精细控制工作流的逻辑和状态更新,适应复杂的业务需求

**多主体协作:**允许多个代理协同工作,每个代理负责特定任务,通过图结构协调交互

1.5 使用场景

LangGraph 特别适用于以下场景:

**对话代理:**构建能够记住上下文、动态调整策略的智能聊天机器人

**多步骤任务:**处理需要分解为多个阶段的复杂问题,如研究、写作或数据分析

**多代理系统:**协调多个代理分工合作,比如一个负责搜索信息、另一个负责总结内容的系统

1.6 与 LangChain 的关系

  • LangGraph 是 LangChain 生态的一部分,但它是独立于 LangChain 的一个模块
  • LangChain 更擅长处理简单的线性任务链(DAG),而 LangGraph 专注于更复杂的循环和多主体场景
  • 你可以单独使用 LangGraph,也可以结合 LangChain 的组件(如提示模板、工具接口)来增强功能

2. 快速入门

本快速入门指南演示了如何使用LangGraph Graph API构建一个“会算数”的简单智能体。 前置准备:Python 3.10+;pip install -U langgraph langchain-core,可选pip install ipython用于可视化。示例默认使用 DeepSeek 模型,如未配置可替换为本地/开源模型并调整init_chat_model

本示例中将智能体定义为由节点和边组成的图,使用Graph API来驱动执行。

2.1 定义 tools 和 model

我们将使用 DeepSeek 模型,并定义加法、乘法和除法工具。

from langchain.tools import tool
from langchain.chat_models import init_chat_model

model = init_chat_model(
    model="deepseek-chat", 
    model_provider="deepseek"
)

# Define tools
@tool
def multiply(a: int, b: int) -> int:
    """Multiply `a` and `b`.

    Args:
        a: First int
        b: Second int
    """
    return a * b


@tool
def add(a: int, b: int) -> int:
    """Adds `a` and `b`.

    Args:
        a: First int
        b: Second int
    """
    return a + b


@tool
def divide(a: int, b: int) -> float:
    """Divide `a` and `b`.

    Args:
        a: First int
        b: Second int
    """
    return a / b


# Augment the LLM with tools
tools = [add, multiply, divide]
tools_by_name = {tool.name: tool for tool in tools}
model_with_tools = model.bind_tools(tools)

小检查:此时你可以在 Python 里直接调用add(3, 4)、multiply(2, 5),确认工具函数本身工作正常,再继续后面的图构建。

2.2 定义 State(状态)

图的状态用于存储消息和 LLM 调用次数。

LangGraph 中的状态在智能体执行过程中始终存在。

from langchain_core.messages import AnyMessage
from typing_extensions import TypedDict, Annotated
import operator

class MessagesState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]
    llm_calls: int

这里有两个关键点:

  • messages:对话历史,是一个“不断追加”的列表;Annotated[..., operator.add]告诉 LangGraph 在每个节点执行完后,把新消息追加到旧消息后面,而不是整段替换。
  • llm_calls:我们自己加的一个“计数器字段”,用来记录当前这条对话中 LLM 被调用了多少次,说明状态不仅能存消息,还能存任意业务相关信息。

2.3 定义模型节点(llm_call)

模型节点llm_call用于调用 LLM,并根据对话内容决定是否调用工具。

from langchain_core.messages import SystemMessage

def llm_call(state: dict):
    """LLM decides whether to call a tool or not"""

    return {
        "messages": [
            model_with_tools.invoke(
                [
                    SystemMessage(
                        content="You are a helpful assistant tasked with performing arithmetic on a set of inputs."
                    )
                ]
                + state["messages"]
            )
        ],
        "llm_calls": state.get('llm_calls', 0) + 1
    }

小检查:你可以临时构造一个简单的state = {"messages": [], "llm_calls": 0}并调用一次llm_call(state),确认不会报错,帮助你在真正接入图之前先验证好这一节点的逻辑。

2.4 定义工具节点(tool_node)

工具节点tool_node用于真正执行工具调用并返回结果。

from langchain_core.messages import ToolMessage , HumanMessage


def tool_node(state: dict):
    """Performs the tool call"""

    result = []
    for tool_call in state["messages"][-1].tool_calls:
        tool = tools_by_name[tool_call["name"]]
        observation = tool.invoke(tool_call["args"])
        result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
    return {"messages": result}

2.5 定义结束逻辑(should_continue)

条件边函数用于根据 LLM 是否发出工具调用来路由到工具节点或终点。

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


def should_continue(state: MessagesState) -> Literal["tool_node", END]:
    """Decide if we should continue the loop or stop based upon whether the LLM made a tool call"""

    messages = state["messages"]
    last_message = messages[-1]

    # If the LLM makes a tool call, then perform an action
    if last_message.tool_calls:
        return"tool_node"

    # Otherwise, we stop (reply to the user)
    return END

补充说明:START:图的入口,首次调用从这里开始。END:图的出口,一旦路由到这里,本次执行就结束,结果返回给调用方。should_continue:就是“路由规则函数”,根据最后一条消息是否包含工具调用,决定下一步是继续走tool_node,还是直接结束。

2.6 构建并编译代理程序

使用StateGraph构建工作流图,并通过compile()方法进行编译:

# Build workflow
agent_builder = StateGraph(MessagesState)

# Add nodes
agent_builder.add_node("llm_call", llm_call)
agent_builder.add_node("tool_node", tool_node)

# Add edges to connect nodes
agent_builder.add_edge(START, "llm_call")
agent_builder.add_conditional_edges(
    "llm_call",
    should_continue,
    ["tool_node", END]
)
agent_builder.add_edge("tool_node", "llm_call")

# Compile the agent
agent = agent_builder.compile()

# Show the agent
from IPython.display import Image, display
display(Image(agent.get_graph(xray=True).draw_mermaid_png()))

恭喜!您已使用 LangGraph Graph API 构建了您的第一个智能体。

运行本地服务

最后,我们把上面的工作流封装到一个main()函数里,便于直接通过python quickstart.py运行整个示例:

def main():
    """脚本入口:构建图、可选地展示结构,并执行一次示例调用。"""
    # 可选:尝试展示工作流图结构(终端环境下失败会自动忽略)
    try:
        from IPython.display import Image, display  # 延迟导入,避免缺少 IPython 时直接报错
        img_bytes = agent.get_graph(xray=True).draw_mermaid_png()
        print(f"[info] 已生成工作流图像(字节数: {len(img_bytes)}),在 Notebook 中可直接 display。")
        # 在纯脚本环境中直接 display(Image(...)) 一般不会有可视化效果,这里仅提示用户
    except Exception as e:
        print(f"[warn] 跳过图像展示(可能未安装 IPython 或当前环境不支持图像显示):{e}")

    # 执行一次示例调用
    print("\n[info] 开始执行示例:'Add 3 and 4.'\n")
    messages = [HumanMessage(content="Add 3 and 4.")]
    result = agent.invoke({"messages": messages})
    for m in result["messages"]:
        m.pretty_print()


if __name__ == "__main__":
    main()

执行结果(预期):

  • 控制台提示生成工作流图(纯终端可忽略)。
  • 示例调用 “Add 3 and 4.”,返回工具调用add(3,4),最终回答 “The sum of 3 and 4 is 7。”

2.7 练习与扩展

建议你在本讲结束后尝试完成以下小练习,加深理解:

  • 练习 1:新增一个减法工具 仿照add/multiply定义subtract(a: int, b: int) -> int,并让 LLM 在需要做减法时自动选择这个工具。
  • 练习 2:修改系统提示语SystemMessage的内容改成“如遇到除零,请解释错误而不是直接调用工具”,观察 LLM 在输入 “divide 3 by 0” 时的行为是否符合预期。
  • 练习 3:打印 LLM 调用次数main()中打印result["llm_calls"],看看一次简单请求中 LLM 被调用了多少次,从而理解“循环 + 工具调用”的执行过程。

小提示:如果你把这三个练习都完成了,说明这一讲你已经不仅“看懂了代码”,而且真正掌握了 LangGraph 的基础用法,可以安心进入下一讲的 Memory 专题。

小结与下一讲预告

本讲完成了:

  • 认识 LangGraph 的核心:节点、边、状态,用 Graph API 构建可循环的有状态计算器智能体。
  • 理解工具调用在图中的位置:LLM 节点决定是否调用工具,工具节点执行后再回到 LLM。
  • 拿到最小可跑通的示例,并了解如何生成可视化图(缺图形环境可跳过)。

下一讲(Memory 专题)将重点讲:

  • LangGraph 的内存机制:短期记忆(线程内)与长期记忆(跨线程/跨会话)。
  • 用代码实现短期记忆、长期记忆,演示多线程隔离与跨会话复用。
  • 常见存储选项对比与示例:内存(MemorySaver / InMemoryStore)、SQLite、MySQL、Redis 等。

请准备:一个可用的数据库环境(SQLite 默认即可,或本地 MySQL/Redis 实例)与连接配置,方便直接实践下一讲。