一、简介

本系列为Langgraph文章,最终以实现企业级项目。

在这里插入图片描述

该系列文章,以官方文档路径撰写,深入浅出并配以自己理解,配以GIF动图演示、适当扩展延伸官方案例以及源码讲解
当然如果你需要你也可以查看官方文档

最终实战项目目标:构建一个Agents Framework(智能代理框架) 多智能体协作企业系统

  • 📊 Agent-Graph:每个业务 Agent 以状态图形式编排节点、条件边与循环边;
  • 🔧 工具体系:自动发现与注册工具,支持函数调用(tool calling),可扩展MCP服务;
  • 🗄️ 记忆/持久化:使用 Postgres 作为 LangGraph 的 checkpointer 与 store,Redis缓存prompt;
  • 📋 统一注册中心:自动发现、注册并预编译 Agent 图与工具;
  • 💪 滚动窗口摘要算法压缩上下文,用户画像,短期记忆,长期记忆混合
  • 🌐 API 网关:FastAPI 路由聚合,提供通用 chat、agents、session 等接口;
  • 🔄 可插拔 LLM:通过模型工厂与配置驱动,统一管理多厂商 LLM。
  • 🌀 prompt缓存:redis加载prompt,prompt-web热更新管理
  • 👁️‍🗨️ RAG 向量数据库,与工程结合,结构化,非结构化管理检索,召回
  • 🥰 下一步引导功能,猜你想要功能

在这里插入图片描述
请添加图片描述

本系列文章,配套项目源码地址:
https://github.com/wenwenc9/langgraph-tutorial-wenwenc9

langchain的系列文章(相信我把Langchain全部学一遍,你能深入理解AI的开发)
01|LangChain | 从入门到实战-介绍
02|LangChain | 从入门到实战 -六大组件之Models IO
03|LangChain | 从入门到实战 -六大组件之Retrival
04|LangChain | 从入门到实战 -六大组件之Chain
05|LangChain | 从入门到实战 -六大组件之Memory
06|LangChain | 从入门到实战 -六大组件之Agent

二、LangGraph基础

1、什么是LangGraph?

LangGraph是一个基于图的高级编排框架,专门用于构建AI代理。它建立在LangChain之上,通过状态图来管理代理之间的交互,相比传统的线性工作流提供了更强大的功能。

官方地址:https://github.com/langchain-ai/langgraph

核心特点

  1. 图形化架构:使用有向图表示代理工作流,支持循环和条件分支
  2. 内置持久化:自动管理状态持久化和工作流进度保存
  3. 循环处理:支持代理与工具的重复交互,创建反馈循环
  4. 人机集成:内置人工介入操作支持,允许专家审查和干预
  5. 状态管理:跨所有组件维护共享状态,实现无缝通信

与LangChain的区别

  • LangChain:提供基础功能,适合简单的线性工作流,需要手动设置内存、持久化等
  • LangGraph:提供更复杂的能力,支持状态管理、循环工作流和人工监督的开箱即用功能

关键组件

  1. 节点(Nodes):执行特定任务的处理单元
  2. 边(Edges):节点之间的连接和关系,决定信息流向
  3. 状态管理(State Management):维护代理操作的当前上下文和进度

白话文:想象一下,给你一张白纸,你可以创建很多点Nodes(工具/agent…)都可以视作点,然后用边连线Edges(完成业务逻辑),其中点与点之间的交流是是通过State来沟通的

2、快速入门

对应代码位置

2.1 创建一个Agent代理

  • langgraph.prebuilt 公开了一个更高级别的 API,用于创建和执行代理和工具。
  • 下面这个案例,使用的是豆包的模型

你也可以尝试使用这个方法也行:https://python.langchain.com/docs/integrations/chat/volcengine_maas/

from langgraph.prebuilt import create_react_agent

def get_weather(city: str) -> str:
    """Get weather for a given city."""
    return f"关于{city},的天气一直很好!"

agent = create_react_agent(
    model=f"openai:{modelName}", # 目前没有豆包的,但是使用openai的格式是可以调通的 
    tools=[get_weather],
    prompt="You are a helpful assistant",
)

res = agent.invoke(
    {"messages": [{"role": "user", "content": "北京的天气如何?"}]}
)
rich.print(res)

输出如下
在这里插入图片描述

2.2 自定义Agent

有时候想要调整模型的温度,相对于前面传入字符串,这里也可以传入model实例,在构建实例的时候
就可以指定如temperature 模型温度进行操作

from langchain.chat_models import init_chat_model
from langgraph.prebuilt import create_react_agent

# 通过这个方法可以设置模型温度等操作
model = init_chat_model(
    f"openai:{modelName}",
    temperature=0
)

# create_react_agent 不仅可以传入字符串,现在也可以传入模型实例
agent = create_react_agent(
    model=model,
    tools=[get_weather],
)

2.3 为代理加上prompt

2.3.1 静态pormpt

prompt=strprompt不可变

from langgraph.prebuilt import create_react_agent

agent = create_react_agent(
    model=f"openai:{modelName}",
    tools=[get_weather],
    prompt="你是一个智能助手,拒绝回复用户天气相关的问题"
)
res = agent.invoke(
    {"messages": [{"role": "user", "content": "深圳的天气如何?"}]}
)
rich.print(res)

输出
在这里插入图片描述

2.3.2 动态pormpt

这里prompt=func即可做到

