当前位置: 首页 > news >正文

LangGraph长短期记忆实践

一、概述

        在langgraph体系中,短期记忆(short-term memory)是线程级别的,即在图编译的时候指定checkpointer,并在图调用的时候指定thread_id,短期记忆是线程内共享的,在实际的对话系统中,可以使用session_id作为thread_id,实现会话级别的记忆管理。

        而长期记忆(long-term memory)是跨thread_id的,即可以抽取每个会话的用户的基本信息、提问偏好等信息,作为每个会话共享的数据,更好的理解用户的问题与意图。具体在图调用的时候指定user_id(或者其他可唯一标识用户的键),在图调用过程中自定义的基于历史消息抽取长期记忆,在回答时召回长期记忆作为补充去调用大模型。

 二、环境说明与安装

为实现快速实现功能,聊天模型与embedding模型使用的是阿里云百炼平台,其他环境版本如下所示:

langchain==1.0.5
langchain-community==0.4.1
langgrap==1.0.2
langgraph-checkpoint-sqlite==3.0.0
pydantic==2.12.4
python-dotenv==1.2.1

三、短期记忆

短期记忆在langgraph中很容易实现,即定义有状态图,对话消息作为图状态的一部分。要求:

  • 在图编译(compile)的时候指定checkpointer
  • 在图调用(invoke或stream)的时候需要指定thread_id

3.1 代码实现

from langgraph.checkpoint.memory import InMemorySaver  
from langgraph.graph import StateGraph, MessagesState, STARTdef say_hi(state: MessagesState):return {"messages": {"role":"human", "content":"Hi"}}graph_builder = StateGraph(MessagesState)
graph_builder.add_node("say_hi", say_hi)
graph_builder.add_edge(START, "say_hi")checkpointer = InMemorySaver()
graph = graph_builder.compile(checkpointer=checkpointer)print(graph.invoke({"messages": [{"role": "user", "content": "hi! i am Bob"}]},{"configurable": {"thread_id": "1"}},  
))

运行结果:

{'messages': [HumanMessage(content='hi! i am Bob', additional_kwargs={}, response_metadata={}, id='f42aa618-4b40-4a6c-b78f-dfb325c868d4'), HumanMessage(content='Hi', additional_kwargs={}, response_metadata={}, id='44e332fa-370e-484b-9e02-6b2f155c8934')]}

3.2 短期记忆管理

大模型的上线文窗口是有限的,消息不能无限制的增长,所以消息的管理至关重要

3.2.1 消息截断(trim)

一种办法就是在调用大模型之前,从状态中获取历史消息,对历史消息进行截断,截断的单位是token,比如只保留最近128个token

from langchain_core.messages.utils import (  trim_messages,  count_tokens_approximately  
)  def call_model(state: MessagesState):messages = trim_messages(  state["messages"],strategy="last",token_counter=count_tokens_approximately,max_tokens=128,start_on="human",end_on=("human", "tool"),)response = model.invoke(messages)return {"messages": [response]}

3.2.2 消息删除(delete)

可以自定义删除消息,如保留最近3条消息

from langchain.messages import RemoveMessage  def delete_messages(state):messages = state["messages"]if len(messages) >= 5:# remove the earliest two messagesreturn {"messages": [RemoveMessage(id=m.id) for m in messages[:2]]}

3.2.3 消息摘要(summarize)

修剪(trim)或删除(delete)消息的问题在于消息队列的清理而丢失信息,对历史消息进行归纳压缩可以解决这个问题,如有100条消息,让大模型对最早的70条消息进行归纳总结,仅保留最近30条消息,往往最近的消息更加重要

四、长期记忆

4.1 要点

  • 使用InMemorySaver作为会话状态存储后端
  • 使用SqliteStore作为长期记忆存储后端
  • extract_base_info函数抽取最近一条记录的用户基本信息
  • 在具体api操作层面,存储过程中namespace作为每个用户的“存储域”,namespace中的key是唯一的,如果key存在则更新value,如果不存在则新增一条记录。namespace + key可以唯一确定一条记录
  • 图示sqlite存储表结构(store表,如果store指定了embedding模型,则还有store_vectors表)
