我们的聊天机器人现在可以使用工具回答用户问题,但它不记得之前交互的上下文。这限制了其进行连贯的多轮对话的能力。

LangGraph 通过持久化检查点(persistent checkpointing)解决了这个问题。如果在编译图时提供了 checkpointer,并在调用图时提供了 thread_id,LangGraph 会在每一步后自动保存状态。当您使用相同的 thread_id 再次调用图时,图会加载其保存的状态,从而允许聊天机器人从上次离开的地方继续。

简单来说,类似于咱们使用AI聊天可以创建多个对话,每个对话都有个thread_id作为其标识符,让图来加载其保存的状态来继续对话。

看官方的代码

创建检查点

from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()

这里官方使用的是内存检查点。这对于教程很方便(它将所有内容保存在内存中)。在生产应用中,您可能会将其更改为使用 SqliteSaver 或 PostgresSaver 并连接到您自己的数据库。

构建图

from typing import Annotated

from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition class State(TypedDict):
messages: Annotated[list, add_messages] graph_builder = StateGraph(State) tool = TavilySearch(max_results=2)
tools = [tool]
llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
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=[tool])
graph_builder.add_node("tools", tool_node) graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
)
# Any time a tool is called, we return to the chatbot to decide the next step
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot") graph = graph_builder.compile(checkpointer=memory)#使用提供的检查点编译图。

这部分与上一章节的代码基本相同,官方代码用 LangGraph 的预构建 ToolNode 和 tools_condition 替换了上节构建的 BasicToolNode。

想详细了解可以参考对应的API文档。

测试聊天机器人的记忆功能

config = {"configurable": {"thread_id": "1"}}

user_input = "Hi there! My name is Will."

# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print() user_input = "Remember my name?" # The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()

官方案例这里在第两段对话中测试聊天机器人是否记得第一段对话中提到的用户名字。

实践为聊天机器人添加记忆

创建带记忆的聊天机器人

import os
from typing import Annotated # from langchain.chat_models import init_chat_model
from langchain_openai import ChatOpenAI
from langchain_tavily import TavilySearch
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver memory = MemorySaver()#设置检查点 #环境变量设置,设置为你的API_KEY
os.environ['TAVILY_API_KEY'] = 'TAVILY_API_KEY'
os.environ['ARK_API_KEY'] = 'API_KEY' class State(TypedDict):
messages: Annotated[list, add_messages] graph_builder = StateGraph(State) tool = TavilySearch(max_results=2)
tools = [tool] llm = ChatOpenAI(
base_url="https://ark.cn-beijing.volces.com/api/v3",
api_key=os.environ.get('ARK_API_KEY'),
model="doubao-1-5-pro-32k-250115" # 根据实际模型名称修改
)
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=[tool])
graph_builder.add_node("tools", tool_node) graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
)
# Any time a tool is called, we return to the chatbot to decide the next step
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot") graph = graph_builder.compile(checkpointer=memory)#使用提供的检查点编译图。

代码相比之前只是多创建了个MemorySaver检查点,然后使用它来编译图。

测试聊天机器人的记忆功能

config = {"configurable": {"thread_id": "1"}}

user_input = "Hi there! My name is Will."

# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()

第一次对话返回结果:

================================ Human Message =================================

Hi there! My name is Will.
================================== Ai Message ================================== Hello, Will! It's great to meet you. If you have any questions, feel free to tell me.

我们在第二次对话测试聊天机器人是否还记得我们的名字:

user_input = "Remember my name?"

# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()

返回结果:

================================ Human Message =================================

Remember my name?
================================== Ai Message ================================== Of course! I'll remember that your name is Will. If you have any other needs, just let me know.

显然,聊天机器人已经具备了记忆功能。接下来我们尝试切换配置中的thread_id,来看看是否还记得我们的名字:

