work hard work smart

专注于AI+Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

LangChain Memory记忆

Posted on 2026-10-04 17:45  work hard work smart  阅读(4)  评论(0)    收藏  举报

1、内存模式 RunnableWithMessageHistory

"""
可持续记忆(RunnableWithMessageHistory)
"""
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.chat_history import InMemoryChatMessageHistory  # 内存型消息记录
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
import os

load_dotenv()

# 设置本地模型
llm = init_chat_model(
    model="qwen-plus",
    model_provider="openai",
    api_key=os.getenv("aliQwen-api"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)


# 定义全局的“会话存储”,用来保存每个 session 的聊天历史
#    (真实项目中可改为 Redis、SQLite 等)
store = {}

def get_session_history(session_id: str):
    """
    根据 session_id 获取对应的历史消息对象。
    如果不存在则创建一个新的 InMemoryChatMessageHistory。
    """
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]


# 定义 Prompt 模板
#     - system: 给模型设定角色
#     - MessagesPlaceholder: 历史消息将注入这里
#     - human: 当前用户输入
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个友好的中文助理,会根据上下文回答问题。"),
    MessagesPlaceholder("history"),
    ("human", "{question}")
])


#构建基本链:Prompt → LLM → 输出解析
memory_chain = prompt | llm | StrOutputParser()

# -----------------------------------------------------
# 将链包装为支持记忆的版本
with_history = RunnableWithMessageHistory(
    memory_chain,              # 原始链
    get_session_history,       # 获取历史函数
    input_messages_key="question",  # 对应 prompt 输入的 key
    history_messages_key="history", # 对应 MessagesPlaceholder 的变量名
)

# -----------------------------------------------------
# 模拟一个会话,用 session_id 区分不同用户
cfg = {"configurable": {"session_id": "user-001"}}

# 第一次提问:告诉模型“我叫张三”
print("用户:我叫张三。")
print("AI:", with_history.invoke({"question": "我叫张三。"}, cfg))

# 第二次提问:让模型回忆前面的对话
print("\n 用户:我叫什么?")
print("AI:", with_history.invoke({"question": "我叫什么?"}, cfg))

  

  

2、持久化记忆 Redis

from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableConfig
import os
import redis  #导入原生redis库,pip install redis==5.3.1
from loguru import logger

load_dotenv()

REDIS_URL = "redis://:123456@localhost:6379"
# 创建原生Redis客户端,decode_responses 控制 Redis 返回数据的类型:False 返字节串,True 返字符串
redis_client = redis.Redis.from_url(REDIS_URL, decode_responses=True)