动态prompt允许我们根据运行时的状态和配置动态生成系统提示词,而不是使用固定的静态文本。

主要优势:

  1. 个性化交互:可以根据用户信息(如用户名、偏好设置等)动态调整提示词
  2. 上下文感知:能够根据当前对话状态、历史消息等信息调整AI的行为
  3. 灵活配置:通过RunnableConfig传递配置参数,无需修改代码就能改变行为
  4. 多租户支持:在同一个agent实例中为不同用户提供不同的服务体验

使用场景举例:

  • 根据用户VIP等级调整服务态度和详细程度
  • 根据用户所在地区调整语言和文化习惯
  • 根据业务规则动态添加或移除某些限制
  • 在多用户系统中为每个用户提供个性化的助手
from langchain_core.messages import AnyMessage
from langchain_core.runnables import RunnableConfig
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.prebuilt import create_react_agent

def prompt(state: AgentState, config: RunnableConfig) -> list[AnyMessage]:
    user_name = config["configurable"].get("user_name")
    system_msg = f"你是一个智能助手。请称呼用户为{user_name}。"
    return [{"role": "system", "content": system_msg}] + state["messages"]

agent = create_react_agent(
    model=f"openai:{modelName}",
    tools=[get_weather],
    prompt=prompt
)

res = agent.invoke(
    {"messages": [{"role": "user", "content": "上海的天气怎么样"}]},
    config={"configurable": {"user_name": "张先生","token":"wenwenc9"}}
)

在这里插入图片描述
实战案例:根据VIP等级提供不同服务

RunnableConfig 这个方法为透传方法,没有必要纠结,就是一个代理运行时候全局贯串,可以取很多内容

from langchain_core.messages import AnyMessage
from langchain_core.runnables import RunnableConfig
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.prebuilt import create_react_agent

def vip_prompt(state: AgentState, config: RunnableConfig) -> list[AnyMessage]:
    """根据用户VIP等级动态调整服务质量"""
    user_name = config["configurable"].get("user_name", "用户")
    vip_level = config["configurable"].get("vip_level", 0)
    
    if vip_level >= 3:
        system_msg = f"你是一位专业的高级顾问。请以最尊敬和详细的方式为尊贵的{user_name}提供服务,提供深度分析和建议。"
    elif vip_level >= 1:
        system_msg = f"你是一位友好的助手。请为{user_name}提供周到的服务和详细的信息。"
    else:
        system_msg = f"你是一个智能助手。请简洁地回答{user_name}的问题。"
    
    return [{"role": "system", "content": system_msg}] + state["messages"]

# 创建使用动态prompt的agent
vip_agent = create_react_agent(
    model=f"openai:{modelName}",
    tools=[get_weather],
    prompt=vip_prompt
)

# 测试不同VIP等级的服务体验
print("=== 普通用户 (VIP 0) ===")
result1 = vip_agent.invoke(
    {"messages": [{"role": "user", "content": "广州的天气如何?"}]},
    config={"configurable": {"user_name": "普通用户", "vip_level": 0}}
)
print(result1["messages"][-1].content)

print("\n=== VIP用户 (VIP 3) ===")
result2 = vip_agent.invoke(
    {"messages": [{"role": "user", "content": "广州的天气如何?"}]},
    config={"configurable": {"user_name": "李总", "vip_level": 3}}
)
print(result2["messages"][-1].content)

打印
在这里插入图片描述

2.4 为Agent添加记忆

为了实现长短期期记忆,langgraph提供了很多组件,这里使用内存作为存储聊天

from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver() # 内存节点

agent = create_react_agent(
    model=f"openai:{modelName}",
    tools=[get_weather],
    checkpointer=checkpointer # 指定为内存节点 ,创建图的时候就要声明
)

# Run the agent
config = {"configurable": {"thread_id": "1"}} # thread_id 可以理解为本轮对话的id
agent.invoke(
    {"messages": [{"role": "user", "content": "深圳的天气怎么样?"}]},
    config
)

agent.invoke(
    {"messages": [{"role": "user", "content": "我前面问题是什么?"}]},
    config
)

输出
在这里插入图片描述

2.5 结构化输出

通常来说,模型的输出内容是固定,但是有时候我们想要输出指定的json,以便于我们处理

这里要强调一下,部分模型不支持结构化输出,就用不了下面的代码

你可以浏览一下官方声明常见模型的清单
https://python.langchain.com/docs/integrations/chat/

也可以到对应的厂商说明文档查看,比如我用的是豆包,可以看到不支持

https://console.volcengine.com/ark/region:ark+cn-beijing/model/detail?Id=doubao-1-5-pro-256k
在这里插入图片描述

官方代码如下,是跑不通的

from pydantic import BaseModel
from langgraph.prebuilt import create_react_agent

class WeatherResponse(BaseModel):
    conditions: str

agent = create_react_agent(
    model=model, # 用的豆包的,这个代码会出错
    tools=[get_weather],
    response_format=WeatherResponse
)

response = agent.invoke(
    {"messages": [{"role": "user", "content": "what is the weather in sf"}]}
)

response["structured_response"]

在这里插入图片描述

你可以这样做

实际上langchian_core 可以 作为链式调用时候,可以修正这个问题

from pydantic import BaseModel
import json
import re

class WeatherResponse(BaseModel):
    conditions: str

