前言

在过去的一年里,AI Agent 的概念从一个遥远的研究课题迅速变成了每个开发者工具箱中的必备技能。从最早的 AutoGPT 掀起的热潮,到后来 CrewAI、AutoGen 等框架的百家争鸣,再到如今 LangGraph 的异军突起,整个 Agent 生态正在经历一场前所未有的变革。

我还记得 2023 年夏天第一次尝试 AutoGPT 的情景。当时我给它布置了一个简单的任务:“研究 Python 异步编程的最佳实践并写一份总结报告”。看着它在终端里不断地思考、搜索、写文件,那种震撼感至今难忘。然而,兴奋过后留下的却是深深的挫败感——这个 Agent 经常在某个环节陷入死循环,或者完全偏离了最初的目标,最后生成的报告质量也参差不齐。

相信很多开发者都有过类似的经历。早期的 Agent 框架虽然理念先进,但在实际应用中却面临着诸多挑战:

  • 状态管理混乱:Agent 的中间状态散落在各个地方,难以追踪和调试
  • 可控性差:Agent 经常"神游天外",偏离用户设定的目标
  • 工作流僵化:难以定义复杂的分支逻辑和循环流程
  • 持久化困难:对话中断后无法恢复之前的状态
  • 多 Agent 协作复杂:多个 Agent 之间的通信和协调缺乏统一标准

这些问题并不是某一个框架的缺陷,而是整个 Agent 范式在发展初期必然要经历的成长阵痛。直到 LangGraph 的出现,我们终于看到了一条清晰的解决路径。

LangGraph 核心架构概览

LangGraph 不是对现有 Agent 框架的简单改良,而是一次底层设计理念的革命。它放弃了传统的"黑盒式"Agent 设计,转而采用图状态机的架构思想,将 Agent 的整个决策过程建模为一个有向图。这种设计带来了前所未有的可控性和灵活性。

在这篇文章中,我将带你从零开始掌握 LangGraph 的核心概念和实战技巧。我们不仅会讲解理论知识,还会通过三个完整的实战项目——从最简单的研究 Agent,到复杂的多 Agent 代码审查系统,让你真正掌握用 LangGraph 构建生产级 Agent 应用的能力。


一、为什么选择 LangGraph 而不是传统 Agent 框架?

在深入技术细节之前,我们需要回答一个最根本的问题:在众多 Agent 框架中,为什么 LangGraph 值得你投入时间学习?

1.1 从链到图的范式转变

传统的 LangChain 应用本质上是**线性链(Chain)**结构。数据从输入端流入,经过一系列固定的处理步骤,最终从输出端流出。这种结构对于简单的问答系统、RAG 应用来说足够用了,但当你需要构建更复杂的 Agent 系统时,线性链的局限性就暴露无遗:

  • 无法处理循环逻辑:Agent 可能需要多次调用工具才能解决问题
  • 缺乏条件分支:根据不同的输入,Agent 应该选择不同的处理路径
  • 状态难以共享:Chain 之间的数据传递需要手动管理
  • 调试极其困难:出了问题很难定位是哪一步出了错

LangGraph 的核心洞察是:Agent 的决策过程本质上是一个图,而不是链。通过将 Agent 的行为建模为图结构,我们自然而然地获得了循环、分支、状态共享等能力。

1.2 可控性与灵活性的完美平衡

早期的 Agent 框架走向了两个极端:

极端一:完全自动化(AutoGPT 模式)

  • 优点:能力强大,理论上可以完成任何任务
  • 缺点:完全不可控,经常跑偏,成本高昂

极端二:完全固定(LangChain Chain 模式)

  • 优点:流程可控,成本可预测
  • 缺点:灵活性差,只能处理预设的场景

LangGraph 找到了这两个极端之间的黄金平衡点。你可以精确地定义 Agent 的工作流程图,同时保留 LLM 的推理能力。你既可以让 Agent 在你定义的边界内自由探索,也可以在关键时刻介入进行人工干预。

1.3 内置的企业级特性

LangGraph 从设计之初就考虑了生产环境的需求,提供了许多开箱即用的企业级特性:

