在构建基于大语言模型(LLM)的复杂应用时,传统的线性调用模式往往捉襟见肘——多步骤推理、多智能体协作、条件分支与循环校验等场景,需要一种更灵活、可维护的编排方式。LangGraph 作为 LangChain 生态下的核心扩展库,将图结构引入LLM工作流,把每一次LLM调用、工具执行、逻辑判断抽象为节点和边,让复杂业务逻辑的编排如同搭建积木。本文将从后端架构视角出发,结合微服务、中间件等概念,手把手带你搭建第一个 LangGraph 应用,并解析其核心原理与实战技巧。

一、LangGraph 核心概念:节点、边与状态

LangGraph 的设计灵感源自有向图,其核心组件与后端架构中的微服务概念异曲同工。以下三个要素是理解 LangGraph 的基础:

  • 节点(Node):相当于微服务中的独立服务单元,可封装任意操作——LLM 生成、工具调用、数据处理、逻辑判断等。每个节点接收上下文(State)作为输入,执行后返回更新后的上下文。
  • 边(Edge):定义节点间的调用关系,分为确定性边(固定流向,如 A→B)和条件边(根据节点输出动态判断流向,如 if-else 分支)。
  • 状态(State):贯穿整个图的全局数据容器,类似微服务架构中的共享数据库或消息队列,存储所有节点的输入、输出和中间结果,确保多节点协作时数据一致。

相比传统线性工作流,LangGraph 支持循环(如多轮校验)、分支(如根据意图切换处理逻辑)、并行(如多智能体同时工作),且具备可追溯、可调试的特性,非常适合构建智能助手、代码生成器、推理引擎等复杂LLM应用。

二、环境准备:安装依赖与配置

在编写代码前,需安装 LangGraph 及相关依赖。LangChain 用于调用LLM,这里以 OpenAI 模型为例,也可替换为本地模型。建议通过 .env 文件管理 API Key,避免硬编码。

# 安装核心依赖
pip install langgraph langchain openai python-dotenv
# 可选:若需使用其他工具(如搜索、数据库),安装对应依赖
# pip install langchain-community

⚠️ 注意:OpenAI API Key 需在官网申请,并妥善保管。如果你使用本地模型(如 Ollama),需调整 LangChain 的模型初始化方式。

三、基础实战:多步骤问答工作流

我们搭建一个“用户提问 → LLM 初步回答 → 校验完整性 → 完善回答(若不完整)→ 返回最终结果”的闭环工作流。该示例包含4个节点、1条条件边和2条确定性边,清晰展现 LangGraph 的分支与循环能力。

3.1 完整代码实现

from dotenv import load_dotenv
from langgraph.graph import Graph, StateGraph, END
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import Dict, Any
# 1. 加载环境变量(读取OpenAI API Key)
load_dotenv()
# 2. 定义全局状态(State):存储所有中间结果
# 用Pydantic模型定义State,结构更清晰,支持类型校验
class QAState(BaseModel):
    question: str = Field(description="用户的原始问题")
    initial_answer: str = Field(default="", description="LLM的初步回答")
    is_complete: bool = Field(default=False, description="回答是否完整的标记")
    final_answer: str = Field(default="", description="最终完善后的回答")
# 3. 初始化LLM(这里使用OpenAI的gpt-3.5-turbo,可替换为其他模型)
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
# 4. 定义各个节点(Node)的执行逻辑
# 节点1:接收用户问题(初始化状态)
def init_state(state: QAState) -> Dict[str, Any]:
    """初始化状态,将用户问题存入State"""
    return {"question": state.question, "initial_answer": "", "is_complete": False}
# 节点2:LLM生成初步回答
def generate_initial_answer(state: QAState) -> Dict[str, Any]:
    """根据用户问题,生成初步回答"""
    prompt = f"请回答用户的问题,简洁明了,无需展开:{state.question}"
    response = llm.invoke(prompt)
    return {"initial_answer": response.content}
# 节点3:校验回答完整性(条件判断节点)
def check_answer_complete(state: QAState) -> str:
    """校验初步回答是否完整,返回下一步节点的名称"""
    # 简单校验逻辑:判断回答长度是否大于50字符,或是否包含关键信息(可自定义复杂逻辑)
    prompt = f"""
    请判断以下回答是否完整回答了用户的问题,只需返回"完整"或"不完整":
    用户问题:{state.question}
    初步回答:{state.initial_answer}
    """
    check_result = llm.invoke(prompt).content.strip()
    if check_result == "完整":
        state.is_complete = True
        return "complete_node"  # 流向“生成最终回答”节点
    else:
        state.is_complete = False
        return "improve_node"  # 流向“完善回答”节点
