1. 项目概述从“健忘”的聊天到“有记忆”的智能体如果你尝试过与早期的AI助手进行多轮对话大概率会遇到这样的窘境当你聊到第10轮兴致勃勃地追问“那我们刚才讨论的那个方案它的第三步具体是什么来着”AI助手很可能给你一个礼貌但令人沮丧的回答“抱歉我无法访问之前的对话内容。” 这感觉就像和一个金鱼聊天记忆只有七秒。我们今天要聊的“OpenClaw 会话与记忆”正是为了解决这个核心痛点——让AI智能体Agent真正“记住”对话的上下文而不仅仅是进行一轮又一轮彼此孤立的问答。简单来说这个项目探讨的是如何为基于OpenClaw框架或类似Agent框架构建的智能体赋予长期、结构化、可管理的记忆能力。这远不止是技术上的“上下文窗口”延长。传统的多轮聊天模型只是被动地接收一串越来越长的文本作为输入记忆是“扁平”且“易失”的。而“记忆”在这里是一个主动的、可被索引、总结、提取和遗忘的系统。它能让Agent在跨越数天甚至数周的交互中依然记得用户的偏好、未完成的任务、讨论过的关键决策点从而实现真正个性化的、连贯的服务。想象一下你正在和一个AI开发助手协作一个项目。昨天你让它分析了代码库的结构今天你问“我们昨天说的那个冗余模块有重构思路了吗”。一个有记忆的Agent能立刻关联起昨天的会话结合当时的分析结论给出延续性的建议。而一个仅有多轮聊天能力的Agent可能就需要你重新描述一遍整个问题体验断崖式下跌。因此这个主题的核心价值在于提升交互的连续性、智能体的个性化程度以及复杂任务处理的可行性是AI Agent从“玩具”走向“工具”乃至“伙伴”的关键一步。2. 核心需求与挑战拆解记忆不是简单的堆砌为什么给Agent加个记忆这么难直接把所有历史对话记录都塞给模型不就行了吗这里面的水比想象的要深。我们得先理清核心需求和技术挑战。2.1 记忆的三大核心需求长期性Long-term记忆必须能够跨越单次会话Session存在。用户关闭了聊天窗口第二天再打开Agent应该能“认出”老用户并回忆起之前的互动脉络。这要求记忆的存储介质必须是外部的、持久化的比如数据库或向量存储而非仅仅存在于程序运行时的内存中。结构化与可索引性Structured Indexable原始对话记录是线性的、冗长的文本流。有效的记忆需要被提炼、打上标签、转换成便于检索的结构。例如将“用户喜欢喝美式咖啡不加糖”提取为{“实体”: “用户偏好” “属性”: “咖啡” “值”: “美式无糖”}这样的结构化信息并存入向量数据库以便日后通过“咖啡”、“偏好”等关键词快速召回。相关性筛选与摘要Relevance Filtering Summarization不可能也无必要将每一句对话都作为记忆。Agent需要具备“判断什么值得记住”的能力。例如闲聊的“今天天气不错”可能无需记忆但“我下周五要去上海出差”则必须记住。更进一步对于一段冗长的技术讨论记忆系统应该能自动生成一个摘要如“讨论了用户认证模块从JWT迁移到OAuth 2.0的可行性主要顾虑是迁移成本”而不是保存全部原始文本。2.2 面临的主要技术挑战上下文长度限制Context Window Limit这是最直接的硬约束。无论是GPT-4还是Claude其处理的上下文都有token数量上限。当对话历史超过这个限制时必须进行裁剪。简单的“掐头去尾”会丢失关键信息因此需要智能的摘要或关键信息提取技术。记忆的准确性与幻觉Accuracy Hallucination由AI来总结和提取记忆本身就可能引入错误或“幻觉”。如果Agent错误地记住了“用户对花生过敏”实际用户说的是“不过敏”后果可能很严重。因此记忆的写入和读取流程需要设计校验机制例如要求用户确认关键事实或提供记忆的来源引用。记忆的更新、冲突与遗忘Update, Conflict Forgetting记忆不是一成不变的。用户可能说“我最近开始喝拿铁了”这就需要更新之前“喜欢美式”的记忆。如果出现冲突信息比如两次对话中地址不一致系统需要有能力检测并解决冲突。同样无用的、过时的信息也需要有“遗忘”机制来清理防止记忆库变得臃肿且低效。多会话记忆隔离与共享Session Isolation Sharing这是“hermes agent官网”和热词中“为什么你的workbuddy记忆会‘乱窜’”所指向的典型问题。在同一个Agent服务多个用户或多个对话线程时用户A的记忆绝不能泄露给用户B。这需要严格的会话隔离机制。但同时也可能存在需要共享的“公共记忆”比如Agent学到的通用知识或公司规章制度。设计清晰的内存作用域用户级、会话级、全局级至关重要。3. 记忆系统的架构设计从理论到蓝图理解了需求和挑战我们就可以着手设计一个实用的记忆系统架构。一个健壮的Agent记忆系统通常不是单一模块而是一个分层、协同工作的体系。这里我结合业界常见模式如“三层记忆架构”思想和OpenClaw这类框架的实际情况提出一个可落地的设计蓝图。3.1 经典的三层记忆模型这个模型借鉴了认知心理学将记忆分为三个层次各有其职感官记忆/短期记忆Sensory / Short-term Memory对应技术实现当前对话的上下文窗口。这是最直接、最快速的记忆但容量小、易消失。通常由大语言模型LLM本身的上下文长度决定。职责处理当前轮次的对话理解最新的用户意图和查询。它是工作台所有即时思考都在这里发生。工作记忆/中期记忆Working / Mid-term Memory对应技术实现向量数据库如Chroma, Pinecone, Weaviate或传统数据库。这是记忆系统的核心枢纽。职责存储保存从短期记忆中提炼出的结构化记忆片段Memories。每个片段包含内容、元数据如时间、会话ID、实体、类型和向量嵌入Embedding。检索根据当前查询通过向量相似性搜索快速从海量记忆中召回最相关的若干条。它是“记住”这个动作的主要发生地。长期记忆Long-term Memory对应技术实现关系型数据库如PostgreSQL、文档数据库或简单的文件系统。用于存储高度压缩、高度结构化的摘要和核心事实。职责摘要与压缩定期如会话结束时对工作记忆中的大量片段进行总结生成一个高度凝练的“用户档案”或“会话摘要”存入长期记忆。核心事实存储保存不容有失的关键信息如用户ID、基础偏好等。它是人格和历史的基石容量大但读取和更新速度较慢通常不用于实时检索。注意在实际工程中“工作记忆”和“长期记忆”的界限有时会模糊很多系统会用同一个向量库同时承担两者角色通过不同的索引或元数据来区分。但概念上的分层有助于我们设计清晰的数据流转逻辑。3.2 记忆处理的核心工作流一个完整的记忆交互周期通常遵循“感知-思考-行动-记忆”的循环具体到记忆模块流程如下记忆写入Write触发时机在Agent对用户消息做出响应后或在一个明确的任务节点完成时。过程系统会分析本轮对话短期记忆判断其中是否有值得长期保存的信息。这可以通过一个独立的“记忆提炼”LLM调用来完成提示词类似于“请从以下对话中提取出关于用户或世界的新的、重要的事实、偏好或任务状态。以JSON格式输出。”输出生成结构化的记忆对象例如{ content: 用户计划在下周五2023-10-27前往上海出差。, type: user_plan, entities: [用户, 上海, 出差], timestamp: 2023-10-20T14:30:00Z, session_id: sess_abc123 }存储将该对象的文本内容转换为向量通过Embedding模型如text-embedding-3-small连同元数据一并存入向量数据库工作记忆。记忆检索Retrieve触发时机在Agent开始思考如何响应用户的新消息时。过程将用户的新查询或结合查询生成的搜索关键词转换为向量在向量数据库中进行相似性搜索。关键技巧检索不是简单的“全文匹配”。为了提高相关性我们常采用“查询扩展”技术。例如用户问“我下周出行准备得怎么样了”系统可以自动衍生出“出差”、“上海”、“行程”等搜索词进行多路检索再将结果去重、排序。输出召回Top-K个最相关的历史记忆片段。记忆整合与呈现Integrate Present过程将检索到的记忆片段与当前的用户问题短期记忆一起组合成一份增强版的上下文Context提交给LLM进行最终的回答生成。提示词设计这里非常关键。你需要明确告诉LLM哪些是历史记忆哪些是当前问题。典型的提示词结构你是一个有帮助的AI助手。以下是一些可能相关的历史背景信息 memory 1 memory 2 ... 当前用户的提问是用户最新问题 请结合上述历史信息如果相关来回答用户的问题。3.3 会话Session管理记忆的容器与边界“会话”是记忆的组织单元也是隔离的边界。一个会话通常对应一次连续的交互过程例如用户打开一个聊天窗口到关闭它。会话创建当新用户访问或用户点击“开始新对话”时系统会创建一个唯一的会话IDSession ID。会话绑定该会话ID会作为元数据与本次交互产生的所有记忆片段、聊天记录关联。记忆隔离在检索记忆时系统通常会默认将搜索范围限定在当前会话ID内或者结合用户ID进行查询确保不会跨会话泄露信息。这就是解决“记忆乱窜”问题的根本方法。会话持久化会话本身的状态如是否活跃、创建时间、最后活动时间也需要被记录和管理便于清理僵尸会话和资源。4. 基于OpenClaw的实战实现与代码解析理论说再多不如一行代码。下面我将以一个简化但完整的示例展示如何在OpenClaw或类似Agent框架中实现一个具备基础会话记忆能力的智能体。我们将使用Python并假设结合LangChain的生态组件来构建。4.1 环境准备与核心组件选型首先明确我们的技术栈Agent框架OpenClaw此处作为概念核心具体实现可能调用其SDK或遵循其设计模式。注OpenClaw的具体API可能变化以下代码为示意性原型。LLM用于生成回答和提炼记忆。例如使用OpenAI的GPT-4或ChatGLM等开源模型。Embedding模型用于将文本转换为向量。例如OpenAI的text-embedding-3-small或开源的BGE-M3。向量数据库用于存储和检索记忆向量。这里我们使用轻量且易上手的ChromaDB内存模式。记忆管理逻辑我们自己编写。安装基础依赖假设环境pip install openai langchain langchain-openai chromadb4.2 构建记忆管理类这是整个系统的引擎。我们创建一个MemoryManager类。import uuid from datetime import datetime from typing import List, Dict, Any, Optional import chromadb from chromadb.config import Settings from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain.schema import HumanMessage, SystemMessage class MemoryManager: def __init__(self, persist_directory: str “./chroma_db”): # 初始化Embedding模型 self.embeddings OpenAIEmbeddings(model“text-embedding-3-small”) # 初始化Chroma客户端持久化存储 self.client chromadb.PersistentClient(pathpersist_directory) # 获取或创建集合类似数据库的表以用户ID为分区 self.collection self.client.get_or_create_collection(name“agent_memories”) # 用于提炼记忆的LLM self.memory_llm ChatOpenAI(model“gpt-4-turbo-preview”, temperature0.1) # 活跃会话字典 {session_id: {‘user_id’: …, ‘created_at’: …}} self.active_sessions {} def create_session(self, user_id: str) - str: 创建一个新的会话 session_id str(uuid.uuid4()) self.active_sessions[session_id] { ‘user_id’: user_id, ‘created_at’: datetime.utcnow().isoformat() } print(f“新会话创建: {session_id} for user {user_id}”) return session_id def extract_memory(self, conversation_turn: str, session_id: str) - Optional[Dict]: 从一轮对话中提炼结构化记忆 prompt f“”” 请从以下助理与用户的对话回合中提取出值得长期记住的、关于用户或世界的**新**事实、偏好、计划或任务状态。 如果没有任何值得记住的新信息直接输出‘NO_MEMORY’。 如果有请以JSON格式输出包含‘content’记忆内容、‘type’如user_preference, user_plan, world_fact、‘entities’涉及的主要实体列表字段。 对话回合 {conversation_turn} “”” messages [SystemMessage(content“你是一个记忆提取专家。”), HumanMessage(contentprompt)] response self.memory_llm.invoke(messages) result response.content.strip() if result “NO_MEMORY”: return None # 简单解析JSON实际应用需更健壮的解析 import json try: memory_data json.loads(result) memory_data[‘id’] str(uuid.uuid4()) memory_data[‘session_id’] session_id memory_data[‘timestamp’] datetime.utcnow().isoformat() memory_data[‘user_id’] self.active_sessions[session_id][‘user_id’] return memory_data except json.JSONDecodeError: print(f“Failed to parse memory JSON: {result}”) return None def store_memory(self, memory_data: Dict): 将记忆存储到向量数据库 if not memory_data: return # 生成向量 vector self.embeddings.embed_query(memory_data[‘content’]) # 准备元数据 metadata { “type”: memory_data.get(‘type’, ‘unknown’), “session_id”: memory_data[‘session_id’], “user_id”: memory_data[‘user_id’], “timestamp”: memory_data[‘timestamp’], “entities”: “,”.join(memory_data.get(‘entities’, [])) } # 存入Chroma self.collection.add( embeddings[vector], metadatas[metadata], documents[memory_data[‘content’]], ids[memory_data[‘id’]] ) print(f“记忆已存储: {memory_data[‘content’][:50]}...”) def retrieve_memories(self, query: str, session_id: str, top_k: int 5) - List[Dict]: 根据查询检索相关记忆 user_id self.active_sessions.get(session_id, {}).get(‘user_id’) if not user_id: return [] # 生成查询向量 query_vector self.embeddings.embed_query(query) # 在向量库中搜索限定于当前用户和会话可选 results self.collection.query( query_embeddings[query_vector], n_resultstop_k, where{“user_id”: user_id} # 可加上 “session_id”: session_id 进行更严格隔离 ) # 整理结果 retrieved_memories [] if results[‘documents’]: for doc, meta in zip(results[‘documents’][0], results[‘metadatas’][0]): retrieved_memories.append({“content”: doc, “metadata”: meta}) return retrieved_memories4.3 集成到OpenClaw Agent主循环假设我们有一个简单的OpenClaw Agent循环现在将记忆管理集成进去。# 主逻辑示例 def main_agent_loop(): memory_manager MemoryManager() llm ChatOpenAI(model“gpt-4”, temperature0.7) # 模拟用户登录/开始对话 user_id “user_001” session_id memory_manager.create_session(user_id) conversation_history [] # 用于存储当前会话的原始对话记录 print(“Agent启动。输入‘exit’退出输入‘new’开始新会话。”) while True: user_input input(“\n用户: “) if user_input.lower() ‘exit’: break if user_input.lower() ‘new’: # 开始新会话旧会话记忆仍保留但检索时会隔离 session_id memory_manager.create_session(user_id) conversation_history [] print(“[新会话已开始]”) continue # 1. 检索相关记忆 relevant_memories memory_manager.retrieve_memories(user_input, session_id) memory_context “” if relevant_memories: memory_context “以下是一些相关的历史信息\n” “\n”.join([f”- {m[‘content’]}” for m in relevant_memories]) # 2. 构建包含记忆的提示词 system_prompt f“””你是一个有帮助的AI助手。{memory_context} 请根据当前对话和上述历史信息如果相关来回答用户。当前对话历史仅本次会话 {‘’.join([f‘{msg[“role”]}: {msg[“content”]}’ for msg in conversation_history[-5:]])} # 保留最近5轮作为短期上下文 “”” messages [ SystemMessage(contentsystem_prompt), HumanMessage(contentuser_input) ] # 3. 调用LLM生成回答 response llm.invoke(messages) agent_response response.content print(f“助手: {agent_response}”) # 4. 保存本轮对话到历史 conversation_history.append({“role”: “user”, “content”: user_input}) conversation_history.append({“role”: “assistant”, “content”: agent_response}) # 5. 尝试从本轮对话中提炼并存储长期记忆 # 将最近一轮的对话拼接成文本供记忆提炼 latest_turn f“用户: {user_input}\n助手: {agent_response}” new_memory memory_manager.extract_memory(latest_turn, session_id) if new_memory: memory_manager.store_memory(new_memory) if __name__ “__main__”: main_agent_loop()4.4 关键代码段解析与实操心得记忆提取的触发上述示例在每一轮对话后都尝试提取记忆。在实际应用中这可能会产生大量冗余或低价值记忆。更好的策略是基于事件触发在检测到用户明确表达偏好、制定计划、完成任务时触发。定时/定量触发每N轮对话后或会话结束时对一段时间内的对话进行批量摘要。使用更精细的提示词让LLM同时输出记忆的“重要性分数”只有高于阈值的才被存储。检索的优化示例中直接使用用户输入作为查询。更优的做法是让LLM根据输入和对话历史生成一个更精准的搜索查询Query Reformulation。例如用户问“之前说的那个东西怎么样了”LLM可以将其重写为“关于[项目X]的进度更新”。向量搜索的元数据过滤collection.query中的where参数非常强大。除了过滤user_id你还可以根据type、timestamp等过滤实现更精确的检索。例如当用户问“我喜欢什么音乐”你可以添加where{“type”: “user_preference”, “entities”: {“$contains”: “music”}}。会话隔离的实现代码中通过在检索时添加where{“user_id”: user_id}来实现用户级隔离。要实现严格的会话级隔离可以加上“session_id”: session_id。“记忆乱窜”往往就是因为这里过滤条件没设对或者存储时忘了给记忆打上正确的会话标签。5. 高级主题与优化策略实现基础记忆后我们可以追求更智能、更高效的系统。5.1 记忆的总结与压缩长期对话会产生海量记忆片段导致检索效率下降和成本增加。我们需要定期对记忆进行总结。会话级总结在一个会话结束时触发一个总结任务。将本会话的所有记忆片段或原始对话交给LLM生成一段连贯的摘要例如“本次会话中用户咨询了Python异步编程的问题重点讨论了asyncio的事件循环和task管理并决定在项目X中尝试使用。” 然后将此摘要作为一个新的、更高级别的“记忆”存入长期存储并可以酌情清理或归档原始的细颗粒度记忆片段。用户级档案更新定期如每10次会话将用户的各类记忆进行整合更新其“用户档案”。这个档案可以是一个结构化的JSON包含“技能”、“偏好”、“正在进行项目”等字段。这个档案本身可以作为一条超级记忆在每次对话开始时优先加载提供最基础的背景。5.2 记忆的更新、冲突解决与遗忘更新机制当检测到新旧记忆冲突时例如旧记忆“地址A”新记忆“地址B”系统应能标记冲突。一种策略是“新记忆覆盖旧记忆”但更稳妥的是引入置信度和来源时间戳。高置信度、更新的记忆可以覆盖旧的。对于关键信息甚至可以设计一个向用户确认的流程“我记得您之前说过地址是A现在更新为B对吗”遗忘机制记忆不是越多越好。可以设计以下策略基于时间的衰减为每条记忆附加一个“上次访问时间”和“强度”。长时间未被检索的记忆其强度逐渐衰减低于阈值后可被归档或删除。基于相关性的清理定期运行聚类算法将相似记忆聚类。只保留每个聚类中最具代表性或最新的一条删除冗余条目。主动遗忘允许用户通过指令管理记忆如“忘记我之前告诉你的关于XX的事情”。5.3 与外部工具和知识库的集成真正的智能体记忆不应局限于对话历史。它应该能记住它“做过”的事情。工具调用记忆当Agent通过工具如执行代码、查询数据库、调用API完成了某项操作这个操作的结果和上下文应该被有选择地存入记忆。例如Agent运行了一个数据分析脚本并得到了图表记忆可以是“已为用户生成关于Q3销售数据的趋势图主要结论是华东区增长显著”。知识库增强检索RAG将记忆系统与企业的知识库、文档库打通。当用户提问时不仅检索个人记忆也检索公共知识库。这需要统一向量化存储和检索接口并在提示词中清晰区分“个人记忆”和“公司知识”。6. 常见问题、排查与性能考量在实际部署中你会遇到各种各样的问题。下面是一些典型场景和解决思路。6.1 记忆检索不相关或遗漏症状Agent的回答明显没有用到本该记得的信息。排查步骤检查存储首先确认你认为应该被记住的信息是否成功生成了记忆对象并存入了向量库。查看数据库记录。检查Embedding模型不同的Embedding模型对同一文本的向量表示差异很大。确保存储和检索使用的是同一个Embedding模型。如果更换模型整个向量库需要重建。检查检索查询打印出用于检索的查询文本和生成的向量。有时用户问题太模糊需要先让LLM重写查询。例如将“那个事”重写为“上周讨论的服务器迁移计划”。调整检索参数增加top_k的返回值数量比如从5调到10。调整相似度阈值过滤掉分数太低的低相关性结果。优化元数据过滤检查where过滤条件是否过于严格误把相关记忆过滤掉了。比如如果记忆的entities字段没有包含某个关键词但内容相关基于元数据的过滤就会失效。此时应更多依赖向量相似度。6.2 记忆提取质量差记了废话或记错症状记忆库里充满了“你好”、“谢谢”这样的无用信息或者关键事实被提取错误。解决方案优化提炼提示词在给LLM的指令中更明确地定义“值得记忆”的标准。例如“只提取关于用户长期偏好、已确认的计划、已完成的任务结果或重要的世界事实。忽略问候语、客套话、未确认的想法和临时性信息。”引入分类器在提炼记忆前先用一个更小的、快速的文本分类模型判断本轮对话是否“可能包含值得记忆的内容”如果没有则跳过调用大模型提炼的步骤节省成本。设置置信度阈值让提炼记忆的LLM输出一个置信度分数只有高置信度的记忆才被存入。6.3 性能与成本问题症状每次对话都调用Embedding和向量检索延迟高、API费用贵。优化策略缓存对相同的查询文本缓存其Embedding向量和检索结果。对于高频但固定的提示词部分如系统指令可以预先计算其Embedding。批量处理不要每轮对话都立即提炼记忆。可以积累若干轮对话后一次性提交给LLM进行批量摘要和提取。分层检索首先用更廉价、快速的方法如基于关键词在元数据中匹配进行初筛得到一小部分候选记忆再对这部分候选记忆进行昂贵的向量相似度计算。使用本地轻量模型对于Embedding可以考虑使用开源的本地模型如BGE-M3避免调用云端API产生的延迟和费用。对于记忆提炼也可以尝试使用中小型模型如Qwen-7B在质量和成本间取得平衡。6.4 会话状态丢失问题症状在长时间运行或分布式部署中active_sessions字典可能丢失导致无法关联会话。解决方案绝对不能将会话状态只保存在内存中。必须将active_sessions字典持久化到外部存储如Redis或数据库。每次请求时根据传入的session_id从外部存储中恢复会话上下文。这是构建生产级Agent服务的基本要求。给Agent赋予记忆是一个从“对话机器”迈向“智能伙伴”的质变过程。它涉及的不只是技术拼接更是对交互本质的理解。从我自己的实践来看最难的不是实现存储和检索而是设计一套符合人类认知习惯的记忆逻辑——什么该记什么该忘如何关联如何更新。这其中的参数和策略需要在具体的业务场景中反复打磨。