特性 说明 传统框架
状态持久化 Checkpointer 自动保存对话状态 需要自己实现
Human-in-the-loop 支持在任意节点暂停等待人工确认 不支持或极其复杂
断点续跑 中断的对话可以从断点继续 不支持
异步支持 原生支持异步执行 部分支持
流式输出 Token 级别的流式响应 有限支持
时间旅行 可以回溯到历史任意状态重新运行 不支持

这些特性不是锦上添花,而是构建企业级 Agent 应用的必备条件。如果你曾经尝试过将一个简单的 Agent Demo 部署到生产环境,你就会明白这些特性有多重要。

1.4 与 LangChain 生态的无缝集成

LangGraph 不是 LangChain 的替代品,而是 LangChain 生态的自然延伸。这意味着:

  • 你可以继续使用所有现有的 LangChain 工具、集成、文档加载器
  • 已有的 RAG 应用可以无缝迁移到 LangGraph
  • 学习曲线平缓,大部分概念你已经熟悉
  • 可以共享 LangChain 的社区资源和最佳实践

对于已经在使用 LangChain 的团队来说,迁移到 LangGraph 的成本极低,但收益却是巨大的。


二、LangGraph 的核心设计理念

理解 LangGraph 的最佳方式不是从代码开始,而是从它的设计哲学入手。LangGraph 的整个架构建立在三个核心概念之上:状态(State)节点(Node)边(Edge)

2.1 一切围绕状态

在 LangGraph 中,状态是一等公民。这是它与传统框架最本质的区别。

在 LangChain Chain 中,数据在各个步骤之间传递是隐式的。每个步骤接收一些输入,产生一些输出,然后传递给下一个步骤。这种模式的问题在于:没有一个全局统一的视图来查看整个系统的状态。

而在 LangGraph 中,整个图的执行过程就是状态不断演化的过程。所有节点共享同一个状态对象,每个节点读取状态、处理、然后更新状态。这种设计带来了几个关键优势:

  1. 全局可见性:你可以在任何时候查看完整的系统状态
  2. 易于调试:出问题时可以检查状态,定位问题所在
  3. 持久化简单:只需要保存状态对象即可实现持久化
  4. 并发安全:状态更新是原子性的,支持并发执行

2.2 节点:图的执行单元

节点是 LangGraph 中执行实际工作的地方。每个节点本质上就是一个 Python 函数,它接收当前状态作为输入,返回对状态的更新。

节点可以做任何事情:

  • 调用 LLM 进行推理
  • 调用外部工具(搜索、代码执行、API 调用)
  • 执行数据处理逻辑
  • 甚至什么都不做,只是作为一个标记

节点的设计遵循单一职责原则。理想情况下,每个节点只做一件事情,比如:

  • call_model:调用 LLM 生成响应
  • execute_tool:执行工具调用
  • human_feedback:等待人工输入
  • summarize:对对话进行总结

这种细粒度的设计使得整个系统更加模块化,也更容易测试和调试。

2.3 边:控制流的表达

边定义了图中的执行顺序。LangGraph 支持三种类型的边:

普通边(Normal Edge):从节点 A 指向节点 B,表示执行完 A 之后一定执行 B。

条件边(Conditional Edge):从节点 A 出发,根据一个路由函数的返回值决定下一步跳转到哪个节点。这是实现分支逻辑的关键。

入口边(Entry Point):定义图的起始节点。

这种基于边的控制流机制比传统的流程控制强大得多。你可以构建任意复杂的工作流:顺序执行、条件分支、循环、并行执行……只要你能画出来的流程图,LangGraph 就能实现。

2.4 执行模型:事件驱动的循环

LangGraph 的执行过程可以简化为一个简单的循环:

1. 从队列中取出下一个要执行的节点
2. 将当前状态传递给节点函数
3. 节点函数执行并返回状态更新
4. 应用状态更新到全局状态
5. 根据边的定义计算后续节点
6. 如果还有后续节点,回到步骤 1
7. 否则,执行结束