def extract_structured_response(agent_response, target_model: BaseModel):
    """从agent回复中提取结构化数据"""
    last_message = agent_response["messages"][-1].content

    # 方法1: 尝试直接JSON解析
    try:
        json_match = re.search(r'\{.*\}', last_message, re.DOTALL)
        if json_match:
            json_data = json.loads(json_match.group())
            return target_model(**json_data)
    except:
        pass

    # 方法2: 基于关键词提取
    if "conditions" in last_message.lower():
        # 简单的文本解析逻辑
        conditions = last_message.strip()
        return target_model(conditions=conditions)

    return None

# 使用普通的agent
agent = create_react_agent(
    model=f"openai:{modelName}",
    tools=[get_weather],
    prompt="你是个乐于助人的助手。在提供天气信息时,请将您的最终回复格式化为 JSON with 'conditions' field."
)

response = agent.invoke(
    {"messages": [{"role": "user", "content": "深圳今天天气如何?"}]}
)

# 后处理提取结构化数据
structured_response = extract_structured_response(response, WeatherResponse)
if structured_response:
    print("结构化输出:", structured_response)
else:
    print("提取失败,原始回复:", response["messages"][-1].content)

3、综合案例

我们将从一个 基本的聊天机器人 开始,并逐步添加更复杂的功能,在此过程中介绍关键的LangGraph概念。

  • ✅ 通过搜索网络 来 回答常见问题
  • ✅ 在调用之间保持对话状态
  • ✅ 将复杂查询 转发给人工进行审核
  • ✅ 使用自定义状态 来控制其行为
  • ✅ 回溯并探索 替代对话路径

3.1 构建一个基础聊天Agent

你可以这么理解,State是点与点之间沟通的载体,现在就是定义状态机

graph_builder则是我们的白纸,准备在上面画画

from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages


# 创建图的时候,第一步就是定义状态机
class State(TypedDict):
    # 消息的类型为 “list”。'add_messages' 函数
    # 定义如何更新此状态键
    # (在本例中,它将消息附加到列表中,而不是覆盖它们)就是每次人机聊天的请求响应都会在这个messages以add方式存储list
    messages: Annotated[list, add_messages]

graph_builder = StateGraph(State)

现在在这张白纸上面画一个点

from langchain.chat_models import init_chat_model
llm = init_chat_model(model=f"openai:{modelName}")

def chatbot(state: State):
    return {"messages": [llm.invoke(state["messages"])]}


# 为这个图添加一个可用节点
# 第一个参数为节点的名称,第二个参数是将在每次调用时使用的函数或对象。
graph_builder.add_node("chatbot", chatbot)

接下来指定开始节点为chatbot,并且chatbot执行完成后,直接到结束,workflow的绘制,跟coze跟dify概念相似

graph_builder.add_edge(START, "chatbot") # 等同于graph_builder.set_entry_point("chatbot")
graph_builder.add_edge("chatbot", END)

编译图使其实例化对象,并且查看我们刚构建的workflow

graph = graph_builder.compile()
from IPython.display import Image, display

display(Image(graph.get_graph().draw_mermaid_png()))  # 可能会超时,需要开启代理

在这里插入图片描述
现在我们来运行一下

for event in graph.stream({"messages": [{"role": "user", "content": "你是谁"}]}):
    for value in event.values():
        print("Assistant:", value["messages"][-1].content)

输出

Assistant: 我是豆包呀,能陪你畅快聊天,解答各种知识疑问,无论是生活琐事、科学难题还是文学艺术相关,都能和你探讨~

3.2 为Agent绑定工具

现在Agent没有使用任何,工具,我们为其创建一个工具

  • 学习内置工具,跟自定义创建工具
  • 学习内置条件路由,跟自定义条件路由
  • PS:很重要的一点,模型需要绑定工具,图也需要绑定工具
from langchain_core.tools import tool
########### 定义工具
@tool
def get_personal_info(name: str) -> dict:
    """
        通过名称获取个人信息
    :param name:
    :return:
    """
    personal_info = {
        "love": "打篮球",
        "age": "18"
    }
    return personal_info
######### 创建图
from langchain.chat_models import  init_chat_model
llm = init_chat_model(model=f"openai:{modelName}")
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

# 定义状态机
class State(TypedDict):
    messages: Annotated[list, add_messages]

graph_builder = StateGraph(State)

# 为模型创建工具
llm_with_tools = llm.bind_tools([get_personal_info])
def chatbot(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)

这里使用@tool来讲一个函数作为工具,bind_tools为模型进行绑定工具
现在这张画布上面,仅有chabot节点,我们需要在画布上添加一个工具节点

3.2.1 自定义图工具与路由

在这段代码,我们自定义了一个BasicToolNode,作为工具执行节点

import copy
graph_builder_1 = copy.deepcopy(graph_builder)
import json
from langchain_core.messages import ToolMessage
class BasicToolNode:
    def __init__(self, tools: list) -> None:
        self.tools_by_name = {tool.name: tool for tool in tools}

    def __call__(self, sate: State):
        if messages := sate.get("messages", []):
            message = messages[-1]
        else:
            raise ValueError("No message found in input")
        outputs = []
        for tool_call in message.tool_calls:
            tool_result = self.tools_by_name[tool_call["name"]].invoke(
                tool_call["args"]
            )
            outputs.append(
                ToolMessage(
                    content=json.dumps(tool_result),
                    name=tool_call["name"],
                    tool_call_id=tool_call["id"],
                )
            )
        return {"messages": outputs}