# 节点4:完善回答(循环节点)
def improve_answer(state: QAState) -> Dict[str, Any]:
    """如果回答不完整,基于初步回答进行完善"""
    prompt = f"""
    你的初步回答不够完整,请结合用户问题,补充关键信息,完善回答:
    用户问题:{state.question}
    初步回答:{state.initial_answer}
    完善要求:逻辑清晰,信息全面,不冗余
    """
    improved_response = llm.invoke(prompt)
    return {"initial_answer": improved_response.content}  # 覆盖初步回答,用于再次校验
# 节点5:生成最终回答
def generate_final_answer(state: QAState) -> Dict[str, Any]:
    """将完整的回答整理为最终格式,存入State"""
    final_prompt = f"""
    请将以下回答整理为最终版本,语气友好,格式规范:
    用户问题:{state.question}
    完整回答:{state.initial_answer}
    """
    final_response = llm.invoke(final_prompt)
    return {"final_answer": final_response.content}
# 5. 构建LangGraph(StateGraph)
# 初始化图,指定State类型
graph_builder = StateGraph(QAState)
# 向图中添加所有节点(参数:节点名称,节点执行函数)
graph_builder.add_node("init", init_state)
graph_builder.add_node("generate_initial", generate_initial_answer)
graph_builder.add_node("check_complete", check_answer_complete)
graph_builder.add_node("improve", improve_answer)
graph_builder.add_node("complete", generate_final_answer)
# 6. 定义节点之间的边(流向)
# 确定性边:从init节点 → generate_initial节点(固定流向)
graph_builder.add_edge("init", "generate_initial")
# 确定性边:从generate_initial节点 → check_complete节点(固定流向)
graph_builder.add_edge("generate_initial", "check_complete")
# 条件边:从check_complete节点出发,根据返回值流向不同节点
graph_builder.add_conditional_edges(
    source="check_complete",  # 源节点
    # 条件函数:接收源节点的输出(这里是check_answer_complete的返回值),返回目标节点名称
    path_selector=lambda x: x,
    # 映射关系:源节点输出 → 目标节点
    path_map={
        "improve_node": "improve",  # 输出"improve_node" → 流向improve节点
        "complete_node": "complete"  # 输出"complete_node" → 流向complete节点
    }
)
# 确定性边:improve节点 → check_complete节点(循环校验,直到回答完整)
graph_builder.add_edge("improve", "check_complete")
# 确定性边:complete节点 → 结束节点(END是LangGraph内置的结束标记)
graph_builder.add_edge("complete", END)
# 7. 设置图的入口节点(起始点)
graph_builder.set_entry_point("init")
# 8. 编译图(生成可运行的图实例)
graph = graph_builder.compile()
# 9. 运行图,传入初始状态(用户问题)
if __name__ == "__main__":
    # 初始状态:仅传入用户问题
    initial_state = QAState(question="请详细说明LangGraph和LangChain的区别与联系")
    # 运行图(stream_mode="values"表示返回每一步的状态,便于调试)
    for step, state in enumerate(graph.stream(initial_state, stream_mode="values")):
        print(f"\n=== 步骤 {step + 1} ===")
        print(f"当前节点:{graph.get_current_node()}")
        print(f"用户问题:{state.question}")
        print(f"初步回答:{state.initial_answer}")
        print(f"回答是否完整:{state.is_complete}")
        print(f"最终回答:{state.final_answer}")
    # 输出最终结果
    final_result = graph.invoke(initial_state)
    print("\n=== 最终结果 ===")
    print(f"用户问题:{final_result.question}")
    print(f"最终回答:{final_result.final_answer}")

3.2 代码解析

  • State 定义:使用 Pydantic 模型 QAState 定义全局状态,明确存储用户问题、初步回答、完整性标记、最终回答,确保数据结构清晰,便于节点间读写。
  • 节点逻辑:每个节点是一个函数,接收 State 并返回修改后的片段(字典格式),LangGraph 自动合并这些片段,更新全局状态。
  • 边的配置
    • 确定性边(add_edge):固定流向,如“初始化 → 生成初步回答”“完善回答 → 重新校验”。
    • 条件边(add_conditional_edges):动态流向,由校验节点的输出决定,实现“完整则结束,不完整则完善”的分支逻辑。
  • 运行方式stream 方法流式返回每一步状态,便于调试;invoke 方法直接返回最终状态,适合生产部署。

3.3 运行效果示例