# 设置本地模型
llm = init_chat_model(
    model="qwen-plus",
    model_provider="openai",
    api_key=os.getenv("aliQwen-api"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# 创建提示模板
prompt = ChatPromptTemplate.from_messages([
    MessagesPlaceholder("history"),
    ("human", "{question}")
])

def get_session_history(session_id: str) -> RedisChatMessageHistory:
    """获取或创建会话历史(使用 Redis)"""
    # 创建 Redis 历史对象
    history = RedisChatMessageHistory(
        session_id=session_id,
        url=REDIS_URL,
        # ttl=3600  # 注释:关闭自动过期,避免重启后数据被清理
    )

    return history

# 创建带历史的链
chain = RunnableWithMessageHistory(
    prompt | llm,
    get_session_history,
    input_messages_key="question",
    history_messages_key="history"
)

# 配置
# session_id 就是登录大模型的各自帐户,类似登录手机号码,各不相同
config = RunnableConfig(configurable={"session_id": "user-001"})

# 主循环
print("开始对话(输入 'quit' 退出)")
while True:
    question = input("\n输入问题:")
    if question.lower() in ['quit', 'exit', 'q']:
        break

    response = chain.invoke({"question": question}, config)
    logger.info(f"AI回答:{response.content}")

    # 等同于redis-cli的SAVE命令,强制写入dump.rdb
    redis_client.save()

 

image

 

连接redis  redis-cli -a 123456

127.0.0.1:6379> keys *
1) "message_store:user-001"

LRANGE message_store:user-001 0 -1

 

查看redis保存的数据 power命令执行

 .\.venv\Scripts\python.exe -X utf8 -c "import redis,json; c=redis.Redis.from_url('redis://:123456@localhost:6379',decode_responses=True);  [print(json.dumps(json.loads(x),ensure_ascii=False,indent=2)) for x in c.lrange('message_store:user-001',0,-1)]" 

  

返回数据:总共四条

{
  "type": "ai",
  "data": {
    "content": "你是小明!😄  \n刚刚你已经自我介绍啦~名字简洁有力,听起来就充满朝气和亲切感!✨  \n(悄悄说:我还记得呢,不会忘~)  \n\n如果你愿意,也可以告诉我更多关于你的事——比如你喜欢什么、最近在学什么、有什么小目标,或者今天心情怎么样?我在这里,认真听,也真心想了解你 🌈",
    "additional_kwargs": {
      "refusal": null
    },
    "response_metadata": {
      "token_usage": {
        "completion_tokens": 83,
        "prompt_tokens": 79,
        "total_tokens": 162,
        "completion_tokens_details": null,
        "prompt_tokens_details": {
          "audio_tokens": null,
          "cache_write_tokens": null,
          "cached_tokens": 0,
          "image_tokens": null,
          "text_tokens": null
        }
      },
      "model_provider": "openai",
      "model_name": "qwen-plus",
      "system_fingerprint": null,
      "id": "chatcmpl-c054fcae-fadc-9f50-93f6-8f69237f0dff",
      "finish_reason": "stop",
      "logprobs": null
    },
    "type": "ai",
    "name": null,
    "id": "lc_run--01a1063a-8e35-7e21-b899-1c4598fbb86f-0",
    "tool_calls": [],
    "invalid_tool_calls": [],
    "usage_metadata": {
      "input_tokens": 79,
      "output_tokens": 83,
      "total_tokens": 162,
      "input_token_details": {
        "cache_read": 0
      },
      "output_token_details": {}
    }
  }
}
{
  "type": "human",
  "data": {
    "content": "我是谁",
    "additional_kwargs": {},
    "response_metadata": {},
    "type": "human",
    "name": null,
    "id": null
  }
}
{
  "type": "ai",
  "data": {
    "content": "你好,小明!很高兴认识你~😊  \n有什么想聊的、需要帮忙的,或者最近在忙什么?无论是学习、生活、兴趣爱好,还是遇到的小困惑,我都很乐意听你说说,一起探讨或帮你出出 主意!🌟",
    "additional_kwargs": {
      "refusal": null
    },
    "response_metadata": {
      "token_usage": {
        "completion_tokens": 55,
        "prompt_tokens": 12,
        "total_tokens": 67,
        "completion_tokens_details": null,
        "prompt_tokens_details": {
          "audio_tokens": null,
          "cache_write_tokens": null,
          "cached_tokens": 0,
          "image_tokens": null,
          "text_tokens": null
        }
      },
      "model_provider": "openai",
      "model_name": "qwen-plus",
      "system_fingerprint": null,
      "id": "chatcmpl-6e3c3b67-c5be-9fb2-a079-01a11dbab809",
      "finish_reason": "stop",
      "logprobs": null
    },
    "type": "ai",
    "name": null,
    "id": "lc_run--01a10638-2663-72b2-9824-64a94d1c0e0b-0",
    "tool_calls": [],
    "invalid_tool_calls": [],
    "usage_metadata": {
      "input_tokens": 12,
      "output_tokens": 55,
      "total_tokens": 67,
      "input_token_details": {
        "cache_read": 0
      },
      "output_token_details": {}
    }
  }
}
{
  "type": "human",
  "data": {
    "content": "我叫小明",
    "additional_kwargs": {},
    "response_metadata": {},
    "type": "human",
    "name": null,
    "id": null
  }
}