events = graph.stream(
{"messages": [{"role": "user", "content": user_input}]},
{"configurable": {"thread_id": "2"}},#这里切换一个thread_id
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()

返回结果为:

================================ Human Message =================================

Remember my name?
================================== Ai Message ================================== I'm sorry, you haven't told me your name yet. Could you please share it with me?

我们还可以通过.get_state()查看图的状态:

snapshot = graph.get_state(config)
snapshot

返回结果:

StateSnapshot(values={'messages': [HumanMessage(content='Hi there! My name is Will.', additional_kwargs={}, response_metadata={}, id='6696cb1b-0b66-4670-bf71-cc460dde6f87'), AIMessage(content="Hello, Will! It's nice to meet you. If you have any questions, feel free to tell me.", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 23, 'prompt_tokens': 1055, 'total_tokens': 1078, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'doubao-1-5-pro-32k-250115', 'system_fingerprint': None, 'id': '0217496320997100f848b5e7d5cd90fc22435541affcfca860dc5', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--1f01829c-0590-4f37-853d-7883e30b7b1b-0', usage_metadata={'input_tokens': 1055, 'output_tokens': 23, 'total_tokens': 1078, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}}), HumanMessage(content='Remember my name?', additional_kwargs={}, response_metadata={}, id='f92cb8d1-38c0-4bee-8033-55b61171731d'), AIMessage(content="Of course, Will. I'll remember your name. If you have anything you'd like to ask or discuss, just let me know. ", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 29, 'prompt_tokens': 1091, 'total_tokens': 1120, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'doubao-1-5-pro-32k-250115', 'system_fingerprint': None, 'id': '0217496321039670f848b5e7d5cd90fc22435541affcfcacd74c6', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--88da7655-4f55-4cb7-81a5-0cbd05126e2c-0', usage_metadata={'input_tokens': 1091, 'output_tokens': 29, 'total_tokens': 1120, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f046a1c-4f79-6899-8004-a7da7152350f'}}, metadata={'source': 'loop', 'writes': {'chatbot': {'messages': [AIMessage(content="Of course, Will. I'll remember your name. If you have anything you'd like to ask or discuss, just let me know. ", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 29, 'prompt_tokens': 1091, 'total_tokens': 1120, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_name': 'doubao-1-5-pro-32k-250115', 'system_fingerprint': None, 'id': '0217496321039670f848b5e7d5cd90fc22435541affcfcacd74c6', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--88da7655-4f55-4cb7-81a5-0cbd05126e2c-0', usage_metadata={'input_tokens': 1091, 'output_tokens': 29, 'total_tokens': 1120, 'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 0}})]}}, 'step': 4, 'parents': {}, 'thread_id': '1'}, created_at='2025-06-11T08:55:05.672514+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f046a1c-3e81-621e-8003-bb633a5bc23e'}}, tasks=(), interrupts=())

使用.next查看待处理的节点:

snapshot.next

返回next是空的,表示图已经到达了END状态。

恭喜! 您的聊天机器人现在可以借助LangGraph的检查点系统跨会话维护对话状态。这为更自然、更具情境性的交互打开了令人兴奋的可能性。LangGraph的检查点系统甚至能够处理任意复杂的图状态,这比简单的聊天记忆要表达性和强大得多。