这个简单的模型是整个 LangGraph 的核心。理解了这个循环,你就理解了 LangGraph 80% 的工作原理。


三、状态管理:LangGraph 的灵魂

状态管理是 LangGraph 最重要的概念,也是最容易被误解的概念。让我们深入探讨这个话题。

3.1 定义你的状态 Schema

在 LangGraph 中,状态不是一个无结构的字典,而是一个有明确类型定义的 Schema。你可以使用 TypedDict、Pydantic 模型,甚至 dataclass 来定义状态的结构。

这是一个典型的状态定义:

from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage
import operator

class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    research_notes: str
    code_output: str
    review_feedback: str
    iteration_count: int

这里有几个关键点需要理解:

类型注解不是装饰:LangGraph 会真正使用这些类型信息。特别是 Annotated 类型,它告诉 LangGraph 如何处理这个字段的更新。

Reducer 函数operator.add 在这里不是一个普通的类型标注,而是一个 Reducer 函数。当多个节点同时更新 messages 字段时,LangGraph 会使用这个函数来合并更新。

可扩展性:状态可以包含任意类型的数据,从简单的字符串到复杂的自定义对象。

3.2 Reducer:状态更新的秘密

Reducer 是 LangGraph 状态管理中最巧妙的设计。让我们通过一个例子来理解它的作用。

假设我们有两个节点并发执行,它们都需要更新 messages 字段。如果没有 Reducer,后执行的节点会覆盖先执行的节点的更新,导致数据丢失。

有了 Reducer 之后,LangGraph 会这样处理:

# 初始状态
state = {"messages": [msg1, msg2]}

# 节点 A 返回的更新
update_a = {"messages": [msg3]}

# 节点 B 返回的更新
update_b = {"messages": [msg4]}

# LangGraph 使用 operator.add 合并更新
state["messages"] = operator.add(state["messages"], update_a["messages"])
# 现在 state["messages"] = [msg1, msg2, msg3]

state["messages"] = operator.add(state["messages"], update_b["messages"])
# 最终 state["messages"] = [msg1, msg2, msg3, msg4]

这就是为什么我们要用 Annotated[Sequence[BaseMessage], operator.add] 而不是简单的 list。这个注解告诉 LangGraph:“当更新这个字段时,不要覆盖,而是把新的消息追加到列表后面。”

你甚至可以定义自定义的 Reducer 函数来处理更复杂的合并逻辑:

def merge_dicts(existing: dict, new: dict) -> dict:
    """自定义 Reducer:深度合并两个字典"""
    result = existing.copy()
    result.update(new)
    return result

class ComplexState(TypedDict):
    config: Annotated[dict, merge_dicts]
    results: Annotated[list, operator.add]

(第一部分完,约2400字)


四、图构建:从简单到复杂

现在我们已经理解了核心概念,让我们开始实际构建图。LangGraph 的图构建 API 设计得非常优雅,只需要几分钟就能上手。

4.1 最基本的图结构

让我们从一个最简单的例子开始——一个只有两个节点的图:

from langgraph.graph import StateGraph, END

# 1. 定义状态
class SimpleState(TypedDict):
    input: str
    output: str
    counter: int

# 2. 定义节点函数
def node_1(state: SimpleState) -> SimpleState:
    """第一个节点:处理输入"""
    print("Running Node 1")
    return {"counter": state.get("counter", 0) + 1}

def node_2(state: SimpleState) -> SimpleState:
    """第二个节点:生成输出"""
    print("Running Node 2")
    processed = f"Processed: {state['input']}"
    return {"output": processed, "counter": state["counter"] + 1}

# 3. 创建图
graph = StateGraph(SimpleState)

# 4. 添加节点
graph.add_node("node1", node_1)
graph.add_node("node2", node_2)

# 5. 设置入口点
graph.set_entry_point("node1")

# 6. 添加边
graph.add_edge("node1", "node2")
graph.add_edge("node2", END)

# 7. 编译图
app = graph.compile()

运行这个图非常简单:

