10_LangGraph快速入门与底层剖析

291 阅读8分钟

什么是LangGraph?

LangGraph(🦜🕸️)是LangChain团队开发的一个强大工具,它让我们能够以图的方式构建语言代理。简单来说,它就像是给AI搭建的一个"思维导图",让AI能够按照我们设计的路径进行思考和决策。

与传统的线性工作流相比,LangGraph最大的特点是支持循环分支,这意味着AI可以:

  • 反复思考同一个问题直到解决它
  • 根据不同情况选择不同的行动路径
  • 在需要时回到之前的步骤重新考虑

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

LangGraph工作流示例

LangGraph的核心优势

LangGraph相比其他LLM框架有三大核心优势:

  1. 循环能力:大多数代理架构都需要循环(比如思考-行动-观察的循环),LangGraph让这变得简单
  2. 精细控制:作为底层框架,它提供了对应用流程和状态的精确控制权
  3. 内置持久性:可以在任何时候保存和恢复执行状态,实现高级的"人机交互"和记忆功能

LangGraph的核心概念

1. 状态(State)

状态是LangGraph的核心概念,它就像是代理的"记忆"或"工作区":

  • 作用:保存计算过程中的所有上下文信息
  • 特点:随着代理思考和行动不断更新
  • 示例:对话历史、收集到的信息、中间计算结果等

2. 节点(Node)

节点代表计算过程中的一个步骤或行动:

  • 作用:执行特定任务,如思考、搜索信息、调用工具等
  • 特点:接收当前状态,执行操作,然后更新状态
  • 示例:代理思考节点、工具调用节点、人类反馈节点等

3. 边(Edge)

边连接不同的节点,定义计算流程:

  • 作用:决定下一步该执行哪个节点
  • 类型:普通边(固定路径)和条件边(根据状态动态选择路径)
  • 特点:支持循环和分支逻辑,实现复杂决策流程

LangGraph的主要功能

  1. 循环和分支:在应用中实现循环和条件语句,让AI能够反复思考或根据情况选择不同路径

  2. 持久性:在图中的每个步骤后自动保存状态,支持:

    • 错误恢复:出错时可以从上一个正确状态恢复
    • 暂停和恢复:可以随时暂停执行,稍后再继续
    • 时间旅行:可以回到之前的任何状态,尝试不同的决策路径
  3. 人机交互:可以在关键决策点暂停执行,让人类审核或修改AI的计划

  4. 流式输出:支持在每个节点产生输出时进行流式传输,包括令牌级的流式输出

  5. 与LangChain集成:与LangChain和LangSmith无缝集成,但不强制依赖它们

安装LangGraph

pip install langgraph

如果你想使用LangSmith进行可视化和调试(推荐但不是必须的):

pip install langsmith

LangGraph实战:构建一个具有搜索能力的代理

下面我们将构建一个简单但功能完整的代理,它能够:

  1. 理解用户问题
  2. 决定是直接回答还是使用搜索工具
  3. 调用搜索工具获取信息
  4. 基于搜索结果生成最终答案

步骤1:设置环境和导入依赖

import os
from typing import List, Tuple, Dict, Any
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from langgraph.graph import StateGraph, END
from langgraph.checkpoint import MemorySaver
from langgraph.prebuilt import MessagesState

# 设置API密钥
os.environ["OPENAI_API_KEY"] = "你的OpenAI API密钥"
os.environ["TAVILY_API_KEY"] = "你的Tavily API密钥"  # 用于搜索工具

步骤2:定义搜索工具

# 创建搜索工具
search_tool = TavilySearchResults(max_results=3)

步骤3:创建语言模型

# 创建支持工具调用的语言模型
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
llm_with_tools = llm.bind_tools([search_tool])

步骤4:定义代理节点函数

# 代理节点 - 决定下一步行动
def agent(state: MessagesState) -> MessagesState:
    """代理决策节点,决定是直接回答还是使用工具"""
    # 获取消息历史
    messages = state.messages
    
    # 添加系统提示,指导代理如何工作
    if not any(isinstance(msg, SystemMessage) for msg in messages):
        messages = [
            SystemMessage(content="你是一个有用的AI助手。当用户提出问题时,如果你知道答案,就直接回答。"
                         "如果你不确定或需要最新信息,使用搜索工具查找相关信息。")
        ] + messages
    
    # 调用语言模型获取回复
    response = llm_with_tools.invoke(messages)
    
    # 返回更新后的状态
    return MessagesState(messages=messages + [response])

步骤5:定义工具调用节点函数

# 工具调用节点 - 执行工具调用并返回结果
def tools(state: MessagesState) -> MessagesState:
    """执行工具调用并将结果添加到消息历史"""
    # 获取最后一条AI消息
    messages = state.messages
    last_message = messages[-1]
    
    if not isinstance(last_message, AIMessage) or not last_message.tool_calls:
        # 如果没有工具调用,直接返回原状态
        return state
    
    # 处理所有工具调用
    new_messages = []
    for tool_call in last_message.tool_calls:
        # 获取工具调用信息
        tool_name = tool_call.name
        tool_args = tool_call.args
        
        # 执行工具调用
        if tool_name == "tavily_search_results_json":
            # 调用搜索工具
            search_results = search_tool.invoke(tool_args.get("query", ""))
            # 创建工具响应消息
            new_messages.append(
                AIMessage(
                    content="",
                    tool_call_id=tool_call.id,
                    tool_calls=[],
                    additional_kwargs={"tool_responses": [{"content": str(search_results)}]}
                )
            )
    
    # 返回更新后的状态
    return MessagesState(messages=messages + new_messages)

步骤6:定义条件路由函数

