公司动态
智能体双层记忆架构:从会话连贯到知识沉淀的工程实践
1. 从“健忘”到“博闻强识”为什么Agent需要双层记忆如果你玩过早期的AI聊天机器人或者用过一些基础的代码助手肯定遇到过这样的场景你刚告诉它你的项目用的是Python 3.9五句话之后它又建议你安装一个只兼容Python 2.7的库或者在一个冗长的对话里你费尽心思解释了整个系统的架构但当你回过头问它“我们刚才说的数据库选型是什么”时它却一脸茫然地开始胡诌。这种“金鱼式”的记忆七秒就忘是早期智能体Agent最让人抓狂的短板。问题的核心在于记忆的单一性与需求的复杂性不匹配。传统的会话式AI其记忆模型可以简单理解为“一个不断滚动的上下文窗口”。就像你只能记住最近几分钟内别人对你说的话超出窗口长度的信息就被“遗忘”了。这对于一次性的、简短的问答是足够的但一旦我们要构建一个能持续工作、积累经验、甚至拥有“个性”的智能体这种健忘症就成了致命伤。我们真正需要的是一个能像人类一样拥有“短期工作记忆”和“长期知识库”的智能体。短期工作记忆负责处理当前任务的上下文保持会话的连贯性比如记住我们正在讨论的API接口参数格式。而长期知识库则用于沉淀跨会话的、结构化的知识比如用户偏好的代码风格、项目特定的配置规则、或者从历史错误中总结出的避坑指南。这就是“双层记忆”架构要解决的核心问题让Agent不仅能聊得下去还能学得进去越用越聪明。从网络上的热议也能看出这已经是Agent开发领域最迫切的实践需求之一。无论是开发者抱怨Claude Code切换文件夹后丢失上下文还是用户苦恼于Kimi因会话过长而建议重启亦或是社区在探索LangGraph的长期记忆方案其本质都是在呼唤一个更健壮、更持久的记忆系统。本文将从一个实践者的角度拆解如何为你的Agent构建这样一个从“会话连续”到“长期沉淀”的双层记忆体系并聚焦于一个非常实用的工具——PostgresSaver来落地实现。2. 拆解“双层记忆”会话记忆与知识记忆的协同作战在动手写代码之前我们必须从概念上厘清这两层记忆各自扮演什么角色以及它们如何协同工作。理解这一点是设计出高效、稳定记忆系统的前提。2.1 会话记忆保障任务连贯性的“白板”会话记忆或称短期记忆、工作记忆是Agent执行当前任务的“思维白板”。它的核心特征是高时效性、强相关性和有限的容量。高时效性记忆内容与当前对话轮次紧密绑定。你刚刚上传的文件、最近十条消息的历史、用户当前明确指代的指令如“用上面提到的函数”都属于这个范畴。强相关性记忆内容彼此关联共同构成一个完整的任务上下文。例如在代码调试对话中错误信息、你尝试的修复方法、以及系统当前的运行状态这些信息相互引用缺一不可。有限容量受限于模型上下文长度如GPT-4的128K Tokens和计算成本我们不可能无限制地堆砌历史消息。因此会话记忆需要智能的“修剪”策略比如保留最重要的系统指令、最近的几轮对话和关键的工具调用结果。在实际架构中会话记忆通常由对话历史管理器如LangChain的ConversationBufferWindowMemory或更高级的总结提炼器如ConversationSummaryMemory来实现。它的目标是确保Agent在单次会话中“不跑偏”能基于完整的上下文做出合理决策。2.2 知识记忆实现能力进化的“笔记本”知识记忆或称长期记忆是Agent的“经验笔记本”和“知识库”。它的核心特征是持久化、结构化、可检索。持久化记忆内容必须被存储在外部介质数据库、向量库、文件系统中跨越会话周期而存在。关闭应用、重启服务知识都不会丢失。结构化原始对话是流式的、非结构化的。有效的长期记忆需要被提取、加工成结构化的知识单元。这可能是一个问题解决方案对一个实体属性条目或者一段带有标签的代码片段。可检索当新任务到来时Agent需要能快速从海量长期记忆中找到最相关的历史经验来辅助决策。这通常依赖向量化检索Embedding Vector Search或传统数据库查询。知识记忆的实现更为复杂它涉及知识提取何时、何地、保存什么、知识存储存到哪里、以什么格式和知识召回如何快速找到需要的知识三个关键环节。这也是双层记忆系统中最具挑战和价值的部分。2.3 协同机制记忆的流动与触发两层记忆并非孤立的仓库它们之间存在着动态的数据流动。从会话到知识沉淀并非所有聊天内容都值得长期保存。我们需要设定规则或训练模型来识别“有价值”的信息。例如任务成功闭环后当一个复杂的调试任务被解决自动将“问题现象-根因分析-解决方案”三元组保存到知识库。用户显式指令当用户说“记住我更喜欢用axios而不是fetch”这是一个明确的保存信号。基于重要性的自动筛选通过分析对话中实体、决策点的出现频率和位置自动提取关键结论。从知识到会话赋能当新会话开始时或会话中遇到特定问题时Agent应主动查询知识库。会话初始化加载根据用户身份或会话主题预加载相关的个性化偏好或领域知识到本次会话的上下文窗口。运行时按需检索当Agent处理任务遇到困难或用户提到某个历史概念时自动触发对知识库的检索并将检索结果作为背景信息插入当前上下文。这个“沉淀-召回”的闭环正是Agent实现能力迭代和个性化的关键。接下来我们将聚焦于一个能优雅支撑这套体系特别是长期记忆存储环节的具体工具PostgresSaver。3. 核心工具深潜用PostgresSaver构建可靠的记忆存储层当我们谈论长期记忆的“持久化”存储时选择很多比如简单的JSON文件、SQLite、乃至专业的向量数据库如Chroma Pinecone。那么为什么我要特别推荐PostgresSaver尤其是在你已经开始使用LangChain或LangGraph这类框架时3.1 为什么是Postgres不仅仅是关系型数据库PostgresSaver顾名思义其存储后端是PostgreSQL。这个选择背后有非常务实的考量结构化与半结构化的完美平衡Agent的记忆内容往往是半结构化的。例如一次工具调用的记录需要包含工具名、输入参数、输出结果、时间戳、会话ID等结构化字段而输入输出本身可能是复杂的JSON对象。PostgreSQL的JSONB字段类型专门为此而生它允许你以二进制格式高效存储和查询JSON数据同时还能利用索引加速检索。这是纯文档数据库或简单键值对存储难以媲美的优势。强大的查询能力与事务支持当你需要回答“用户A在上周所有会话中最常调用的三个工具是什么”这类复杂查询时SQL的强大便显现出来。联表查询、聚合函数、窗口函数等可以让你对Agent的行为进行深度分析。ACID事务保证也确保了记忆写入的可靠性不会因为系统崩溃导致记忆碎片化。与向量扩展的无缝结合pgvector这是杀手级特性。PostgreSQL可以通过pgvector扩展直接支持向量数据类型和相似度搜索。这意味着你可以在同一数据库、甚至同一张表里既用结构化字段如session_id, tool_name做精确过滤又用向量字段存储对话内容的embedding做语义相似度检索。你不再需要维护一个关系型数据库和一个向量数据库两套系统极大简化了架构和运维。生态与可靠性PostgreSQL是久经考验的企业级数据库在连接池、备份恢复、监控等方面有极其成熟的生态。LangChain/LangGraph社区对Postgres的支持也非常友好PostgresSaver作为内置组件集成起来非常顺畅。3.2 PostgresSaver 实战安装、配置与基础使用假设我们基于LangGraph来构建Agent并使用其提供的PostgresSaver来持久化检查点Checkpoint这天然就成为了我们保存Agent状态包含记忆的机制。步骤1环境准备与安装首先确保你有可用的PostgreSQL数据库版本12以上。可以通过Docker快速启动一个docker run -d --name my-postgres -e POSTGRES_PASSWORDmysecretpassword -p 5432:5432 postgres:15然后在你的Python项目中安装必要依赖pip install langgraph langchain postgresql-client psycopg2-binary pgvector # 或者使用异步驱动 asyncpg # pip install asyncpg步骤2数据库初始化连接到你的PostgreSQL创建数据库并启用pgvector扩展。-- 在psql中或通过管理工具执行 CREATE DATABASE agent_memory; \c agent_memory; CREATE EXTENSION IF NOT EXISTS vector;步骤3在LangGraph中配置PostgresSaver现在你可以在构建你的Agent图Graph时将PostgresSaver作为检查点存储器注入。from langgraph.checkpoint.postgres import PostgresSaver import asyncpg import asyncio async def get_saver(): # 创建数据库连接池。使用连接池是生产环境的最佳实践。 conn await asyncpg.create_pool( hostlocalhost, port5432, userpostgres, passwordmysecretpassword, databaseagent_memory, min_size5, # 连接池最小连接数 max_size20 # 连接池最大连接数 ) # 初始化PostgresSaver它会自动创建所需表如果不存在 saver PostgresSaver(conn) # 可选的设置序列化器默认使用json # from langchain_core.load import dumps, loads # saver PostgresSaver(conn, serdedumps, deserdeloads) return saver # 在你的主程序中异步获取saver async def main(): saver await get_saver() # 假设你正在构建一个Graph from langgraph.graph import StateGraph, END from your_agent import YourState, your_node_function workflow StateGraph(YourState) workflow.add_node(agent, your_node_function) workflow.set_entry_point(agent) workflow.add_edge(agent, END) # 关键步骤将saver绑定为图的检查点管理器 app workflow.compile(checkpointersaver) # 现在app的运行状态会自动保存到PostgreSQL # 使用 config 中的 configurable 来指定线程ID即会话ID config {configurable: {thread_id: user_123_session_1}} initial_state {messages: [(human, 你好请帮我分析这段代码。)]} async for event in app.astream(initial_state, configconfig): # 处理事件... print(event) # 下次可以用相同的 thread_id 恢复状态记忆得以延续 resumed_state await app.aget_state(config) print(f恢复的会话有 {len(resumed_state.values[messages])} 条消息。) # 运行 asyncio.run(main())通过以上配置你的Agent在每次执行完一个节点或你设定的检查点频率后其整个状态包括所有的消息历史、内部变量都会被序列化并保存到Postgres中。thread_id是恢复会话的关键它就像是一个唯一的会话标识符。3.3 进阶自定义记忆结构与混合检索默认情况下PostgresSaver保存的是整个图的状态快照。但对于“长期知识沉淀”我们可能需要更精细的控制。方案一利用状态快照中的特定字段你可以在你的State定义中专门开辟一个字段用于存放需要长期沉淀的知识摘要。from typing import TypedDict, Annotated, List from langgraph.graph import add_messages import operator class AgentState(TypedDict): messages: Annotated[List, add_messages] # 会话消息 conversation_summary: str # 本轮对话的摘要 knowledge_snippets: List[dict] # 本轮产生的待沉淀知识片段 # ... 其他状态然后在你的Agent逻辑中在任务完成时将knowledge_snippets里的内容提取出来通过另一个异步任务写入到另一张专门的知识库表中。这张表可以有自己的schema例如(id, user_id, topic, content, embedding, created_at)。方案二旁路存储与向量化这是更常见的生产级做法。在Agent运行的关键节点如生成最终答案、用户给出正面反馈后触发一个“记忆沉淀”函数。from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores.pgvector import PGVector from langchain.schema import Document class KnowledgeManager: def __init__(self, connection_string): self.embeddings OpenAIEmbeddings() # 使用PGVectorLangChain组件连接同一Postgres但使用不同的表 self.vector_store PGVector( embedding_functionself.embeddings, collection_nameagent_knowledge, connection_stringconnection_string, ) async def save_knowledge(self, session_id: str, query: str, answer: str, metadata: dict): 将Q-A对保存为向量化知识 # 1. 创建文档 doc Document( page_contentfQ: {query}\nA: {answer}, metadata{ session_id: session_id, source: agent_dialogue, created_at: datetime.utcnow().isoformat(), **metadata # 可以包含topic, project等自定义标签 } ) # 2. 添加到向量库PGVector会处理embedding和存储 await self.vector_store.aadd_documents([doc]) async def search_knowledge(self, query: str, k3): 检索相关知识 docs await self.vector_store.asimilarity_search(query, kk) return docs # 在Agent节点中调用 async def agent_node(state: AgentState): # ... Agent处理逻辑 ... if task_is_completed_and_valuable(state): knowledge_manager get_knowledge_manager() # 获取全局管理器 await knowledge_manager.save_knowledge( session_idstate.config[thread_id], querystate[last_user_query], answerstate[last_agent_response], metadata{topic: python_debug, complexity: high} ) # ... 返回新状态 ...这样你就建立了一个独立的、基于向量检索的长期知识库。当新会话开始时你可以先根据用户问题检索相关历史知识并将其作为系统提示的一部分注入实现“长期记忆”到“短期会话”的赋能。4. 避坑指南双层记忆系统构建中的常见陷阱与解决方案构建一个稳定可用的双层记忆系统远不止调用几个API那么简单。下面是我在多个项目中趟过的坑以及对应的解决方案。4.1 记忆泛滥与存储爆炸如何设定沉淀规则问题如果毫无选择地将所有对话都存入长期记忆知识库会迅速被低质量、重复或琐碎的信息淹没导致检索效率下降甚至引入噪声干扰Agent判断。解决方案设计精细化的沉淀触发规则。基于置信度过滤只保存Agent高置信度例如自身评估分数高的答案。可以利用LLM自我评估Self-Critique或工具调用成功与否作为指标。基于信息熵过滤避免保存“好的”、“谢谢”、“我明白了”这类低信息量的内容。可以简单计算文本长度、关键词密度或使用更复杂的文本摘要模型来判断信息价值。基于用户反馈过滤这是最有效的信号。当用户明确给出“点赞”、“收藏”或“这个很有用”的反馈时无论是显式还是隐式触发保存。可以结合langgraph的Human-in-the-Loop节点来实现。结构化提取而非全文保存不要保存原始对话流。而是定义一个模板如“问题”“根因”“解决方案”“适用场景”用一个小型LLM如GPT-3.5-turbo从对话中提取结构化信息后再保存。这极大减少了存储空间并提升了后续检索的准确性。4.2 记忆冲突与信息过时如何维护知识库的“新鲜度”问题知识会过时。例如项目从Webpack 4升级到了Vite但知识库里还保存着基于Webpack 4的构建方案。或者关于同一个问题存在多个相似但略有矛盾的答案该相信哪一个解决方案建立知识库的维护与更新机制。版本化与时效性标签为每条知识记录添加版本号或有效截止日期字段。在检索时可以优先返回版本最新或仍在有效期内的知识。基于来源的权重系统为不同来源的知识赋予不同权重。例如“官方文档”权重为1.0“社区高赞答案”为0.8“Agent自行总结”为0.6。当出现冲突时优先采用高权重来源。也可以记录每条知识的被引用次数或用户好评次数动态调整权重。定期回顾与清理作业设置一个后台任务定期如每周扫描知识库。可以查找语义高度重复的知识条目进行去重或合并。对长时间未被检索或使用的“冷知识”进行归档或删除。使用最新的基础LLM对旧知识条目进行“有效性评估”标记可能过时的内容。4.3 检索效率与精准度如何让Agent快速找到“对的”记忆问题知识库大了以后简单的向量相似度搜索可能返回不相关的结果。比如搜索“Python列表去重”可能返回一篇讲“JavaScript数组去重”的文章因为它们在语义上很接近。解决方案采用混合检索Hybrid Search策略。关键词过滤 向量检索这正是PostgreSQL pgvector的优势。你可以在执行向量相似度搜索的同时用SQL的WHERE子句进行精确过滤。-- 假设我们的知识表有 embedding (vector), topic (text), language (text) 字段 SELECT content FROM knowledge_base WHERE language python AND topic performance -- 精确过滤 ORDER BY embedding [你的问题向量] -- 向量相似度排序 LIMIT 5;在LangChain的PGVector中可以通过filter参数实现。docs await vector_store.asimilarity_search( query列表去重最快的方法, k5, filter{language: python, topic: data_structure} # 元数据过滤 )查询重写Query Rewriting在检索前先用LLM对用户原始查询进行优化和扩展。例如将“怎么让它跑快点”重写为“优化Python程序执行性能的方法与技巧”。这能显著提升向量检索的命中率。重排序Re-ranking先通过向量检索召回一批比如50条候选文档再用一个更精细但更耗资源的重排序模型如Cohere的rerank API或交叉编码器对Top K的结果进行精排选出最相关的3-5条。这是平衡召回率与精确度的有效手段。4.4 隐私、安全与数据隔离问题记忆系统可能保存敏感信息如代码片段、业务数据、用户偏好。如何保证不同用户、不同项目间的记忆隔离解决方案在数据模型和检索层面实施隔离。多租户数据模型在数据库设计时每条知识记录都必须包含tenant_id租户ID可以是用户ID、组织ID或项目ID字段。所有的CRUD操作都必须带上tenant_id作为过滤条件。PostgresSaver的thread_id可以设计为包含用户前缀如user_{id}_session_{sid}。检索时强制过滤在KnowledgeManager的搜索接口中强制传入当前会话的tenant_id并将其作为元数据过滤条件确保用户A永远检索不到用户B的数据。敏感信息脱敏在记忆沉淀之前可以增加一个脱敏环节使用正则表达式或NER模型识别并替换掉密码、API密钥、内部IP等敏感信息。构建一个健壮的双层记忆系统是一个需要持续迭代和调优的过程。从最基础的会话状态持久化开始逐步引入向量检索、混合查询、知识提炼和库维护机制你的Agent才能真正从“对话工具”进化为“智能伙伴”。记住好的记忆系统是“润物细无声”的它让Agent变得更聪明、更贴心而用户感知到的只是越来越顺畅和精准的交互体验。