result = app.invoke({"input": "Hello, LangGraph!"})
print(result)
# {'input': 'Hello, LangGraph!', 'counter': 2, 'output': 'Processed: Hello, LangGraph!'}

这个例子虽然简单,但展示了 LangGraph 图构建的完整流程。

4.2 条件边:实现分支逻辑

条件边是 LangGraph 最强大的特性之一,也是实现 Agent 决策能力的关键。让我们看一个例子:

def should_continue(state: SimpleState) -> str:
    """路由函数:决定下一步去哪里"""
    if state["counter"] < 5:
        return "node1"  # 继续循环
    else:
        return "node2"  # 结束循环

# 修改图的构建
graph = StateGraph(SimpleState)
graph.add_node("node1", node_1)
graph.add_node("node2", node_2)
graph.set_entry_point("node1")

# 使用条件边
graph.add_conditional_edges(
    "node1",  # 源节点
    should_continue,  # 路由函数
    {
        "node1": "node1",  # 映射返回值到目标节点
        "node2": "node2"
    }
)
graph.add_edge("node2", END)

app = graph.compile()

现在当我们运行这个图时,它会在 node1 循环 5 次,然后才会走到 node2 并结束。这就是实现 Agent"思考-行动"循环的基本模式!

4.3 内置工具节点:MessageGraph

对于大多数 Agent 应用,你只需要处理消息列表。LangGraph 为此提供了一个专门的 MessageGraph

from langgraph.graph import MessageGraph
from langchain_openai import ChatOpenAI

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

def call_model(state):
    return [model.invoke(state)]

graph = MessageGraph()
graph.add_node("agent", call_model)
graph.set_entry_point("agent")
graph.add_edge("agent", END)

app = graph.compile()
result = app.invoke([("user", "你好,解释一下量子纠缠")])

MessageGraph 本质上就是预定义了状态为 Sequence[BaseMessage]StateGraph,它是构建 Agent 应用最常用的起点。


五、实战一:构建一个研究 Agent

现在让我们把学到的知识应用到实际项目中,构建一个真正有用的研究 Agent。这个 Agent 可以:

  1. 接收用户的研究主题
  2. 判断是否需要搜索信息
  3. 调用搜索工具获取信息
  4. 总结研究结果

5.1 准备工作

首先安装必要的依赖:

pip install langgraph langchain langchain-openai tavily-python python-dotenv

然后设置环境变量:

import os
from dotenv import load_dotenv
load_dotenv()

os.environ["OPENAI_API_KEY"] = "your-api-key"
os.environ["TAVILY_API_KEY"] = "your-tavily-key"

5.2 定义状态和工具

from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import ToolNode
import operator
from tavily import TavilyClient

tavily = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])

@tool
def search(query: str) -> str:
    """搜索互联网获取最新信息"""
    result = tavily.search(query=query, max_results=3)
    return "\n\n".join([
        f"来源: {r['title']}\n{r['content']}"
        for r in result['results']
    ])

tools = [search]
tool_node = ToolNode(tools)

model = ChatOpenAI(model="gpt-4o", temperature=0)
model = model.bind_tools(tools)

class ResearchState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]

5.3 定义节点和路由

def should_continue(state: ResearchState) -> str:
    """决定是否需要继续搜索"""
    messages = state["messages"]
    last_message = messages[-1]
    
    # 如果 LLM 调用了工具,就继续执行工具
    if last_message.tool_calls:
        return "tools"
    # 否则结束
    return END

def call_model(state: ResearchState):
    """调用 LLM"""
    messages = state["messages"]
    response = model.invoke(messages)
    return {"messages": [response]}

5.4 构建和运行图

from langgraph.graph import StateGraph, END

# 构建图
workflow = StateGraph(ResearchState)

workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)

workflow.set_entry_point("agent")

workflow.add_conditional_edges(
    "agent",
    should_continue,
    {
        "tools": "tools",
        END: END
    }
)

workflow.add_edge("tools", "agent")

app = workflow.compile()

# 运行
inputs = {
    "messages": [
        HumanMessage(content="研究 2026 年最新的 AI Agent 框架发展趋势")
    ]
}