tool_node = BasicToolNode(tools=[get_personal_info])
graph_builder_1.add_node("tools", tool_node)

下面再自定义一个路由工具节点

def route_tools(
    state: State,
):
    if isinstance(state, list):
        ai_message = state[-1]
    elif messages := state.get("messages", []):
        ai_message = messages[-1]
    else:
        raise ValueError(f"No messages found in input state to tool_edge: {state}")
    if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
        return "tools"
    return END
# 创建条件路由
graph_builder_1.add_conditional_edges(
    "chatbot",
    route_tools,
    {"tools": "tools", END: END},
)
graph_builder_1.add_edge("tools", "chatbot")
graph_builder_1.add_edge(START, "chatbot")
graph_1 = graph_builder_1.compile()

我们绘图看看现在的情况,可以看到chatbot与tools有循环条件边

用户问题->由chatbot先执行,然后交给route_tools 判断状态机State中的消息载体,是否有生成工具调用

  • 有的话返回tools 这个字符串,代表要去工具节点
  • 没有则返回END结束回复
  • add_conditional_edges中第三个参数{"tools": "tools", END: END},作为路由,根据返回内容跳转到指定节点
    在这里插入图片描述

打开你的jupyter,进行debug,参考我的gif,打上3处debug,然后继续看我下文

在这里插入图片描述

我们可以切实体验一下从用户问题发起到结束,第一个进入断点,调试堆栈

我们发现是进入到了路由工具 route_tools,进入的时候,堆栈State为状态机其中有消息载体,一个是咱们输入的,一个是chatbot回复的,可以ai回复的内容有个tool_calls,并且content声明了需要调用何种工具

在这里插入图片描述
现在进入我们的第二个断点,直接F9,进入到BasicToolNode,这个时候我们来理解代码,前面chatbot生成要调用工具,路由工具指向了我们定义的工具节点BasicToolNode,通过状态机State来获取工具需要执行的参数传参tool_call["args"],使用invoke运行工具,最后返回ToolMessages,retrun的时候更新状态State中的messages,
在这里插入图片描述
在按F9,又回到了路由工具route_tools,此时我们来看看堆栈(其实可以在chatbot多加一个断点,方便最后一条载体理解)

在这里插入图片描述

3.2.2 内置节点与内置路由

langgraph官方提供了很多优秀的封装方法from langgraph.prebuilt import ToolNode方便我们快速操作,你可以查看这个链接
https://langchain-ai.github.io/langgraph/agents/overview/?h=prebuilt#package-ecosystem
在这里插入图片描述

使用内置方法为图创建工具节点

import copy
graph_builder_2 = copy.deepcopy(graph_builder)
from langgraph.prebuilt import ToolNode
tools = ToolNode([get_personal_info])
graph_builder_2.add_node("tools", tools)

使用内置路由方法

from langgraph.prebuilt import tools_condition
# 创建条件路由
graph_builder2.add_conditional_edges(
    "chatbot",
    tools_condition,
    {"tools": "tools", END: END},
)
graph_builder2.add_edge(START, "chatbot")
graph_builder2.add_edge("tools", "chatbot")
graph_2 = graph_builder2.compile()
3.2.3 两者区别

内置方法固然好,但适合简单操作,高度复杂自定义的话建议还是自定义,比如我们可以在自定义方法中添加日志系统

加上更多个性化化操作等等,send_writher流式回复的时候,发送我正在思考.....,该系列后面会讲到
在这里插入图片描述

3.3 添加记忆

先创建一个基础聊天机器人

from typing import Annotated
from langchain.chat_models import init_chat_model
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
class State(TypedDict):
    messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
llm = init_chat_model(model=f"openai:{modelName}")

def chatbot(state: State):
    return {"messages": [llm.invoke(state["messages"])]}

graph_builder.add_node("chatbot", chatbot)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)

使用checkpointer短期记忆作为Agent的记忆,并且使用内存记忆,当然也可以使用数据库,记忆分为

  • 长期记忆:跨线程,比如用户画像
  • 短期记忆:该线程会话上才文,新建聊天窗口

现在我们可以使用内存操作,执行需要在编译的时候compile(checkpointer=memory) 指定一下即可

  • pretty_print 是内置的方法,当为消息载体的时候可以美化打印
  • stream为流式回复的方法,invoke为同步回复的方法
  • 我们可以发现进行stream的时候传入了一个config,{"configurable": {"thread_id": "1"}} 这个就是线程,用于记住该窗口上下文
#### 添加内存操作
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
graph_by_in = graph_builder.compile(checkpointer=memory)

# 第一次发起询问
config = {"configurable": {"thread_id": "1"}}
events = graph_by_in.stream(
    {"messages": [{"role": "user", "content": "你好!我的名字叫稳稳"}]},
    config,
    stream_mode="values",
)
for event in events:
    event["messages"][-1].pretty_print()
    
# 第二次发起询问
events = graph_by_in.stream(
    {"messages": [{"role": "user", "content": "你还记得我的名字吗?"}]},
    config,
    stream_mode="values",
)
for event in events:
    event["messages"][-1].pretty_print()

我们看看效果
在这里插入图片描述

3.4 添加人机交互

代理可能不可靠,在某些情况下需要人工输入才能成功完成任务。
对于一些关键操作,可能希望在执行前获得人工批准以确保一切按预期运行,比如付款。

  • interrupt函数:在节点内暂停执行的关键函数
  • Command对象:用于恢复执行并传递人工输入的命令
  • 持久化状态:使用检查点支持无限期暂停和恢复
  • 人工监督:在关键决策点引入人工审查和控制

