微信扫码
添加专属顾问
我要投稿
LangChain的Memory模块让AI对话不再"失忆",教你构建能记住上下文的智能应用。 核心内容: 1. 解析LLM无状态性带来的对话挑战 2. 详解LangChain四种记忆类型的工作原理 3. 实战构建具有记忆功能的聊天机器人
from langchain_core.messages import HumanMessage, AIMessagefrom langchain_core.chat_history import BaseChatMessageHistory# 简单的消息历史存储class InMemoryChatMessageHistory(BaseChatMessageHistory): def __init__(self): self.messages = [] def add_message(self, message): self.messages.append(message) def clear(self): self.messages = []# 实例化并添加消息history = InMemoryChatMessageHistory()history.add_message(HumanMessage(content="你好!"))history.add_message(AIMessage(content="你好!有什么可以帮助你的吗?"))history.add_message(HumanMessage(content="你是谁?"))print("--- ChatMessageHistory 示例 ---")for msg in history.messages: print(f"{msg.type}: {msg.content}")print("\n")
from langchain.memory import ConversationBufferMemorybuffer_memory = ConversationBufferMemory()buffer_memory.save_context({"input": "我的名字是小明。"}, {"output": "好的,小明。很高兴认识你!"})buffer_memory.save_context({"input": "你知道我叫什么吗?"}, {"output": "我知道,你叫小明。"})# load_memory_variables 会返回一个字典,其中包含对话历史# 默认键是 "history"print("--- ConversationBufferMemory 示例 ---")print(buffer_memory.load_memory_variables({}))# 你会看到 history 字段包含了所有对话,格式为字符串# output: {'history': 'Human: 我的名字是小明。\nAI: 好的,小明。很高兴认识你!\nHuman: 你知道我叫什么吗?\nAI: 我知道,你叫小明。'}print("\n")
from langchain.memory import ConversationBufferWindowMemory# 只保留最近1轮对话 (即只保留当前轮和上一轮)window_memory = ConversationBufferWindowMemory(k=1)window_memory.save_context({"input": "第一句话。"}, {"output": "这是第一句话的回复。"})window_memory.save_context({"input": "第二句话。"}, {"output": "这是第二句话的回复。"})window_memory.save_context({"input": "第三句话。"}, {"output": "这是第三句话的回复。"})print("--- ConversationBufferWindowMemory (k=1) 示例 ---")print(window_memory.load_memory_variables({}))# output: {'history': 'Human: 第三句话。\nAI: 这是第三句话的回复。'}# 注意,只有最后一句对话被保留了print("\n")
from langchain.memory import ConversationSummaryMemoryfrom langchain_openai import ChatOpenAIfrom dotenv import load_dotenv; load_dotenv() # 确保加载了OPENAI_API_KEYllm_for_summary = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) # 用于摘要的LLMsummary_memory = ConversationSummaryMemory(llm=llm_for_summary)summary_memory.save_context({"input": "我最喜欢的颜色是蓝色。"}, {"output": "蓝色很棒!"})summary_memory.save_context({"input": "你知道天空为什么是蓝色的吗?"}, {"output": "天空看起来是蓝色是因为瑞利散射。"})summary_memory.save_context({"input": "那大海呢?"}, {"output": "大海的蓝色则是由水的固有性质以及对光线的吸收和散射造成的。"})print("--- ConversationSummaryMemory 示例 ---")print(summary_memory.load_memory_variables({}))# 你会看到 history 字段中包含了LLM生成的摘要和最新的对话# 摘要可能类似:"用户提到他最喜欢的颜色是蓝色,询问天空和大海为何呈现蓝色,AI解释了瑞利散射和水的吸收散射。"print("\n")
from langchain.memory import ConversationTokenBufferMemory
from langchain_openai import ChatOpenAI
from langchain.callbacks import get_openai_callback # 用于查看token使用
# 1. 创建自定义的 Chat Model 类,本地是vllm部署的模型,计算token方面需要重写方法
class VllmChatModel(ChatOpenAI):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self._tokenizer = tiktoken.get_encoding("cl100k_base")
def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:
"""根据消息列表计算 token 数量。"""
text = ""
for message in messages:
text += message.content
return len(self._tokenizer.encode(text))
@property
def _llm_type(self) -> str:
return "vllm-chat-model"
llm_token = VllmChatModel(
model=os.environ.get("OPENAI_MODEL"),
temperature=0.9,
base_url=os.environ.get("OPENAI_BASE_URL"),
openai_api_key=os.environ.get("OPENAI_API_KEY"),
)
# 设置最大Token数为 50 (实际会略有浮动,因为是估算)
token_memory = ConversationTokenBufferMemory(llm=llm_token , max_token_limit=50)
token_memory.save_context({"input": "第一句非常长的对话内容,包含了多个词语,这样才能体现Token的限制作用。"}, {"output": "第一句回复。"})
token_memory.save_context({"input": "第二句相对较短的对话内容。"}, {"output": "第二句回复。"})
token_memory.save_context({"input": "第三句简短的话。"}, {"output": "第三句回复。"})
print("--- ConversationTokenBufferMemory 示例 ---")
print(token_memory.load_memory_variables({}))
# 你会发现,可能只有最后几句话被保留了,因为前几句话的Token数已经超出了50的限制
# print(cb) # 如果是LLM调用,可以看到Token数
print("\n")
from dotenv import load_dotenvimport osfrom langchain_openai import ChatOpenAIfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.runnables.history import RunnableWithMessageHistoryfrom langchain_core.messages import HumanMessage, AIMessagefrom langchain.memory import ConversationBufferWindowMemoryfrom langchain_core.chat_history import BaseChatMessageHistoryload_dotenv()llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)# 1. 定义一个用于存储会话历史的字典 (模拟持久化存储,实际应用中会是数据库/Redis)# key 是 session_id, value 是 BaseChatMessageHistory 实例store = {}# 2. 实现 get_session_history 函数# 这个函数会根据 session_id 返回对应的 ChatMessageHistory 对象def get_session_history(session_id: str) -> BaseChatMessageHistory: if session_id not in store: # 这里我们使用 ConversationBufferWindowMemory,只保留最近3轮对话 # 你也可以根据需要替换为 ConversationBufferMemory 或其他记忆类型 store[session_id] = ConversationBufferWindowMemory( k=3, # 保持3轮记忆 return_messages=True, # 以消息对象列表形式返回,适合MessagesPlaceholder output_key="output", input_key="input" # 定义输入输出的key ).chat_memory # 获取底层的 ChatMessageHistory 对象 return store[session_id]# 3. 定义一个普通的 LCEL 链条 (不带记忆)# 注意 PromptTemplate 中使用 MessagesPlaceholder 来占位历史消息prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个友好的AI助手,请根据上下文进行对话。"), MessagesPlaceholder(variable_name="history"), # 这是记忆会填充的地方 ("user", "{input}") # 用户当前输入])# 核心业务逻辑链 (这是我们希望包装成有记忆的链条)# input -> prompt (加入history和input) -> LLM -> output parserbase_conversation_chain = prompt | llm | StrOutputParser()# 4. 使用 RunnableWithMessageHistory 包装你的 LCEL 链条with_message_history_chain = RunnableWithMessageHistory( base_conversation_chain, # 你要包装的 LCEL 链条 get_session_history, # 会话历史获取函数 input_messages_key="input", # 链条的输入中,哪个键是用户消息 history_messages_key="history", # 提示模板中,哪个键是历史消息的占位符 output_messages_key="output" # 定义输出的key,以便save_context能够工作)# 5. 进行多轮对话 (需要传入 config 参数,其中包含 session_id)print("--- 具有记忆的聊天机器人示例 ---")session_id_user1 = "user_123"# 第一轮对话response1 = with_message_history_chain.invoke( {"input": "我的名字是张三。"}, config={"configurable": {"session_id": session_id_user1}})print(f"用户1 (轮1): 我的名字是张三。")print(f"AI (轮1): {response1}")# 第二轮对话 (同一个 session_id)response2 = with_message_history_chain.invoke( {"input": "你知道我叫什么吗?"}, config={"configurable": {"session_id": session_id_user1}})print(f"用户1 (轮2): 你知道我叫什么吗?")print(f"AI (轮2): {response2}") # AI应该能回答出“张三”# 第三轮对话 (同一个 session_id)response3 = with_message_history_chain.invoke( {"input": "我喜欢吃苹果,不喜欢吃香蕉。"}, config={"configurable": {"session_id": session_id_user1}})print(f"用户1 (轮3): 我喜欢吃苹果,不喜欢吃香蕉。")print(f"AI (轮3): {response3}")# 第四轮对话 (同一个 session_id)response4 = with_message_history_chain.invoke( {"input": "我刚才说了我喜欢什么水果?"}, config={"configurable": {"session_id": session_id_user1}})print(f"用户1 (轮4): 我刚才说了我喜欢什么水果?")print(f"AI (轮4): {response4}") # AI应该能回答出“苹果”# 切换到另一个用户 (不同的 session_id)session_id_user2 = "user_456"response_user2_1 = with_message_history_chain.invoke( {"input": "你好,我是李四。"}, config={"configurable": {"session_id": session_id_user2}})print(f"\n用户2 (轮1): 你好,我是李四。")print(f"AI (轮1): {response_user2_1}")response_user2_2 = with_message_history_chain.invoke( {"input": "我喜欢什么水果?"}, # 故意问这个,看AI是否会混淆 config={"configurable": {"session_id": session_id_user2}})print(f"用户2 (轮2): 我喜欢什么水果?")print(f"AI (轮2): {response_user2_2}") # AI应该不知道,因为这是新会话
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-07-23
Langchain学习教程一(Hello World)
2025-07-23
Langchain学习教程二(提示词模板和 LCEL 语法)
2025-07-23
Langchain 教程三(FewShotChatMessagePromptTemplate)
2025-07-23
Langchain 教程四(OutputParser)
2025-07-23
Langchain教程五(LCEL 上篇)
2025-07-23
Langchain教程六(LCEL下篇)
2025-07-22
通俗易懂的LangGraph图定义解析
2025-07-21
LlamaIndex + LangChain 如何协同打造 AI 超级助手
2025-05-06
2025-06-05
2025-05-08
2025-05-28
2025-05-28
2025-05-19
2025-06-26
2025-05-19
2025-05-14
2025-05-15
2025-07-14
2025-07-13
2025-07-05
2025-06-26
2025-06-13
2025-05-21
2025-05-19
2025-05-08