# 条件路由函数 - 决定下一步去向
def should_continue(state: MessagesState) -> str:
    """决定是继续使用工具还是结束执行"""
    # 获取最后一条AI消息
    messages = state.messages
    last_message = messages[-1]
    
    # 检查是否有未完成的工具调用
    if isinstance(last_message, AIMessage) and last_message.tool_calls:
        return "tools"  # 继续执行工具
    else:
        return END  # 结束执行

步骤7:构建和编译图

# 创建状态图
graph = StateGraph(MessagesState)

# 添加节点
graph.add_node("agent", agent)
graph.add_node("tools", tools)

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

# 添加边 - 定义执行流程
graph.add_conditional_edges("agent", should_continue)  # 条件边:根据agent输出决定下一步
graph.add_edge("tools", "agent")  # 工具执行后总是返回到代理

# 编译图
memory_saver = MemorySaver()  # 创建内存保存器
chain = graph.compile(checkpointer=memory_saver)  # 编译图为可执行链

步骤8:执行图并与代理交互

# 创建一个新的对话线程
thread_id = "thread_1"

# 发送第一个问题
response = chain.invoke(
    {"messages": [HumanMessage(content="太阳能电池板是什么?它们如何工作?")]},
    {"configurable": {"thread_id": thread_id}}
)

# 输出代理回复
print("代理回复:")
print(response.messages[-1].content)

# 继续对话 - 发送后续问题
response = chain.invoke(
    {"messages": [HumanMessage(content="太阳能电池板的安装成本大约是多少?")]},
    {"configurable": {"thread_id": thread_id}}
)

# 输出代理回复
print("\n代理回复:")
print(response.messages[-1].content)

LangGraph执行流程详解

当我们运行上面的代码时,LangGraph会按照以下流程执行:

  1. 初始化状态

    • 创建一个新的MessagesState对象,包含用户的初始消息
  2. 进入入口点节点

    • 执行"agent"节点
    • 语言模型分析用户问题并决定下一步行动
  3. 条件路由

    • 如果语言模型决定使用工具(生成了tool_calls):
      • 执行"tools"节点
      • 调用相应的工具(如搜索)
      • 将工具结果添加到状态
      • 返回到"agent"节点
    • 如果语言模型直接回答(没有tool_calls):
      • 执行结束,返回最终状态
  4. 循环执行

    • 代理可能会多次决定使用工具
    • 每次工具执行后都会返回到代理进行下一步决策
    • 直到代理决定不再需要工具,生成最终回答
  5. 返回结果

    • 返回包含完整对话历史的最终状态

LangGraph的高级功能

1. 状态持久化

LangGraph允许在执行过程中保存状态,这对于长时间运行的代理特别有用:

# 使用文件系统保存状态
from langgraph.checkpoint import FileSystemCheckpointer
fs_checkpointer = FileSystemCheckpointer(path="./checkpoints")
chain = graph.compile(checkpointer=fs_checkpointer)

2. 人机协作工作流

可以在关键决策点暂停执行,让人类审核或修改代理的计划:

# 定义人类反馈节点
def human_feedback(state: MessagesState) -> MessagesState:
    """获取人类反馈"""
    messages = state.messages
    last_message = messages[-1]
    
    # 显示AI的计划并获取人类反馈
    print("AI计划:", last_message.content)
    feedback = input("请提供反馈或按Enter继续: ")
    
    if feedback:
        # 添加人类反馈到消息历史
        return MessagesState(messages=messages + [HumanMessage(content=feedback)])
    return state

# 将人类反馈节点添加到图中
graph.add_node("human_feedback", human_feedback)
graph.add_edge("agent", "human_feedback")
graph.add_edge("human_feedback", "agent")

3. 多代理协作

LangGraph支持构建多个代理之间的协作系统:

# 定义不同角色的代理
def researcher(state: MessagesState) -> MessagesState:
    """研究者代理 - 负责收集信息"""
    # 实现研究者逻辑
    ...

def critic(state: MessagesState) -> MessagesState:
    """批评者代理 - 负责评估信息质量"""
    # 实现批评者逻辑
    ...

def writer(state: MessagesState) -> MessagesState:
    """撰写者代理 - 负责生成最终内容"""
    # 实现撰写者逻辑
    ...

# 将多个代理添加到图中并定义它们之间的协作流程
graph.add_node("researcher", researcher)
graph.add_node("critic", critic)
graph.add_node("writer", writer)

# 定义协作流程
graph.add_edge("researcher", "critic")
graph.add_edge("critic", "writer")

进阶:自定义状态类型

除了使用预构建的MessagesState,我们还可以定义自己的状态类型:

from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional

# 定义自定义状态类
class CustomAgentState(BaseModel):
    """自定义代理状态"""
    messages: List[Any] = Field(default_factory=list)
    collected_data: Dict[str, Any] = Field(default_factory=dict)
    current_step: str = "initial"
    
    class Config:
        arbitrary_types_allowed = True

# 使用自定义状态创建图
custom_graph = StateGraph(CustomAgentState)

总结

LangGraph是一个强大的工具,它通过图的方式让我们能够构建复杂的AI代理系统。它的核心优势在于:

  1. 支持循环和分支:让AI能够反复思考和根据情况选择不同路径
  2. 状态管理:维护代理的"记忆",实现复杂的推理过程
  3. 灵活的控制流:通过节点和边定义精确的执行流程
  4. 内置持久性:支持状态保存和恢复,实现高级交互功能

通过LangGraph,我们可以构建比简单问答更复杂的AI应用,如:

  • 多步骤推理代理
  • 自主研究助手
  • 多代理协作系统
  • 人机协作工作流

LangGraph不仅扩展了LangChain的能力,还为构建下一代AI应用提供了坚实的基础。