result = app.invoke(inputs)
print(result["messages"][-1].content)

恭喜!你已经构建了一个功能完整的研究 Agent。这个 Agent 会自主判断是否需要搜索,需要搜索时会调用搜索工具,然后基于搜索结果生成最终答案。


六、实战二:多 Agent 协作系统

单个 Agent 的能力是有限的,真正的威力来自于多个专业 Agent 的协作。让我们构建一个由三个 Agent 组成的团队:

多 Agent 协作工作流

  • Supervisor Agent:负责任务分发和结果协调
  • Research Agent:负责信息检索和事实核查
  • Code Agent:负责代码生成和执行
  • Review Agent:负责代码审查和质量检查

6.1 定义共享状态

class MultiAgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    next: str  # 下一个要执行的 Agent
    research_result: str
    code_output: str
    review_feedback: str
    iteration: int

6.2 定义各个 Agent

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# Supervisor Agent
supervisor_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是团队的协调者。你的任务是:
    1. 分析用户的需求
    2. 决定下一步由哪个 Agent 执行
    3. 整合各个 Agent 的输出,判断任务是否完成
    
    可用的 Agent:
    - Researcher: 需要获取外部信息时调用
    - Coder: 需要编写或执行代码时调用
    - Reviewer: 需要审查代码质量时调用
    - FINISH: 任务已经完成
    
    只返回下一个要执行的 Agent 名称。"""),
    MessagesPlaceholder(variable_name="messages"),
])

supervisor_chain = supervisor_prompt | ChatOpenAI(model="gpt-4o")

# Research Agent
researcher_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是研究专家。使用搜索工具获取最新、最准确的信息。"),
    MessagesPlaceholder(variable_name="messages"),
])
researcher_chain = researcher_prompt | ChatOpenAI(model="gpt-4o").bind_tools(tools)

# Code Agent
coder_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是资深开发工程师。编写清晰、可运行、经过测试的代码。"),
    MessagesPlaceholder(variable_name="messages"),
])
coder_chain = coder_prompt | ChatOpenAI(model="gpt-4o")

# Review Agent
reviewer_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是代码审查专家。检查代码的:
    1. 正确性:是否有 bug?
    2. 安全性:是否有安全漏洞?
    3. 可读性:代码是否清晰?
    4. 最佳实践:是否遵循编码规范?
    
    提供具体的改进建议。"""),
    MessagesPlaceholder(variable_name="messages"),
])
reviewer_chain = reviewer_prompt | ChatOpenAI(model="gpt-4o")

6.3 实现节点函数

def supervisor_node(state: MultiAgentState):
    result = supervisor_chain.invoke(state)
    return {
        "messages": [result],
        "next": result.content.strip(),
        "iteration": state.get("iteration", 0) + 1
    }

def researcher_node(state: MultiAgentState):
    result = researcher_chain.invoke(state)
    return {
        "messages": [result],
        "research_result": result.content,
        "next": "supervisor"
    }

def coder_node(state: MultiAgentState):
    result = coder_chain.invoke(state)
    return {
        "messages": [result],
        "code_output": result.content,
        "next": "reviewer"
    }

def reviewer_node(state: MultiAgentState):
    result = reviewer_chain.invoke(state)
    return {
        "messages": [result],
        "review_feedback": result.content,
        "next": "supervisor"
    }

(第二部分完,约2500字)


七、高级特性:Checkpointer 与 Human-in-the-loop

现在让我们探索 LangGraph 最强大的企业级特性:状态持久化和人工干预。这些特性是将 Agent 从 Demo 级别提升到生产级别的关键。

7.1 Checkpointer:状态持久化

Checkpointer 是 LangGraph 的状态持久化机制。它可以在图执行的每个步骤后自动保存状态,让你能够:

  • 中断后从断点继续执行
  • 回溯到历史任意状态重新运行(时间旅行)
  • 查看执行历史,进行调试和审计
  • 实现长时间运行的工作流

LangGraph 提供了多种 Checkpointer 实现:

# 内存 Checkpointer(开发测试用)
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()