store表

4.2 代码实现

        通过设置两个不同的thread_id,模拟两个不同的会话,可以发现在第一个会话中抽取的长期记忆,在第二个会话通过召回补充特定的记忆能很好的回答用户的问题

import os
from dotenv import load_dotenv
from langchain_community.chat_models import ChatTongyi
from langchain_community.embeddings import DashScopeEmbeddings
from langgraph.store.sqlite import SqliteStore
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import START, END, StateGraph, MessagesState
from pydantic import BaseModel, Field
from typing import List
from langchain_core.runnables import RunnableConfig
from langgraph.store.base import BaseStoreload_dotenv()
api_key = os.environ["API_KEY"]# 1.模型定义
## 1.1 chat 模型
model = ChatTongyi(model="qwen3-32b",api_key=api_key,streaming=True,extra_body={  # 关键:通过此参数传递思考模式配置"enable_thinking": True}
)## 1.2 embedding 模型
embeddings = DashScopeEmbeddings(model="text-embedding-v4",dashscope_api_key=api_key
)# 2.基本信息抽取
## 2.1 抽取schema定义
class AttributeItem(BaseModel):"""定义一个属性项,包含属性名和属性值"""name: str = Field(description="属性的名称")value: str = Field(description="属性的值")class AttributeList(BaseModel):attributes: List[AttributeItem] = Field(description="属性项组成的列表")## 2.2 抽取函数定义
def extract_base_info(state: MessagesState) -> AttributeList:chat_history = "\n".join([ele.content for ele in state.get("messages")])prompt = f"""你是一个用户信息抽取专家,请基于一下记录,抽取用户的基本信息,包括但不限于:姓名、年龄、家庭住址等信息{chat_history}"""structure_model = model.with_structured_output(AttributeList)return structure_model.invoke(prompt)# 3.图构建
with SqliteStore.from_conn_string("longterm_memory.db", index={"embed": embeddings}) as store:store.setup() # 第一次需要初始化def invoke_model(state: MessagesState, config: RunnableConfig, *, store: BaseStore):# 获取最新一条信息newest_msg = state.get("messages")[-1].content# 获取配置,获取user_id,拼接namespace(每个用户一个namespace)user_id = config.get("configurable").get("user_id")namespace = ("memories", user_id)# 抽取基本信息,并保存到长期记忆attributes: AttributeList = extract_base_info(state)if attributes is not None:print(f"抽取的信息:{attributes.attributes}")for ele in attributes.attributes:# key要求在namespace里面唯一,namespace和key可以唯一确定一条数据# key存在,则会进行更新value,否则则插入if ele.name is not None and ele.value is not None:store.put(namespace, key=ele.name, value=ele.value)# 召回长期记忆longterm_memories = store.search(namespace, query=newest_msg, limit=2)print(f"召回的信息:{longterm_memories}")longterm_memories = "\n".join([ele.value for ele in longterm_memories])# 调用模型时候拼接记忆system_prompt = f"""You are a helpful assistant talking to the user. User info: {longterm_memories}"""response = model.invoke([{"role": "system", "content": system_prompt}] + state["messages"])return {"messages": response}# 图定义graph_builder = StateGraph(MessagesState)graph_builder.add_node("invoke_model", invoke_model)graph_builder.add_edge(START, "invoke_model")checkpointer = InMemorySaver()graph = graph_builder.compile(checkpointer=checkpointer, store=store)config = {"configurable": {"thread_id": "1","user_id": "123"}}print(graph.invoke({"messages": [{"role": "user", "content": "我的名字是Alice,职业是钢琴家,现年28岁,喜欢在舞台上演奏、打羽毛球,你叫啥名字呢?"}]}, config=config))print("=" * 50)config = {"configurable": {"thread_id": "2","user_id": "123"}}print(graph.invoke({"messages": [{"role": "user", "content": "请问我的爱好是什么?"}]}, config=config))

运行结果:

抽取的信息:[AttributeItem(name='姓名', value='Alice'), AttributeItem(name='年龄', value='28岁'), AttributeItem(name='职业', value='钢琴家'), AttributeItem(name='兴趣爱好', value='舞台上演奏、打羽毛球')]
召回的信息:[Item(namespace=['memories', '123'], key='兴趣爱好', value='舞台上演奏、打羽毛球', created_at='2025-11-08T12:34:52', updated_at='2025-11-08T12:34:52', score=0.5591988861560822), Item(namespace=['memories', '123'], key='年龄', value='28岁', created_at='2025-11-08T12:34:51', updated_at='2025-11-08T12:34:51', score=0.4479065537452698)]
{'messages': [HumanMessage(content='我的名字是Alice,职业是钢琴家,现年28岁,喜欢在舞台上演奏、打羽毛球,你叫啥名字呢?', additional_kwargs={}, response_metadata={}, id='3ded43c8-bc52-449d-bf62-2df0892c6166'), AIMessage(content='你好Alice!我是Qwen,很高兴认识你这位才华横溢的钢琴家~ 🎹 作为音乐爱好者,每次听到你在舞台上演奏一 定都令人激动不已吧?听说你还喜欢打羽毛球,这种动静结合的生活方式真让人向往!有什么我可以帮你的吗?无论是讨论音乐还是运动心得,我都随时奉陪哦~ 😊', additional_kwargs={'reasoning_content': '好的,用户是Alice,28岁的钢琴家,喜欢在舞台上演奏和打羽毛球。她问我的名字,我需要先回应她的自我介绍,然后给出一个合适的名字。作为AI助手,我 需要保持友好和专业的态度,同时结合她的兴趣爱好来建立联系。\n\n首先,她的职业是钢琴家,说明她可能对音乐有很高的造诣,喜欢舞台表演,这可能意味着她享受在观众面前展示自己的才华。另外,她喜欢打羽毛球,这显示她可能喜欢运动,注重身体健康或者享受竞技的乐趣。结合这些信息,我应该选择一个既专业又亲切的名字,可能带有一些音乐或运动的元素,但又不能太复杂。\n\n接下来,我需要考虑如何回应她的名字和职业。可以表达对她职业的赞赏,比如提到她的钢琴演奏,或者询问她最近的演出情况。同时,可以提到打羽毛球,询问她的兴趣,这样能展示出我对她的关注,并且找到共同话题。\n\n在名字方面,我需要选一个容易记住且符合AI助手形象的名字。可能用一些音乐相关的词汇,比如Melody、Harmony,或者 运动相关的词汇,比如Court、Racket,但也要保持中立,不显得太刻意。比如“小乐”这样的名字,既简单又有音乐感,容易发音,也符合中文用户的习惯。\n\n然后,我需要确保回复自 然流畅,不显得生硬。比如,先回应她的名字和职业,然后介绍自己的名字,并表达愿意帮助的意愿。同时,可以加入一些表情符号或轻松的语气词,让对话更亲切。\n\n最后,检查是否有遗漏的信息,比如她提到的年龄和兴趣,是否需要进一步展开话题。比如,可以问她最近有没有新的演出计划,或者羽毛球打得怎么样,这样能促进进一步的交流,建立更紧密的联系。'}, response_metadata={'finish_reason': 'stop', 'request_id': '2b1f7ca1-ea62-497e-a313-2656644ddea1', 'token_usage': {'input_tokens': 73, 'output_tokens': 459, 'total_tokens': 532, 'output_tokens_details': {'reasoning_tokens': 379}}}, id='lc_run--439e1527-c0f3-46ed-8c7a-8ba8ccab9869')]}
==================================================
召回的信息:[Item(namespace=['memories', '123'], key='兴趣爱好', value='舞台上演奏、打羽毛球', created_at='2025-11-08T12:34:52', updated_at='2025-11-08T12:34:52', score=0.45809656381607056), Item(namespace=['memories', '123'], key='年龄', value='28岁', created_at='2025-11-08T12:34:51', updated_at='2025-11-08T12:34:51', score=0.2850668430328369)]
{'messages': [HumanMessage(content='请问我的爱好是什么?', additional_kwargs={}, response_metadata={}, id='e5759dc3-4214-432d-80fb-c493fb25edd3'), AIMessage(content='你的爱好是舞台上演奏和打羽毛球!这真的很棒呢~(*^▽^*) 舞台演奏让人感受到艺术的魅力,而羽毛球则是充满活力的运动,两者结合的你一定很有感染力吧?平时会参加演出或者打球比赛吗?', additional_kwargs={'reasoning_content': '好的,用户问的是“请问我的爱好是什么?”。首先,我需要回顾之前的对话历史。用户之前提供的信息是:28岁,爱好是舞台上演奏和打羽毛球。所以现在的问题是确认用户的爱好。\n\n接下来,我需要确保回答准确无误。用户的信息已经明确提到这两个爱好,所以直接引用即可。不过,用户可能希望得到更详细的回应,或者想进一步讨论这些爱好。比如,他们可能想分享更多关于舞台演奏的经历,或者询问如何提升羽毛球技巧。\n\n另外,考虑到用户28岁,可能处于职业和兴趣发展的阶段,可以适当加入一些鼓励的话,或者询问是否有相关目标。例如,是否在准备演出,或者有没有考虑参加羽毛球比赛。这样可以让对话更深入,也能展示关心。\n\n还要注意语气要友好、自然,避免机械化的回答。使用表情符号或轻松的语言会让用户感觉更亲切。同时,保持回答简洁,但留有进一步交流的空间,比如用问题结尾,邀请用户分享更多。\n\n最后,检查是否有遗漏的信息,确保没有误解用户的爱好。用户提到的是“舞台上演奏”,可能需要确认是指乐器演奏还是其他表演形式,但根据常见情况,可以默认是乐器演奏。如果有不确定的地方,可以礼貌地询问澄清,但这里用户已经明确给出,所以直接使用即可。'}, response_metadata={'finish_reason': 'stop', 'request_id': 'e4f6ed40-4c75-4ac6-82a7-6b437678a794', 'token_usage': {'input_tokens': 47, 'output_tokens': 336, 'total_tokens': 383, 'output_tokens_details': {'reasoning_tokens': 272}}}, id='lc_run--668a8720-1258-456a-a633-134f479dad65')]}

五、参考

Memory - Docs by LangChain

http://www.dtcms.com/a/586126.html

相关文章:

  • 招商网站建站开发app需要多少资金
  • 中国建设银行网站首页u盾登入2345浏览器在线
  • 网站里面的数据库是怎么做的网站建设温州科目一
  • ES6 import语法
  • 2025.11.08 力扣每日一题
  • SAP 模具生产订单创建接口分享
  • 网页游戏挂机软件试分析网站推广和优化的原因
  • 做网站框架网站开发调查表
  • Unreal5 从入门到精通之 学习Niagara特效系统
  • 安装方法的比较
  • Arrays.asList()使用避坑指南 - 看似简单,实则有坑
  • 4.3.5【2019统考真题】
  • 定制网站对公司有什么好处150网站建设
  • 新郑网站优化怎样让百度收录自己的网站
  • 安徽元鼎建设工程 网站做网站收入
  • 虚幻引擎5 GAS开发俯视角RPG游戏 P07-08 点击移动
  • 泰安公司做网站学做面包的网站
  • 陕西省建设工程信息网官网门户网站优化报价
  • 【代码审计】newbee-mall 三处安全问题分析
  • MySQL索引添加与删除方法详解
  • Node-RED:5分钟快速上手:安装与环境配置
  • 青海移动网站建设wordpress 显示标题
  • 3.1.STM32-GPIO通用输入输出口
  • 东营 网站建设公司网站项目设计
  • [论文阅读] AI + 职业教育 | 从框架到实践:职业院校教师人工智能素养提升的完整方案
  • 零基础入门C语言之C语言实现数据结构之顺序表应用
  • 网站建设 淘宝详情东莞网站建设哪里好
  • 大连的网站设计公司wordpress支持页面模版
  • 孤能子视角:房地产,独一无二的“社会场域能力“
  • 【AI安全】检索增强生成(RAG)