LangGraph官方文档笔记——3.为聊天机器人添加记忆的更多相关文章

  1. docker官方文档笔记

    Docker在 CentOS7.X上运行.Docker可能在其他EL7的兼容版本中成功安装,但是官方并未进行测试,因此也不提供任何支持. 系统环境要求 docker必须运行在64-bit的系统上,对于 ...

  2. Vue官方文档笔记(二)

    23.$refs是什么东东? 通过在标签上设置ref属性,然后在Vue实例方法中可以通过$refs拿到这些标签,如: <input ref="input"> metho ...

  3. React官方文档笔记之快速入门

    快速开始 JSFiddle 我们建议在 React 中使用 CommonJS 模块系统,比如 browserify 或 webpack. 要用 webpack 安装 React DOM 和构建你的包: ...

  4. Vue官方文档笔记

    1.如何创建一个Vue实例对象? var vm = new Vue({ el: "#app", //标签id 或 标签类名 data:{ //双向绑定的数据 message: &q ...

  5. Spring 官方文档笔记---Bean

    In Spring, the objects that form the backbone of your application and that are managed by the Spring ...

  6. Android的AutoCompleteTextView在API17高版本添加的setText函数在低版本系统居然能正常调用?官方文档是不是不靠谱了?

    官方文档:https://developer.android.com/reference/android/widget/AutoCompleteTextView.html#setText(java.l ...

  7. pm2 官方文档 学习笔记

    一.安装 1.安装 npm install pm2 -g 2.更新 npm install pm2 -g && pm2 update pm2 update 是为了刷新 PM2 的守护进 ...

  8. vue.js 2.0 官方文档学习笔记 —— 01. vue 介绍

    这是我的vue.js 2.0的学习笔记,采取了将官方文档中的代码集中到一个文件的形式.目的是保存下来,方便自己查阅. !官方文档:https://cn.vuejs.org/v2/guide/ 01. ...

  9. Effective Go(官方文档)笔记

    Effective Go(官方文档)笔记 自己主动局部变量提升(编译期完毕?):return &...; 内置函数: new/make copy, append delete range(这是 ...

  10. Swift -- 官方文档Swift-Guides的学习笔记

    在经历的一段时间的郁闷之后,我发现感情都是虚伪的,只有代码是真实的(呸) 因为看了swift语法之后依然不会用swift,然后我非常作死的跑去看官方文档,就是xcode里自带的help>docu ...

随机推荐

  1. 【语义分割专栏】:U-net实战篇(附上完整可运行的代码pytorch)

    目录 前言 U-net全流程代码 模型搭建(model) 数据处理(dataloader) 评价指标(metric) 训练流程(train) 模型测试(test) 效果图 结语 前言 U-net原理篇 ...

  2. idea类和方法的注释模板设置

    作者:故事我忘了¢个人微信公众号:程序猿的月光宝盒 目录 1.在idea中设置类模板 2.设置方法注释模板 软件:idea 版本:2019.3.1 1.在idea中设置类模板 /** * @Class ...

  3. 1分钟了解 GPT-1到GPT-3 演化过程

    在研发ChatMoney这款产品的时候,我开始深入研究GPT的诞生,逐记录下来分享给大家. 前言 Generative Pre-trained Transformer(GPT)系列是由OpenAI开发 ...

  4. C# 异步编程:从 async/await 到 Task 并行库的全面解析

    引言 在现代软件开发中,处理高并发和耗时操作是一个常见的挑战.C# 提供了强大的异步编程模型,它允许程序在执行耗时操作时不会阻塞主线程,从而提高程序的响应性和性能.其中,async/await 关键字 ...

  5. ET5 MailBoxComponent 简单介绍

    根据ET5文档介绍,MailBoxComponent组件一般与Actor搭配使用,挂载该组件后,就可以通过Actor发送消息. 官方demo主要有两种使用方式: session.AddComponen ...

  6. 使用systemd 监控服务并实现故障自动重启

    一.为什么需要自动重启? 在生产环境中,服务可能因内存溢出.资源竞争.外部依赖中断等问题意外崩溃.手动恢复效率低下,而 systemd 的自动重启机制可在秒级内恢复服务,显著提升系统可用性. ⚙️ 二 ...

  7. C# DateTime时间戳帮助类型

    https://www.cnblogs.com/minotauros/p/10773258.html /// <summary> /// 时间工具类 /// </summary> ...

  8. Mysql不支持emoji表情

    报错 今天存储emoji,报错如下: 这是因为表情是4个字节的,Mysql的utf-8编码只支持3个字节的数据,所以要使用到新的编码utf8mb4 解决 注意:数据库的版本要 5.5.3+ 才可以支持 ...

  9. 矩阵与 dp 与线性递推

    矩阵的实质与线性代数有关,但用处并不大感兴趣的可以看看这里. 这里我们重点探讨矩阵乘法,\(n \times m\) 阶的矩阵 \(A\) 乘以 \(m \times k\) 阶的矩阵 \(B\) 得 ...

  10. 触摸/液位/感应三合一SC01F芯片的高性能解决方案

    触摸/液位/感应三合一SC01F芯片的高性能解决方案 SC01F是厦门晶尊微电子的单键电容触摸感应芯片,它可以通过任何非导电介质(如玻璃和塑料)来感应电容变化. SC01F应用场景 SC01F可以实现 ...