人工介入的设计模式:

  1. 批准或拒绝:在关键步骤前暂停以审查和批准操作
  2. 编辑图状态:暂停以审查和编辑图状态,纠正错误或添加信息
  3. 审查工具调用:在工具执行前审查和编辑LLM请求的工具调用
  4. 验证人工输入:在进行下一步之前验证人工输入

创建一个酒店机器人,当用户发起“酒店订阅”,进行人工交互,让用户输入信息后,从当前节点继续运行

  • interrupt 在节点中,其本质就是抛出异常,中断
  • command 具有跳转节点的作用
from langchain_core.tools import tool
from typing_extensions import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
# ps 非常重要的点,导入这2个参数
from langgraph.types import interrupt, Command
from langchain.chat_models import init_chat_model

llm = init_chat_model(model=f"openai:{modelName}")


@tool("用户订阅酒店工具")
def book_hotel(*args,query: str,**kwargs) -> str:
    """
    用于处理酒店预订相关的请求。
    """
    print("*"*10,"进入")

    human_response = interrupt({"message": "好的请你填写酒店表单信息", "form_data": {"name": "", "age": ""}})

    print("*"*10,"离开")

    if human_response:
        return f"尊敬的{human_response['form_data']['name']},已经为您订阅酒店!"
    else:
        return "用户取消了预订"

tools = [book_hotel]
llm = llm.bind_tools(tools)
tools_node = ToolNode(tools)  # 适用于图的节点

### 创建图
class State(TypedDict):
    messages: Annotated[list, add_messages]

def chatbot(state: State):
    # 添加系统提示
    system_prompt = """你是一个智能助手。你有以下工具可用:

    工具使用规则:
    1. "用户订阅酒店工具" - 仅在用户明确想要预订酒店、查询酒店或讨论住宿相关问题时使用
    2. 对于一般性问题(如科学知识、天气、计算等),请直接回答,不要调用工具
    3. 如果用户的问题与可用工具无关,请礼貌地直接回答

    请仔细判断是否需要使用工具。"""
    messages = [{"role": "system", "content": system_prompt}] + state["messages"]
    message = llm.invoke(messages)
    return {"messages": [message]}

graph_builder = StateGraph(State)
graph_builder.set_entry_point("chatbot")
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", tools_node)
graph_builder.add_conditional_edges("chatbot", tools_condition)
graph_builder.add_edge("tools", "chatbot")

from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()
graph_by_in = graph_builder.compile(checkpointer=memory)
from IPython.display import Image, display
display(Image(graph_by_in.get_graph().draw_mermaid_png()))  # 可能会超时,需要开启代理

让我们询问非酒店订阅的功能

user_input = "太阳的体积"
config = {"configurable": {"thread_id": "1"}}
# 使用stream模式以便检测中断
events = graph_by_in.stream(
    {"messages": [{"role": "user", "content": user_input}]},
    config,
    stream_mode="values"
)
# 遍历事件
for event in events:
    if "messages" in event:
        event["messages"][-1].pretty_print()
graph_state = graph_by_in.get_state(config)

输出如下
graph_by_in.get_state(config) 可以获取图的运行状态,我们可以看到其中有个interrupts为空,并且并没有中断

官方说明
https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.ThreadsClient.get_state
在这里插入图片描述

3.4.1 触发中断

现在,让我们询问酒店相关的问题,出现了中断
在这里插入图片描述

3.4.2 恢复中断

假定,我们有前端,完全可以使用该功能,AI识别用户需求,弹出表单,然后用户填写表单后提交,恢复继续流程,这里我们继续模拟
用户填写表单信息

# 用户填写表单
events = graph_by_in.stream(
    Command(resume={"form_data": {"name": "稳稳C9", "age": "18"}}),  # 传入用户填写的表单数据
    config,
    stream_mode="values"
)
# 处理恢复后的事件
for event in events:
    if "messages" in event:
        event["messages"][-1].pretty_print()
graph_state = graph_by_in.get_state(config)

在这里插入图片描述

3.4.1 中断抹除

运行这个代码的时候,请重新运行图编译,然后触发酒店中断,然后再执行下面代码

# 用户填写表单
events = graph_by_in.stream(
    {"messages": [{"role": "user", "content": "你是谁"}]},
    config,
    stream_mode="values"
)
# 处理恢复后的事件
for event in events:
    if "messages" in event:
        event["messages"][-1].pretty_print()
graph_state = graph_by_in.get_state(config)

注意,如果下一轮对话,没有使用command,恢复中断,此时图状态的interrupt会抹除

在这里插入图片描述

3.5 自定义状态机

在前面的代码中,我们的状态机State仅有一个messages

# 创建图的时候,第一步就是定义状态机
class State(TypedDict):
    # 消息的类型为 “list”。'add_messages' 函数
    # 定义如何更新此状态键
    # (在本例中,它将消息附加到列表中,而不是覆盖它们)就是每次人机聊天的请求响应都会在这个messages以add方式存储list
    messages: Annotated[list, add_messages]

状态机,为信息载体的主体对象,我们上面小节都是只有一个messages,实际上我们可以新增很多字段,系列最后企业级项目会以复杂的内容作为展现,现在我们开始了解掌握自定义状态机

