基于向量数据库与LangChain构建AI长期记忆系统实战

发布时间:2026/9/1 12:00:15
基于向量数据库与LangChain构建AI长期记忆系统实战 大家好最近在参与B站的AI创造公开赛尝试给AI聊天机器人增加一个“记忆库”功能目标是让AI在长对话中也能记住几百条甚至上千条之前的聊天内容实现“聊到500楼她还记得第1楼”的效果。这听起来很酷但背后涉及到如何高效存储、检索和利用海量对话历史的技术挑战。本文将围绕如何为AI聊天应用构建一个实用的记忆库系统展开从核心概念、技术选型到完整的代码实现一步步拆解。无论你是想为自己的AI项目增加长期记忆能力还是对AI Agent、向量数据库等前沿技术感兴趣都能从本文中获得一套可直接复用的实战方案。我们将使用Python、LangChain和向量数据库来完成这个项目并重点讲解如何避免“AI幻觉”让对话更连贯、更智能。1. 背景与核心概念为什么AI需要记忆库在传统的聊天机器人或基于大语言模型LLM的对话系统中模型通常只处理当前轮次的输入和有限的上下文窗口。例如GPT-3.5的上下文长度可能是4K或16K tokens。这意味着一旦对话轮次超过这个限制模型就会“忘记”很早之前的对话内容。这导致了几个核心问题上下文丢失用户可能在对话初期介绍了自己的姓名、喜好或背景但在几十轮对话后AI可能就不再记得这些关键信息需要用户反复提醒体验割裂。无法进行深度、连贯的长对话对于故事接龙、长期项目规划、心理咨询等需要长期记忆的场景有限的上下文成为瓶颈。资源浪费每次都将全部历史对话作为上下文输入给模型会消耗大量 tokens增加成本和延迟尤其是当历史很长时。“记忆库”就是为了解决这些问题而设计的。它的核心思想不是把全部历史对话都塞进模型的上下文窗口而是像人脑一样将重要的信息记忆存储起来在需要的时候再快速检索出来与当前对话结合形成完整的上下文。核心组件与工作流程记忆存储将对话中的关键信息如用户偏好、事实陈述、承诺等转化为结构化的“记忆”对象。记忆向量化使用文本嵌入模型Embedding Model将记忆文本转换为高维向量。语义相似的文本其向量在空间中的距离也更近。向量数据库存储将这些向量及其对应的原始文本元数据存入专门的向量数据库。记忆检索当新对话发生时将当前对话内容也向量化然后在向量数据库中搜索与之最相关的若干条历史记忆。记忆注入将检索到的相关记忆作为补充上下文与当前对话一起提交给大语言模型从而生成更准确、更连贯的回复。这样AI就仿佛拥有了一个外置的“海马体”能够突破自身上下文长度的限制实现长期、连贯的对话。2. 环境准备与版本说明在开始编码前我们需要搭建开发环境。本项目主要使用Python并依赖几个关键的库。以下是经过验证的版本组合建议尽量保持一致以避免兼容性问题。操作系统Windows 10/11, macOS, 或 Linux (Ubuntu 20.04) 均可。Python版本 3.8 本文使用 Python 3.9。核心依赖库 我们将使用pip进行安装。建议先创建一个新的虚拟环境。# 创建并激活虚拟环境 (以 conda 为例) conda create -n ai-memory python3.9 conda activate ai-memory # 安装核心依赖 pip install langchain0.1.0 pip install langchain-community0.0.10 pip install openai1.3.0 pip install chromadb0.4.15 pip install tiktoken pip install python-dotenv版本说明与选型理由LangChain: 一个用于开发由LLM驱动的应用程序的框架。它提供了构建链Chains、代理Agents以及本文核心所需的记忆Memory模块的标准化接口。我们使用其ConversationSummaryBufferMemory和自定义记忆类。LangChain-Community: LangChain 0.1.x 版本后许多第三方集成被移到了这个包中。OpenAI: 官方Python SDK用于调用GPT系列模型生成对话和文本嵌入。你需要一个有效的OpenAI API Key。ChromaDB: 一个轻量级、开源、易用的向量数据库非常适合原型开发和中小型项目。它允许我们在本地存储和检索向量无需复杂的服务器部署。Tiktoken: OpenAI开发的分词工具用于准确计算文本的token数量对于管理上下文长度至关重要。Python-dotenv: 用于从.env文件加载环境变量如API Key避免将敏感信息硬编码在代码中。项目结构预览 在开始前我们先规划一下项目目录这有助于理解代码的组织方式。my_ai_memory_project/ ├── .env # 存储环境变量API Key等 ├── main.py # 主程序入口 ├── memory_system.py # 核心记忆库系统实现 ├── chat_agent.py # 聊天代理封装 └── utils.py # 工具函数如文本处理接下来我们进入最核心的部分记忆系统的设计与实现。3. 核心原理与系统设计拆解一个高效的记忆库不仅仅是存储文本它需要智能地决定存什么、怎么存以及何时取。我们来逐一拆解。3.1 记忆的生成从对话中提取关键信息不是每一句“你好”、“在吗”都需要被永久记忆。我们需要从对话流中提取出有价值的、需要被长期记住的信息。常见策略包括基于规则提取识别特定句型如“我的名字是...”、“我喜欢...”、“我住在...”。这种方法直接但不够灵活。使用LLM进行总结性提取每隔几轮对话或者当检测到重要信息出现时如用户详细描述了一个项目将最近的一段对话历史提交给LLM让其生成一个简洁的总结性记忆。例如原始对话“我最近在学Python觉得pandas处理数据特别方便但装饰器还有点难懂。”LLM生成的记忆“用户正在学习Python对pandas掌握较好但觉得装饰器有难度。”实体与关系提取使用NER命名实体识别模型或LLM提取对话中的人名、地点、时间、事件等实体及其关系形成结构化记忆。在本项目中为了平衡效果与复杂度我们将采用“LLM总结性提取”作为主要策略并结合简单的规则触发。3.2 记忆的存储与检索向量数据库的作用提取出的记忆是文本我们需要将其转换为向量才能进行语义搜索。这里涉及两个关键模型文本嵌入模型如text-embedding-ada-002。它将一段文本映射为一个固定长度的向量例如1536维。语义相似的文本其向量余弦相似度会很高。向量数据库如我们选择的ChromaDB。它专门为存储和查询向量而优化。当我们存入一条记忆文本和其对应的向量后在检索时只需将当前问题转换为向量然后请求数据库“找出与这个向量最相似的N个向量”数据库会快速返回对应的原始文本记忆。检索过程当前用户输入“装饰器该怎么理解呢” 1. 将输入通过嵌入模型得到向量 V_query。 2. 在ChromaDB中搜索与 V_query 最相似的Top K个向量。 3. 假设检索到记忆“用户正在学习Python对pandas掌握较好但觉得装饰器有难度。” 4. 将该记忆作为上下文注入给LLM。 5. LLM结合记忆生成回复“你之前提到过觉得装饰器难懂我们可以先从它的概念说起...”这样AI就能“想起”用户之前的相关困惑给出更具连续性和个性化的回答。3.3 记忆的更新与遗忘记忆不是一成不变的。系统需要能更新过时的信息例如用户说“我搬家了现在住在上海”也需要能遗忘一些不重要的、临时的信息防止记忆库无限膨胀。更新当检索到一条相关但可能过时的记忆时可以在提示词中让LLM判断是否需要更新或者设计机制用新的记忆覆盖旧的。遗忘可以基于时间戳只保留最近N天的记忆、使用频率淘汰很少被检索到的记忆或重要性评分由LLM在生成记忆时赋予一个权重来实现简单的遗忘策略。4. 完整实战构建带记忆库的AI聊天系统现在我们将把上述理论转化为代码。我们将构建一个控制台聊天程序它具备长期记忆能力。4.1 项目初始化与配置首先创建项目目录和文件并设置环境变量。创建.env文件填入你的OpenAI API Key。# .env OPENAI_API_KEYsk-your-actual-api-key-here重要确保.env文件被添加到.gitignore中切勿提交到公开仓库。创建utils.py编写一些辅助函数。# utils.py import os from dotenv import load_dotenv def load_environment_variables(): 加载环境变量 load_dotenv() api_key os.getenv(OPENAI_API_KEY) if not api_key: raise ValueError(请在 .env 文件中设置 OPENAI_API_KEY) return api_key def safe_get(dictionary, key, defaultNone): 安全地从字典获取值 return dictionary.get(key, default)4.2 实现核心记忆系统这是最关键的模块我们将其放在memory_system.py中。# memory_system.py import uuid from datetime import datetime from typing import List, Dict, Any from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma from langchain.schema import Document from langchain.chat_models import ChatOpenAI from langchain.prompts import PromptTemplate import tiktoken class MemorySystem: 长期记忆系统 负责记忆的存储、检索、更新和总结。 def __init__(self, persist_directory./chroma_db): # 初始化嵌入模型用于将文本转换为向量 self.embeddings OpenAIEmbeddings(modeltext-embedding-ada-002) # 初始化向量数据库指定持久化目录 self.vectorstore Chroma( embedding_functionself.embeddings, persist_directorypersist_directory ) # 初始化一个用于总结对话的LLM self.summary_llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # 用于计算token管理上下文 self.encoder tiktoken.get_encoding(cl100k_base) # 定义总结记忆的提示词模板 self.summary_prompt PromptTemplate( input_variables[recent_chat_history], template请将以下最近的对话内容总结成一条简洁、客观的长期记忆。 记忆应聚焦于用户透露的关键个人信息、事实、偏好或持续讨论的主题。 避免记录问候语、临时性话题或无关细节。 直接输出总结后的记忆文本。 最近对话 {recent_chat_history} 长期记忆总结 ) def _count_tokens(self, text: str) - int: 计算文本的token数量 return len(self.encoder.encode(text)) def add_memory(self, memory_text: str, metadata: Dict[str, Any] None): 向记忆库中添加一条记忆 Args: memory_text: 记忆的文本内容 metadata: 附加信息如时间戳、重要性、关联的用户ID等 if metadata is None: metadata {} # 确保有唯一ID和时间戳 metadata.update({ id: str(uuid.uuid4()), timestamp: datetime.now().isoformat(), type: fact # 可以是 fact, preference, plan 等 }) # 创建LangChain Document对象包含文本和元数据 doc Document(page_contentmemory_text, metadatametadata) # 添加到向量数据库 self.vectorstore.add_documents([doc]) print(f[记忆系统] 已存储记忆{memory_text[:50]}...) def retrieve_relevant_memories(self, query: str, k: int 3) - List[str]: 根据当前查询检索相关的记忆 Args: query: 当前的对话内容或问题 k: 返回最相关的记忆条数 Returns: 相关记忆文本的列表 # 使用向量数据库进行相似性搜索 docs self.vectorstore.similarity_search(query, kk) memories [doc.page_content for doc in docs] if memories: print(f[记忆系统] 检索到 {len(memories)} 条相关记忆。) return memories def summarize_conversation_to_memory(self, conversation_history: List[str]): 将一段对话历史总结成一条长期记忆并存储。 这是一个简化策略当历史达到一定长度或轮次时触发。 Args: conversation_history: 最近的对话记录列表格式如 [用户: xxx, AI: yyy] if not conversation_history: return # 将对话历史拼接成字符串 history_text \n.join(conversation_history[-6:]) # 取最近3轮对话6条消息 # 如果历史太短可能不足以总结出有价值的记忆 if self._count_tokens(history_text) 30: return # 调用LLM进行总结 chain self.summary_prompt | self.summary_llm try: summary_result chain.invoke({recent_chat_history: history_text}) summary_text summary_result.content.strip() if summary_text and len(summary_text) 10: # 确保总结有效 self.add_memory(summary_text, metadata{source: auto_summary}) except Exception as e: print(f[记忆系统] 总结记忆时出错{e}) def manual_add_memory(self, memory_text: str): 手动添加一条记忆可用于测试或特殊场景 self.add_memory(memory_text, metadata{source: manual})4.3 构建聊天代理接下来我们创建chat_agent.py它将整合记忆系统和LLM完成完整的聊天流程。# chat_agent.py from typing import List from langchain.chat_models import ChatOpenAI from langchain.schema import HumanMessage, AIMessage, SystemMessage from memory_system import MemorySystem class ChatAgentWithMemory: 带有长期记忆的聊天代理 def __init__(self, system_prompt: str None): # 初始化对话LLM self.llm ChatOpenAI(modelgpt-3.5-turbo, temperature0.7) # 初始化记忆系统 self.memory_system MemorySystem() # 维护一个会话内的短期对话历史用于总结和上下文 self.conversation_history: List[str] [] # 系统提示词定义AI的角色和行为 self.system_prompt system_prompt or 你是一个友好、乐于助人的AI助手并且拥有长期记忆。 你能记住和用户对话中的重要信息并在后续对话中自然地运用这些记忆使对话更具连贯性和个性化。 请根据上下文和你的记忆给出最合适的回复。 def _format_context(self, user_input: str, retrieved_memories: List[str]) - str: 格式化上下文将系统提示、记忆和当前对话组合 context_parts [self.system_prompt] if retrieved_memories: context_parts.append(\n 相关记忆 \n) for i, memory in enumerate(retrieved_memories, 1): context_parts.append(f{i}. {memory}) context_parts.append( 记忆结束 \n) # 添加上下文中的最近几轮短期对话防止完全失忆 if self.conversation_history: # 只保留最近4轮8条消息作为短期上下文避免token过长 short_term_history self.conversation_history[-8:] context_parts.append(\n最近的对话) context_parts.extend(short_term_history) context_parts.append(f\n用户{user_input}) context_parts.append(\nAI) return \n.join(context_parts) def chat_cycle(self, user_input: str) - str: 处理一轮完整的聊天检索记忆 - 生成回复 - 更新历史 - 可能触发记忆总结 # 1. 检索相关长期记忆 relevant_memories self.memory_system.retrieve_relevant_memories(user_input, k2) # 2. 构建包含记忆的上下文 full_context self._format_context(user_input, relevant_memories) # 3. 调用LLM生成回复 messages [SystemMessage(contentfull_context)] try: response self.llm.invoke(messages) ai_response response.content except Exception as e: ai_response f抱歉我在思考时遇到了问题{e} # 4. 更新会话历史 self.conversation_history.append(f用户{user_input}) self.conversation_history.append(fAI{ai_response}) # 5. 定期触发自动记忆总结例如每6轮对话后 if len(self.conversation_history) 12: # 6轮对话 self.memory_system.summarize_conversation_to_memory(self.conversation_history) # 总结后可以清空或保留部分历史这里我们保留最后2轮作为短期上下文衔接 self.conversation_history self.conversation_history[-4:] return ai_response def manual_trigger_summary(self): 手动触发记忆总结用于测试或关键节点 self.memory_system.summarize_conversation_to_memory(self.conversation_history)4.4 主程序与运行演示最后我们创建main.py作为程序入口实现一个简单的交互式聊天循环。# main.py import sys import os sys.path.append(os.path.dirname(os.path.abspath(__file__))) from chat_agent import ChatAgentWithMemory from utils import load_environment_variables def main(): # 加载环境变量API Key load_environment_variables() print( * 50) print(启动带长期记忆的AI聊天助手) print(输入 退出、quit 或 exit 结束对话) print(输入 /记忆 查看当前检索到的记忆) print(输入 /总结 手动触发记忆总结) print( * 50) # 初始化聊天代理 agent ChatAgentWithMemory() # 可以预先添加一些“种子记忆”让AI一开始就了解用户 # agent.memory_system.manual_add_memory(用户是一名软件工程师主要使用Python和Java。) # agent.memory_system.manual_add_memory(用户有一只叫‘橘子’的猫。) while True: try: user_input input(\n你).strip() if user_input.lower() in [退出, quit, exit]: print(AI再见期待下次聊天。) break elif user_input /记忆: # 演示检索功能用上一句输入作为查询如果没有用默认查询 if agent.conversation_history: last_user_msg [m for m in agent.conversation_history if m.startswith(用户)][-1][4:] mems agent.memory_system.retrieve_relevant_memories(last_user_msg, k3) else: mems agent.memory_system.retrieve_relevant_memories(自我介绍, k3) print(\n[当前相关记忆]) for i, m in enumerate(mems, 1): print(f{i}. {m}) continue elif user_input /总结: agent.manual_trigger_summary() print([系统] 已手动触发记忆总结。) continue if not user_input: continue # 核心聊天循环 response agent.chat_cycle(user_input) print(fAI{response}) except KeyboardInterrupt: print(\n\n对话被中断。) break except Exception as e: print(f\n程序运行出错{e}) break if __name__ __main__: main()4.5 运行与效果验证在项目根目录下确保.env文件已配置好。在终端运行程序python main.py开始对话。你可以尝试以下流程来验证记忆效果第一轮你“你好我叫小明是一名数据分析师喜欢用Python。”AI会正常回复。第五轮你“对了我最近在学机器学习。”第十轮你“你觉得数据分析师该怎么提升”此时AI的回复可能会提及“Python”或“机器学习”因为它从记忆库中检索到了相关记忆。输入/记忆命令可以看到系统检索到的与你最后问题相关的记忆条目。通过这个流程你可以直观地看到AI在对话后期依然能“记得”你在对话初期透露的信息实现了长期记忆的效果。5. 常见问题与排查思路在实现和使用记忆库系统时你可能会遇到以下典型问题。问题现象可能原因排查与解决思路ModuleNotFoundError: No module named langchain依赖未正确安装或虚拟环境未激活。1. 确认已激活虚拟环境。2. 运行pip list | grep langchain检查是否安装。3. 重新执行pip install -r requirements.txt或手动安装。openai.AuthenticationErrorOpenAI API Key 错误或未设置。1. 检查.env文件中的OPENAI_API_KEY是否正确无误。2. 确保.env文件与主程序在同一目录或已被加载。3. 在代码中打印os.getenv(‘OPENAI_API_KEY’)的前几位确认是否加载成功。向量数据库检索结果不相关1. 嵌入模型不匹配。2. 记忆文本质量差。3. 检索数量K设置不当。1. 确保存储和检索使用相同的嵌入模型 (text-embedding-ada-002)。2. 优化记忆生成策略确保存入的是清晰、独立的“事实单元”。3. 调整retrieve_relevant_memories中的k参数尝试 2, 5, 10。AI回复未利用记忆1. 记忆未正确注入上下文。2. 提示词System Prompt未引导AI使用记忆。3. 记忆检索为空。1. 调试_format_context函数打印出最终发送给LLM的完整上下文检查记忆是否在内。2. 强化System Prompt明确指示AI“请参考以下记忆进行回复”。3. 检查retrieve_relevant_memories的返回结果确保有记忆被检索到。Token超限或响应慢1. 上下文过长。2. 总结或嵌入生成耗时。1. 使用tiktoken严格计算上下文token数限制短期历史长度。2. 将记忆总结设置为异步或后台任务不阻塞主聊天流程。3. 考虑对长记忆进行分块存储。ChromaDB 持久化文件问题数据库文件损坏或权限问题。1. 删除./chroma_db目录让程序重新创建。2. 检查磁盘空间和文件读写权限。6. 最佳实践与工程建议将记忆库投入实际项目时以下经验可以帮助你构建更健壮、高效的系统。6.1 记忆的粒度与质量原子化存储每条记忆应尽可能描述一个独立的事实或观点例如“用户不喜欢香菜”而不是混合多个主题的段落。这有助于提高检索准确率。去重与合并在存入新记忆前可以先检索相似记忆。如果高度相似可以选择更新旧记忆而非新增避免冗余。例如用户说“我今年25岁”一段时间后又说“我下个月就26了”系统应能更新年龄记忆。重要性评分可以让LLM在生成记忆时附带一个重要性分数1-5。在检索时可以结合相似度和重要性进行加权排序优先返回更关键的记忆。6.2 检索优化策略混合检索结合向量检索语义相似和关键词检索精确匹配。例如对于人名、产品代号等专有名词关键词检索可能更有效。可以使用LangChain的Retriever组件轻松实现混合检索。元数据过滤为记忆添加丰富的元数据如user_id,session_id,topic,created_at。检索时可以先按元数据过滤如只查当前用户的记忆再进行向量搜索大幅提升效率和准确性。递归检索先检索到一些相关记忆然后用这些记忆的内容构成新的查询进行二次检索以挖掘更深层次的关联信息。6.3 系统架构与性能异步处理记忆的总结、向量化、存入数据库等操作可以设计为异步任务不阻塞用户收到即时回复的主链路。缓存机制对于频繁被检索的“热记忆”可以将其文本缓存在内存中避免每次都对相同的查询进行向量数据库IO操作。生产级向量数据库对于数据量大、要求高可用的生产环境可以考虑将ChromaDB替换为Pinecone、Weaviate或Qdrant等云服务或可集群部署的开源方案。记忆生命周期管理实现TTL生存时间机制自动清理过于陈旧的记忆。或者实现基于访问频率的“记忆衰减”很少被访问的记忆逐渐被遗忘。6.4 提示词工程清晰的上下文格式在给LLM的上下文中严格区分“系统指令”、“长期记忆”、“短期对话”和“当前问题”。使用清晰的分隔符如有助于模型理解不同部分的信息。指令明确在System Prompt中明确告诉AI如何使用记忆例如“以下是与你当前问题可能相关的历史记忆请仔细参考它们来形成更准确、个性化的回复。如果记忆与当前问题无关可以忽略。”处理记忆冲突当检索到的多条记忆存在矛盾时可以在提示词中要求AI进行判断或询问用户澄清。例如“我发现关于你的居住地有两条记录‘住在北京’和‘搬到上海’请问你现在常住哪里”6.5 安全与隐私数据加密存储在向量数据库中的记忆文本和向量如果涉及敏感信息应考虑加密存储。用户数据隔离严格通过user_id等元数据隔离不同用户的记忆防止数据泄露。记忆审查与删除必须提供用户查看、管理和删除其个人记忆的接口这不仅是良好的用户体验也是合规性如GDPR的要求。避免记忆偏见注意记忆的生成过程可能引入总结性偏差。定期审查自动生成的记忆确保其客观性。通过本文的讲解和实战你已经掌握了为AI聊天机器人构建记忆库的核心方法。从理解向量检索原理到使用LangChain和ChromaDB实现存储与检索再到设计记忆的生成、更新策略我们完成了一个可运行的原型系统。这套系统的价值在于它让AI应用突破了静态提示词和短暂对话历史的限制向更人性化、更智能的长期交互迈出了一步。你可以在此基础上继续探索集成到Web应用或聊天界面中。尝试不同的嵌入模型如开源模型。实现更复杂的记忆图Memory Graph刻画记忆间的关系。结合AI Agent框架让记忆驱动自主行动。记忆是智能的基石。希望这个项目能成为你探索更强大AI应用的一个起点。如果在实践过程中遇到问题欢迎在评论区交流讨论。