# SQLite Checkpointer(生产环境常用)
from langgraph.checkpoint.sqlite import SqliteSaver
sqlite_saver = SqliteSaver.from_conn_string("checkpoints.sqlite")

# Postgres Checkpointer(企业级)
from langgraph.checkpoint.postgres import PostgresSaver
# 需要 psycopg2

使用 Checkpointer 极其简单,只需要在编译时传入:

app = workflow.compile(checkpointer=memory)

现在你需要为每次对话提供一个 thread_id 来标识不同的对话线程:

config = {"configurable": {"thread_id": "conversation-123"}}

# 第一次对话
result = app.invoke(
    {"messages": [HumanMessage(content="帮我写一个快速排序")]},
    config=config
)

# 可以中断,然后继续对话
result = app.invoke(
    {"messages": [HumanMessage(content="解释一下时间复杂度")]},
    config=config
)

同一个 thread_id 下的所有调用都会共享同一个状态。这就是实现多轮对话的正确方式——不再需要手动管理对话历史!

7.2 Human-in-the-loop:人工干预

在很多企业场景中,完全自动化的 Agent 是不可接受的。你需要在关键步骤让人来确认和干预。LangGraph 的 Human-in-the-loop 功能让这变得非常简单。

首先,定义哪些节点需要人工确认:

workflow = StateGraph(AgentState)
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)

workflow.set_entry_point("agent")
workflow.add_conditional_edges("agent", should_continue)
workflow.add_edge("tools", "agent")

# 关键:在 tools 节点前添加中断点
app = workflow.compile(
    checkpointer=memory,
    interrupt_before=["tools"]  # 在执行 tools 前暂停
)

现在当 Agent 决定调用工具时,执行会暂停,等待人工确认:

config = {"configurable": {"thread_id": "human-in-loop-1"}}

# 第一次调用,执行到 tools 前暂停
result = app.invoke(inputs, config=config)

# 检查当前状态
current_state = app.get_state(config)
print("下一个要执行的节点:", current_state.next)
# 下一个要执行的节点: ('tools',)

# 人工检查工具调用是否合理
last_message = current_state.values["messages"][-1]
print("准备调用工具:", last_message.tool_calls)

# 如果确认没问题,继续执行
result = app.invoke(None, config=config)  # 传入 None 表示继续

你甚至可以在继续执行前修改状态:

# 修改工具调用参数
updated_tool_calls = modify_tool_calls(last_message.tool_calls)

# 更新状态
app.update_state(
    config,
    {"messages": [AIMessage(tool_calls=updated_tool_calls)]}
)

# 然后继续执行
result = app.invoke(None, config=config)

这种机制在涉及敏感操作(如数据库写入、API 调用、代码执行)时尤为重要。你可以让 Agent 做所有的准备工作,然后由人工做最终的确认。


八、性能优化与最佳实践

随着你的 Agent 应用越来越复杂,性能和可靠性会成为关键问题。这里是我在实际项目中总结的最佳实践。

8.1 流式输出

对于用户-facing 的应用,流式输出是必须的。没有人愿意等待 30 秒才看到回答:

# 流式输出消息
for chunk in app.stream(inputs, config):
    for node_name, output in chunk.items():
        if "messages" in output:
            for msg in output["messages"]:
                print(f"[{node_name}]: {msg.content}")

# Token 级别的流式输出
async for event in app.astream_events(inputs, version="v1"):
    if event["event"] == "on_chat_model_stream":
        token = event["data"]["chunk"].content
        print(token, end="", flush=True)

8.2 超时和重试

LLM 调用经常会超时或失败,一定要添加重试机制:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def call_model_with_retry(state):
    return model.invoke(state["messages"])

8.3 并发执行

如果你的图中有多个独立的节点,可以并发执行它们:

from langgraph.constants import Send

def parallel_tasks(state):
    """并发执行多个任务"""
    return [
        Send("research", {"query": state["topic"]}),
        Send("code_generation", {"spec": state["spec"]}),
        Send("review", {"draft": state["draft"]})
    ]

workflow.add_conditional_edges("start", parallel_tasks)