在这里插入图片描述

现在我们构建一个案例:

在企业财务管理中,资金拨款需要严格的审批流程。本案例模拟一个智能财务审批系统,当用户申请拨款时,系统会暂停执行等待人工审批,确保资金安全。

  1. 如何在 LangGraph 中实现财务审批机制
  2. 理解 interrupt() 和 Command 的使用方法
  3. 掌握状态管理和工具调用的最佳实践
  4. 学会处理财务敏感操作的安全控制

整体流程:

用户申请 → 系统评估 → 暂停等待审批 → 人工审核/拒绝 → 执行拨款/拒绝
3.5.1 第一步创建自定义状态机
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
from typing import Annotated, Optional
class State(TypedDict):
    messages: Annotated[list, add_messages]  # 对话消息列表
    applicant_name: Optional[str]            # 申请人姓名
    amount: Optional[float]                  # 申请金额
    purpose: Optional[str]                   # 申请用途
    approval_status: Optional[str]           # 审批状态:pending/approved/rejected
    approver_name: Optional[str]    

包含核心的财务申请信息字段
approval_status 跟踪审批状态
简化的状态结构便于理解

3.5.2 第二步创建,评估财务拨款申请的合理性,工具
from langchain_core.tools import tool
@tool
def evaluate_funding_request(applicant_name: str,amount: float,purpose: str) -> str:
    """
    评估财务拨款申请的合理性
    Args:
        applicant_name: 申请人姓名
        amount: 申请金额
        purpose: 申请用途
    Returns:
        评估结果
    """
    # 简单的风险评估
    if amount > 100000:
        risk_level = "高风险"
        suggestion = "需要详细说明资金用途"
    elif amount > 50000:
        risk_level = "中风险"
        suggestion = "建议提供项目计划"
    else:
        risk_level = "低风险"
        suggestion = "申请材料完整"

    evaluation = f"""
    📋 申请评估报告
    申请人: {applicant_name}
    申请金额: ¥{amount:,.2f}
    申请用途: {purpose}

    🔍 风险评估: {risk_level}
    💡 建议: {suggestion}
    """
    return evaluation

实现简单的业务规则
提供结构化的评估报告
根据金额进行风险分级

3.5.3 第三步,创建审批工具

关键概念解释:

  • interrupt() 函数
    • 作用:暂停图执行,等待人工输入
    • 参数:结构化的审批数据
    • 返回值:人工提供的审批决策
  • Command(update=...)
    • 作用:在工具中直接更新图状态
    • 包含:ToolMessage 记录审批结果
from langchain_core.tools import InjectedToolCallId
from langgraph.types import interrupt, Command
from langchain_core.messages import ToolMessage
@tool
def financial_approval(applicant_name: str,amount: float,purpose: str,tool_call_id: Annotated[str, InjectedToolCallId]) -> str:
    """
    财务拨款审批工具

    Args:
        applicant_name: 申请人姓名
        amount: 申请金额,纯数字
        purpose: 申请用途
        tool_call_id: 工具调用ID(自动注入)
    Returns:
        审批结果
    """

    # 暂停执行,等待人工审批
    human_response = interrupt({
        "type": "financial_approval",
        "message": "财务拨款申请需要审批",
        "data": {
            "applicant_name": applicant_name,
            "amount": amount,
            "purpose": purpose
        },
        "options": ["批准申请", "拒绝申请"]
    })

    # 处理审批结果
    action = human_response.get("action", "拒绝申请")
    approver_name = human_response.get("approver_name", "未知审批人")
    comment = human_response.get("comment", "")

    if action == "批准申请":
        status = "approved"
        response = f"✅ 申请已批准\n审批人: {approver_name}\n批准金额: ¥{amount:,.2f}\n审批意见: {comment}"
    else:
        status = "rejected"
        response = f"❌ 申请已拒绝\n审批人: {approver_name}\n拒绝原因: {comment}"

    # 更新状态
    state_update = {
        "approval_status": status,
        "approver_name": approver_name,
        "messages": [ToolMessage(response, tool_call_id=tool_call_id)],
    }

    return Command(update=state_update)
3.5.4 创建工作流
from langchain.chat_models import init_chat_model
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver

# 初始化模型
llm = init_chat_model(f"openai:{modelName}")

# 绑定工具
tools = [evaluate_funding_request, financial_approval]
llm_with_tools = llm.bind_tools(tools)

# 定义聊天节点
def chatbot(state: State):
    """智能财务助手节点"""
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

# 构建图
tool_node = ToolNode(tools=tools)
graph_builder = StateGraph(State)

# 添加节点和边
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", tool_node)
graph_builder.add_conditional_edges("chatbot", tools_condition)
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot")

# 编译图
graph = graph_builder.compile(checkpointer=MemorySaver())

在这里插入图片描述

3.5.5 运行图
config = {"configurable": {"thread_id": "rejection_demo_3"}}
user_request = "我是稳稳,申请拨款10万元用于购买豪华办公家具"
events = graph.stream(
    {"messages": [{"role": "user", "content": user_request}]},
    config,
    stream_mode="values"
)
for event in events:
    if "messages" in event:
        event["messages"][-1].pretty_print()

输出如下
在这里插入图片描述
我们可以看到下面的堆栈,出现了中断,并且消息载体有4个,从human到tool到AI
在这里插入图片描述
在生产环境中,可以利用系统,比如做消息通知相关财务,进行协作。这里我就直接模拟财务审核,并且中断的回复需要使用Command