当输入“请详细说明LangGraph和LangChain的区别与联系”时,流程如下:

  1. init 节点初始化状态,存入用户问题。
  2. generate_initial 节点生成初步回答(可能较简洁)。
  3. check_complete 节点校验,判断不完整,流向 improve 节点。
  4. improve 节点完善回答,补充细节。
  5. check_complete 节点再次校验,判断完整,流向 complete 节点。
  6. complete 节点整理最终回答,流向 END,运行结束。

整个过程无需人工干预,实现自动校验与循环完善,类似微服务架构中的重试机制。

四、进阶技巧:并行节点与持久化

LangGraph 的扩展特性使其能应对更复杂的后端架构场景。

4.1 并行节点:多智能体协作

LangGraph 支持多个节点并行执行,类似微服务中的异步任务。例如,两个智能体同时处理理论和实战内容:

from langgraph.graph import StateGraph
# 定义两个并行执行的节点(比如两个不同的智能体)
def agent1(state: QAState) -> Dict[str, Any]:
    """智能体1:补充理论相关内容"""
    prompt = f"补充LangGraph的理论基础:{state.initial_answer}"
    return {"initial_answer": state.initial_answer + "\n" + llm.invoke(prompt).content}
def agent2(state: QAState) -> Dict[str, Any]:
    """智能体2:补充实战相关内容"""
    prompt = f"补充LangGraph的实战案例:{state.initial_answer}"
    return {"initial_answer": state.initial_answer + "\n" + llm.invoke(prompt).content}
# 构建图时,添加并行边
graph_builder.add_parallel_edges(
    source="generate_initial",  # 源节点
    destinations=["agent1", "agent2"],  # 并行的目标节点
    then="check_complete"  # 并行节点执行完毕后,统一流向的节点
)

上述代码中,agent1agent2 并行执行,分别补充理论和实战内容,执行完毕后共同流向 check_complete 节点,显著提升效率。这种模式在需要同时调用多个中间件(如数据库查询、外部API)时尤为实用。

4.2 持久化与调试

LangGraph 支持将图的结构和运行状态持久化,并提供可视化调试工具,便于排查问题:

# 1. 可视化图结构(生成PNG文件)
graph.draw("qa_graph.png")  # 需要安装graphviz:pip install graphviz
# 2. 持久化图的运行状态(使用LangChain的持久化工具)
from langchain.storage import LocalFileStore
from langgraph.checkpoint.memory import MemorySaver
# 初始化内存持久化器(可替换为本地文件、数据库等)
memory = MemorySaver()
# 编译图时指定持久化器
graph = graph_builder.compile(checkpointer=memory)
# 运行图时,指定会话ID,可恢复会话状态
session_id = "user_123"
final_state = graph.invoke(initial_state, config={"configurable": {"session_id": session_id}})
# 恢复会话状态(比如用户中断后,继续之前的工作流)
restored_state = graph.get_state(config={"configurable": {"session_id": session_id}})

持久化功能类似微服务中的日志系统,可记录每次运行的状态快照,便于回溯分析。结合可视化工具,你能直观看到每个节点的输入输出,快速定位故障点。

[AFFILIATE_SLOT_1]

五、实战场景延伸

LangGraph 的应用场景广泛,以下典型场景与后端架构、中间件紧密相关:

  • 代码生成与调试:节点1(生成代码)→ 节点2(运行代码校验)→ 节点3(调试错误)→ 节点4(生成最终代码),循环直至代码可运行。类似 CI/CD 流水线中的自动重试。
  • 智能助手:节点1(意图识别)→ 节点2(根据意图选择工具:搜索/数据库/LLM)→ 节点3(执行工具,调用中间件)→ 节点4(整理回复)。
  • 多文档总结:节点1(拆分文档)→ 节点2(并行总结单篇文档,类似 MapReduce)→ 节点3(合并总结)→ 节点4(校验完整性)。

在这些场景中,LangGraph 充当了工作流引擎的角色,类似微服务架构中的服务编排层,负责协调多个服务端组件(LLM、数据库、缓存等)的调用顺序与数据流转。

[AFFILIATE_SLOT_2]

六、总结

LangGraph 以图结构为核心,打破了LLM应用的线性限制,让复杂工作流的编排变得直观、灵活。其核心价值在于:将分散的操作(LLM调用、工具调用、逻辑判断)抽象为节点,通过边定义流向,实现分支、循环、并行等复杂逻辑,同时借助 State 管理全局数据,让多节点协作变得简单。从后端架构视角看,LangGraph 可视为一种轻量级的工作流中间件,适用于微服务、服务端等场景。建议你动手修改代码(如替换LLM模型、调整校验逻辑、添加并行节点),快速熟悉其特性,将其应用到实际开发中。