8.4 调试技巧

调试 LangGraph 应用的最佳工具是 get_stateget_state_history

# 查看当前状态
state = app.get_state(config)
print("当前状态:", state.values)
print("下一个节点:", state.next)

# 查看完整历史
for history in app.get_state_history(config):
    print(f"步骤 {history.config['configurable']['checkpoint_ns']}:")
    print(f"状态: {history.values}")

8.5 监控和可观测性

生产环境中,你需要监控 Agent 的执行情况。LangGraph 原生集成了 LangSmith:

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "your-api-key"

这样每次调用都会被记录到 LangSmith,你可以查看完整的执行轨迹、Token 消耗、延迟等指标。


九、完整项目:多 Agent 代码审查系统

现在让我们把所有知识整合起来,构建一个完整的、可以投入生产使用的多 Agent 代码审查系统。

9.1 系统架构

我们的代码审查系统包含四个核心组件:

  1. Analyzer Agent:分析代码变更,识别需要重点审查的区域
  2. Security Agent:专门检查安全漏洞
  3. Style Agent:检查代码风格和最佳实践
  4. Summarizer Agent:整合所有反馈,生成最终审查报告

9.2 完整实现

from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.sqlite import SqliteSaver
import operator
import os

# 状态定义
class CodeReviewState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    code: str
    analysis_result: str
    security_findings: str
    style_findings: str
    final_report: str
    review_completed: bool

# 初始化模型
model = ChatOpenAI(model="gpt-4o", temperature=0)
checkpointer = SqliteSaver.from_conn_string("code_review.db")