approval_feedback = {
        "action": "批准申请",
        "approver_name": "李财务经理",
        "comment": "采购符合预算,批准拨款"
    }
events = graph.stream(
        Command(resume=approval_feedback),
        config,
        stream_mode="values"
    )
for event in events:
    if "messages" in event:
        event["messages"][-1].pretty_print()

输出如下
在这里插入图片描述
我们看看堆栈内容,消息最后可以看到AI最后的回复,以及状态机此时被改变了
在这里插入图片描述

3.6 时间旅行

何为时间旅行?我们看过市面上的产品

  • 交互式AI应用 - 用户可以"后悔"某个选择,回到之前重新开始
  • 复杂工作流调试 - 开发者可以精确定位问题发生的节点
  • A/B测试场景 - 从同一起点测试不同的AI决策路径
  • 自主Agent开发 - 当Agent做出错误决策时,可以回溯并尝试替代方案

下面的案例,我们假定对于工具生成的结果不满意,重新生成,比如想豆包的设计

在这里插入图片描述

这里我们假定,有一个获取个人信息的工具,获取个人信息的工具,随机几个爱好,以便AI每次使用的时候,随机生成

3.6.1 基础代码
from typing import Annotated
from langchain_core.messages import BaseMessage
import random
from langchain.tools import tool
from typing_extensions import TypedDict
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from IPython.display import Image, display

######################### 创建模型 #########################
llm = ChatOpenAI(model=modelName)

######################### 初始化图 #########################

# 创建信息载体
class State(TypedDict):
    messages: Annotated[list, add_messages]
    test:str


# 创建图
graph_builder = StateGraph(State)

######################### 构建工具 #########################
@tool
def get_personal_infos(name: str) -> dict:
    """
        通过姓名获取个人信息
    :param name:姓名
    :return:
    """
    return {
        "name": name,
        "age": 18,
        "love": random.choice(["骑行","唱歌","跳舞","羽毛球"])
    }



# 构建工具
tools = [get_personal_infos]
llm_with_tools = llm.bind_tools(tools)


######################### 绘制图 #########################

# 创建聊天节点
def chatbot(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

# 加入节点
graph_builder.add_node("chatbot",chatbot)
tool_node = ToolNode(tools=tools)
graph_builder.add_node("tools",tool_node)

# 构建条件分支节点
graph_builder.add_conditional_edges(
    "chatbot",tools_condition
)

# 创建关系
graph_builder.add_edge("tools","chatbot")

# 开始
graph_builder.add_edge(START,"chatbot")

# 创建记忆点
memory = MemorySaver()

######################### 编译图 #########################
graph = graph_builder.compile(memory)

######################### 查看图 #########################
display(Image(graph.get_graph().draw_mermaid_png()))

运行上面代码

config = {"configurable":{"thread_id":"10086"}}
events = graph.stream({"messages":[{"role":"user","content":"我想获取小红的个人信息"}]},
                      config=config,
                      stream_mode="values"
                      )
for event in events:
    if "messages" in event:
        event["messages"][-1].pretty_print()

输出如下

在这里插入图片描述

3.6.2 查看历史
graph_hist = list(graph.get_state_history(config))

可以看到有这么一个对象StateSnapshot 官方文档地址:https://langchain-ai.github.io/langgraph/reference/types/?h=statesnapshot#langgraph.types.StateSnapshot

在这里插入图片描述
查看堆栈,有5个对象,对应的其实,就是从问题开始到结束的所有步骤
在这里插入图片描述
我们可以观察一下第4,跟第3个,以及第0个
在这里插入图片描述
现在运行这段代码,我们看看

for StateSnapshot in graph_hist[::-1]:
    print("下一个节点名称:",StateSnapshot.next)
    print("当前执行步骤快照配置:\n",StateSnapshot.config)
    print("上一步父快照配置:\n",StateSnapshot.parent_config)
    print("★"*50,"\n")

这个记录了,从用户执行到工具结束所有的快照id
在这里插入图片描述

工具执行节点的ID为:1f0ad9a6-4780-608f-8001-d7ff1d762934
在这里插入图片描述

3.6.3 从某个快照/节点恢复

我们现在从工具节点重新生成

config = {'configurable': {
    'thread_id': '10086',
    'checkpoint_ns': '',
    'checkpoint_id': '1f0ad9a6-4780-608f-8001-d7ff1d762934'}}
for event in graph.stream(None,config, stream_mode="values"):
    if "messages" in event:
        event["messages"][-1].pretty_print()

工具执行节点的ID为:1f0ad9a6-4780-608f-8001-d7ff1d762934,回顾一下,前面我们第一次执行出来的结果是唱歌,现在我们可以重复调用这个代码,可以看到会随机从["骑行","唱歌","跳舞","羽毛球"] 得到值,爱好骑行

在这里插入图片描述

三、总结

1、 什么是代理?

在这里插入图片描述

代理(Agent)由三个核心组件构成:

  1. 大语言模型(LLM):提供智能决策能力
  2. 工具集(Tools):可调用的函数、API或其他对象
  3. 提示词(Prompt):提供指令和上下文

2、 代理工作循环:

  1. LLM选择并调用工具
  2. 接收工具执行结果(观察)
  3. 基于观察决定下一步行动
  4. 重复循环直到满足停止条件

3、 关键特性:

  • 内存集成:支持短期(会话级)和长期(跨会话)内存
  • 人工介入控制:可无限期暂停等待人工反馈
  • 流式支持:实时流式传输状态、token、工具输出
  • 部署工具:包含LangGraph Platform和Studio可视化IDE

4、 预构建组件包生态:

包名 描述 安装命令
langgraph-prebuilt 创建基础代理的预构建组件 pip install -U langgraph langchain
langgraph-supervisor 构建监督者代理 pip install -U langgraph-supervisor
langgraph-swarm 构建群体多代理系统 pip install -U langgraph-swarm
langchain-mcp-adapters MCP服务器接口 pip install -U langchain-mcp-adapters
langmem 代理内存管理 pip install -U langmem
agentevals 代理性能评估工具 pip install -U agentevals

5、 核心组件详解:

  1. 节点(Nodes):图中的实体或对象,可包含属性和元数据
  2. 边(Edges):定义节点间的关系和连接
  3. 条件边(Conditional Edges):包含动态决策逻辑的连接
  4. 状态(State):维护当前上下文和交互历史
  5. 工具(Tools):可调用的功能和外部系统接口
  6. 路径(Paths):代理遍历图的节点和边序列

6、基础知识点总结:

6.1 创建模型

from langchain.chat_models import init_chat_model
llm = init_chat_model(model=f"openai:{modelName}")
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model=modelName)

6.2 创建代理

可以使用与构建组件,快速创建一个Agent

from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver

class WeatherResponse(BaseModel):
    conditions: str
    
agent = create_react_agent(
    model=model, # 既可以是模型字符串名称,也可以是模型的示例
    tools=[get_weather],
    prompt=prompt, # 既可以是prompt的静态content内容,也可以通过函数构建func实现动态prompt
    checkpointer=checkpointer # 指定为内存节点 ,创建图的时候就要声明
    response_format=WeatherResponse,# 指定结构化输出
    
)

为图添加记忆

  • 可以在create_react_agent通过指定response_format,

结构化输出

  • 可以在create_react_agent通过指定response_format,或者利用prompt结构化输出内容

6.3 图基础架构

  • 状态机定义:使用 TypedDict 定义状态,add_messages 管理对话历史
  • 图构建StateGraph → 添加节点 → 添加边 → compile()
  • 节点类型:聊天节点、工具节点、条件路由节点

6.4 工具节点对比

6.4.1 自定义工具节点
class BasicToolNode:
    def __init__(self, tools: list) -> None:
        self.tools_by_name = {tool.name: tool for tool in tools}
    
    def __call__(self, state: State):
        # 手动处理工具调用
        for tool_call in message.tool_calls:
            tool_result = self.tools_by_name[tool_call["name"]].invoke(tool_call["args"])
            outputs.append(ToolMessage(...))
        return {"messages": outputs}
6.4.2 内置工具节点
from langgraph.prebuilt import ToolNode
tool_node = ToolNode([get_personal_info])  # 一行搞定

对比要点

  • 自定义:完全控制工具调用逻辑,适合复杂业务场景
  • 内置:开箱即用,代码简洁,适合标准工具调用

6.5 路由机制对比

6.5.1 自定义路由
def route_tools(state: State):
    if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
        return "tools"
    return END

graph_builder.add_conditional_edges(
    "chatbot",
    route_tools,  # 自定义路由函数
    {"tools": "tools", END: END},
)
6.5.2 内置路由
from langgraph.prebuilt import tools_condition

graph_builder.add_conditional_edges(
    "chatbot",
    tools_condition,  # 内置路由函数
    {"tools": "tools", END: END},
)

对比要点

  • 自定义路由:灵活控制判断逻辑,支持复杂业务规则
  • 内置路由:标准工具调用判断,代码简洁

6.6 工具集成

  • 工具定义:使用 @tool 装饰器
  • 工具绑定llm.bind_tools(tools)
  • 工具路由tools_condition 自动判断是否需要调用工具
  • 工具节点ToolNode 处理工具调用

6.7 人机交互

  • 中断机制interrupt() 暂停执行等待用户输入
  • 恢复执行Command(resume=...) 恢复执行
  • 表单处理:通过 interrupt 收集用户数据

6.8 记忆管理

  • 检查点MemorySaver() 提供持久化记忆
  • 线程IDthread_id 区分不同对话会话
  • 状态历史get_state_history() 查看执行历史

6.9 时间旅行

  • 状态快照:每个执行步骤都有唯一 checkpoint_id
  • 回滚执行:通过 checkpoint_id 回到任意历史状态
  • 父级关系parent_config 维护执行路径

6.10 高级特性

  • 自定义状态:扩展状态字段支持复杂业务逻辑
  • 条件路由add_conditional_edges 实现智能分支
  • 工具注入InjectedToolCallId 自动注入工具调用ID
  • 状态更新Command(update=...) 批量更新状态

6.11 关键设计模式

  • 流式处理stream() 实时获取执行结果
  • 事件驱动:通过事件循环处理异步操作
  • 状态持久化:自动保存和恢复执行状态
  • 可观测性:完整的执行历史和状态追踪

6.12 选择策略

  • 简单场景:使用内置 ToolNode + tools_condition
  • 复杂业务:自定义工具节点 + 自定义路由
  • 性能要求:内置组件性能更优
  • 灵活性要求:自定义组件控制更精细
Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务

更多推荐