# 各个 Agent 的 Prompt
def create_analyzer_chain():
    from langchain_core.prompts import ChatPromptTemplate
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", """你是资深代码分析专家。请分析以下代码:
        1. 识别代码的主要功能
        2. 找出潜在的逻辑问题
        3. 标记复杂度较高的区域
        4. 建议可以优化的地方
        
        请提供详细、具体的分析。"""),
        ("human", "待分析代码:\n\n{code}")
    ])
    
    return prompt | model

def create_security_chain():
    from langchain_core.prompts import ChatPromptTemplate
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", """你是安全审计专家。检查代码中的安全漏洞:
        1. SQL 注入风险
        2. XSS 漏洞
        3. 认证和授权问题
        4. 敏感信息泄露
        5. 不安全的依赖
        
        每个发现都要说明风险等级和修复建议。"""),
        ("human", "待检查代码:\n\n{code}\n\n分析结果:\n{analysis}")
    ])
    
    return prompt | model

def create_style_chain():
    from langchain_core.prompts import ChatPromptTemplate
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", """你是代码风格审查专家。检查:
        1. 是否符合 PEP 8 规范
        2. 命名是否清晰
        3. 是否有适当的注释和文档
        4. 代码结构是否合理
        5. 可测试性
        
        提供具体的改进建议。"""),
        ("human", "待检查代码:\n\n{code}\n\n分析结果:\n{analysis}")
    ])
    
    return prompt | model

def create_summarizer_chain():
    from langchain_core.prompts import ChatPromptTemplate
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", """你是技术团队负责人。请整合所有审查结果,生成最终报告。
        报告结构:
        1. 总体评价
        2. 关键发现(按优先级排序)
        3. 具体修复建议
        4. 评分(0-100分)
        
        报告应该专业、清晰、可操作。"""),
        ("human", """代码分析:
        {analysis}
        
        安全检查:
        {security}
        
        风格检查:
        {style}""")
    ])
    
    return prompt | model

# 节点函数
def analyzer_node(state: CodeReviewState):
    chain = create_analyzer_chain()
    result = chain.invoke({"code": state["code"]})
    return {
        "messages": [result],
        "analysis_result": result.content
    }

def security_node(state: CodeReviewState):
    chain = create_security_chain()
    result = chain.invoke({
        "code": state["code"],
        "analysis": state["analysis_result"]
    })
    return {
        "messages": [result],
        "security_findings": result.content
    }

def style_node(state: CodeReviewState):
    chain = create_style_chain()
    result = chain.invoke({
        "code": state["code"],
        "analysis": state["analysis_result"]
    })
    return {
        "messages": [result],
        "style_findings": result.content
    }

def summarizer_node(state: CodeReviewState):
    chain = create_summarizer_chain()
    result = chain.invoke({
        "analysis": state["analysis_result"],
        "security": state["security_findings"],
        "style": state["style_findings"]
    })
    return {
        "messages": [result],
        "final_report": result.content,
        "review_completed": True
    }

# 构建图
def build_code_review_graph():
    workflow = StateGraph(CodeReviewState)
    
    # 添加节点
    workflow.add_node("analyzer", analyzer_node)
    workflow.add_node("security", security_node)
    workflow.add_node("style", style_node)
    workflow.add_node("summarizer", summarizer_node)
    
    # 设置入口
    workflow.set_entry_point("analyzer")
    
    # 添加边
    workflow.add_edge("analyzer", "security")
    workflow.add_edge("analyzer", "style")
    workflow.add_edge(["security", "style"], "summarizer")
    workflow.add_edge("summarizer", END)
    
    return workflow.compile(checkpointer=checkpointer)

# 使用示例
if __name__ == "__main__":
    app = build_code_review_graph()
    
    code_to_review = """
    def process_user_input(user_id, input_data):
        # 直接拼接 SQL,危险!
        query = f"SELECT * FROM users WHERE id = {user_id}"
        result = db.execute(query)
        
        # 没有输入验证
        user_input = input_data['content']
        
        # 执行用户提供的代码
        eval(user_input)
        
        return result
    """
    
    config = {"configurable": {"thread_id": "review-001"}}
    result = app.invoke({
        "code": code_to_review,
        "review_completed": False
    }, config=config)
    
    print("=" * 60)
    print("代码审查报告")
    print("=" * 60)
    print(result["final_report"])

这个系统有几个亮点:

  1. 并行执行:Security Agent 和 Style Agent 可以并行运行,节省时间
  2. 状态持久化:所有审查历史都保存在 SQLite 中,可以随时查看
  3. 模块化设计:每个 Agent 都是独立的,可以单独测试和优化
  4. 可扩展性:很容易添加新的审查 Agent(如性能审查、兼容性审查等)

总结

在这篇文章中,我们从设计理念到实战项目,全面介绍了 LangGraph。让我们回顾一下核心要点:

核心概念

  • 状态:LangGraph 的灵魂,所有节点共享的全局数据
  • 节点:执行单元,接收状态、处理数据、返回更新
  • :控制流,定义执行顺序和分支逻辑
  • Reducer:状态合并机制,解决并发更新冲突

关键特性

  • 图结构:天然支持循环、分支、并行
  • Checkpointer:状态持久化,支持断点续跑和时间旅行
  • Human-in-the-loop:人工干预,企业级安全保障
  • 流式输出:良好的用户体验

最佳实践

  1. 从简单开始:先用 MessageGraph 构建基础 Agent,再逐步复杂化
  2. 重视状态设计:好的状态 Schema 是成功的一半
  3. 节点保持单一职责:每个节点只做一件事
  4. 总是使用 Checkpointer:即使现在不需要,以后也会需要
  5. 添加重试和超时:LLM 调用是不可靠的
  6. 集成可观测性:LangSmith 是调试和优化的必备工具

未来展望

LangGraph 仍然在快速发展中。未来我们可能会看到:

  • 更强大的并行执行和分布式支持
  • 更多的内置节点类型和工具集成
  • 可视化的图编辑器和调试工具
  • 性能优化,支持更大规模的图
  • 与更多 LLM 提供商的深度集成

Agent 技术正处于爆发前夜,而 LangGraph 正是这个新时代最强大的武器之一。掌握 LangGraph,你就掌握了构建下一代 AI 应用的钥匙。

现在,是时候开始你自己的 LangGraph 之旅了。从一个简单的 Agent 开始,逐步添加功能,探索更多可能性。记住,最好的学习方式就是动手实践。

(全文完,约7200字)