资讯动态

AI Agent记忆架构实践:从上下文窗口到长期记忆系统设计

发布时间:2026/9/10 4:52:11 来源:尧图企业网站定制
让 Agent 记住你AI Agent 记忆能力的架构与实践做了这么久的 Agent 开发我越来越发现一个尴尬的事实一个没有记忆的 Agent就像一个每次见面都重新自我介绍的朋友。你上周刚告诉它的信息这周再问它它一脸茫然。尤其是做个人 AI 助手、知识库问答这类应用时这个问题会直接决定用户的去留——谁愿意天天对着一个永远记不住自己的“人工智障”呢这篇文章是“走进 AI Agent”系列的第三篇专门拆解 Agent 的记忆Memory能力。如果你已经跑通了 Agent 的基本对话流程但发现它总是在多轮对话里“失忆”或者想让 Agent 真正像个长期伙伴一样记住你的偏好、习惯和历史决策那么这篇文章正好适合你。我会从记忆类型、方案选型、代码实现到生产环境的坑一条龙讲透。本文里的代码以 Python 为主核心思路不绑定具体框架哪怕你用的是 LangChain、Spring AI 或者其他 Agent 框架也能直接借鉴。1. 记忆不只是“上下文”而是 Agent 的人格底座1.1 为什么说无状态 LLM 撑不起真正的 Agent先聊一个经常被忽视的事实大语言模型本身是无状态的。每次请求都是独立的模型不记得你上一次说了什么。我们平时觉得 ChatGPT 好像记得我本质上是它在同一个会话里把历史消息一股脑塞给了模型——这叫上下文窗口不是真正的记忆。上下文窗口的问题有两个一是容量有限聊长了就溢出只能做截断或摘要二是窗口一关就没了你今天聊完明天再开一个 session它对你依然一无所知。而一个真正意义上的 Agent应该是能跨越会话、跨越时间的。它能记住你的名字、你的项目背景、你上周说过的一个想法甚至能记住你已经排除了某个方案——这些信息构成了 Agent 对你的“认知模型”是它做出个性化决策的基础。所以我的观点很直接记忆层不是 Agent 的加分项而是地基的一部分。没有记忆Agent 就是一个功能测试合格的机器人有记忆它才开始像“你的”助手。1.2 拆开看Agent 记忆至少分四种类型既然要做记忆就得先搞清楚“记什么”。我在实操中习惯把 Agent 的记忆分成四类这个分法和认知科学的记忆分类有些渊源但更贴合工程实现记忆类型核心作用典型实现方式类比短期记忆维持当前多轮对话的连贯性对话历史拼接、上下文缓存你聊天时脑子里还装着前几句话长期记忆跨会话保存用户事实与偏好数据库 向量检索你记着朋友的生日和忌口情景记忆记录发生过的事件与决策过程时序日志、事件存储你记得上周三开会决定了什么程序记忆固化可复用的技能与流程工具定义、Prompt 模板、Skill 库你知道怎么做红烧肉的步骤很多初学者会把“记忆”直接等同于“长期记忆”然后一股脑做向量库结果对话里该记的短期信息反而丢了。一个完整的 Agent 记忆系统应该是多种记忆协同工作的。短期记忆保证当下对话不接错话长期记忆保证跨时间的个性化情景记忆提供“我们之前聊过什么”的上下文程序记忆则让 Agent 越用越顺手。1.3 上下文窗口和长期记忆的正确分工这里必须澄清一个容易走偏的点不要试图用上下文窗口代替长期记忆也不要用长期记忆塞满上下文。我见过不少团队为了“让 Agent 记住用户”把用户的所有历史聊天记录全部塞进 Prompt 里结果要么爆 token要么模型被海量无效信息干扰回答质量直线下降。正确的分工是上下文窗口负责承载当前任务需要的那一小部分相关信息长期记忆负责存储和索引全部历史信息在需要时按相关性召回一小部分注入上下文。打个比方上下文窗口是你的工作台长期记忆是仓库。你不会把仓库里的所有货都堆到工作台上你需要什么就去仓库里按需调取。所以长期记忆系统的核心能力其实就两个存得进去、拿得出来。2. 记忆系统的整体设计先定架构再写代码2.1 一个最小可用的记忆系统由哪几层组成在动手写第一行代码前我建议先把记忆系统的边界画清楚。我现在的标准做法是分成四层接入层负责接收需要记忆的数据来源可能是用户对话、Agent 执行日志、外部文档等。在接入层通常会做一轮清洗和提炼不是所有原始数据都值得进记忆库。存储层决定数据以什么形态落地。常用的有向量数据库用于语义检索、关系型数据库用于结构化事实、键值存储用于 KV 型简单信息。三者不是互斥关系实际项目中常常混合使用。检索层负责在需要时把最相关的记忆捞出来。这一步是记忆系统体验好坏的分水岭纯向量召回往往不够还要叠加过滤条件、时间衰减、重要性加权等策略。应用层把检索到的记忆注入 Prompt或通过工具接口暴露给 Agent 调用。四层的职责很清楚耦合度也低。哪怕你现在只是做一个个人小项目我也劝你按这个分层来组织代码否则后面加功能的时候会非常痛苦。2.2 方案选型别一上来就上向量数据库现在的技术社区里一说做 Agent 记忆很多人第一反应就是“用向量数据库 Embedding”。但以我踩坑的经验来看向量库只是长期记忆的一种实现手段不是唯一方案也不是所有场景的最优解。这里给你一张方案对比表是我多次项目选型时用的实现方案优点缺点适用场景纯关系型数据库SQLite/PostgreSQL精确匹配、事务性好、易维护语义检索能力弱用户画像、KV 型事实、偏好记录向量数据库Chroma/pgvector/Milvus语义相似度检索强、支持非结构化数据精确匹配弱、需要 Embedding 成本文档记忆、历史对话片段、模糊回忆混合方案关系库 向量库兼顾精确与语义架构复杂、两套数据要同步生产级 Agent、知识库型助手纯缓存式Redis速度极快容量有限、无持久化能力短期记忆、会话级状态管理我的个人推荐组合是短期内做 MVP用 SQLite 一个轻量向量索引就够了做到生产级优先选支持向量检索的 PostgreSQLpgvector这样关系数据和向量数据可以放在一个库里运维成本直线下降。注意不要一开始就把系统拆成 MySQL Redis Milvus 三件套你会花大量时间在同步数据上而不是调记忆效果。2.3 数据结构设计记忆也需要 Schema很多人在做记忆系统时犯的错误是把记忆当成一个“大杂烩”抓到什么存什么存的时候也不管字段最后检索的时候一团乱。我建议至少在逻辑上给每条记忆定义一个标准结构我习惯把它称为MemoryRecordid全局唯一标识用于更新和删除。user_id这条记忆属于哪个用户。多用户系统必须有这一层隔离。type记忆类型比如user_fact用户事实、event历史事件、preference偏好、task_state任务状态。content记忆的正文内容。可以是一句话、一段摘要甚至是一段代码。metadataJSON 字段存放结构化属性比如“这条记忆涉及的项目名称”“这条记忆的来源渠道”。importance重要度评分初始可以设为 0.5后续可以按使用频率自动调整。last_accessed_at最近被召回的时间用于时间衰减和清理策略。created_at / updated_at创建和更新时间。这里有两个字段经常被新手忽略importance和last_accessed_at。它们决定了记忆系统能不能“分清轻重”。一个人和你聊了 100 句无关紧要的寒暄其中夹杂了一句“我下个月搬家”如果这条记忆没有打到高优先级过几天检索你可能就回不来这条关键信息了。3. 核心实现用 Python 做出一个能存能取的记忆库3.1 用 SQLite 打底写一个最小的记忆存储层我建议从 SQLite 开始因为它零部署、零依赖单文件特别适合做 MVP 和本地跑通流程。下面是这个最小记忆库的建表语句CREATE TABLE IF NOT EXISTS memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, type TEXT NOT NULL, content TEXT NOT NULL, metadata TEXT, importance REAL DEFAULT 0.5, last_accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_memories_user_type ON memories(user_id, type); CREATE INDEX IF NOT EXISTS idx_memories_user_importance ON memories(user_id, importance DESC);注意这里建了两个索引一个用于按用户和类型过滤一个用于按重要度排序。真到了生产环境你还会加last_accessed_at的索引用来做定期清理和记忆衰减。对应的 Python 封装类大概长这样import json import sqlite3 from datetime import datetime class MemoryStore: def __init__(self, db_path: str agent_memory.db): self.conn sqlite3.connect(db_path) self.conn.row_factory sqlite3.Row self._init_schema() def _init_schema(self): self.conn.executescript( CREATE TABLE IF NOT EXISTS memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, type TEXT NOT NULL, content TEXT NOT NULL, metadata TEXT, importance REAL DEFAULT 0.5, last_accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_memories_user_type ON memories(user_id, type); CREATE INDEX IF NOT EXISTS idx_memories_user_importance ON memories(user_id, importance DESC); ) def add_memory(self, user_id: str, type_: str, content: str, metadata: dict None, importance: float 0.5) - int: cursor self.conn.execute( INSERT INTO memories (user_id, type, content, metadata, importance) VALUES (?, ?, ?, ?, ?) , (user_id, type_, content, json.dumps(metadata or {}), importance), ) self.conn.commit() return cursor.lastrowid def get_memories_by_type(self, user_id: str, type_: str, limit: int 20): cursor self.conn.execute( SELECT * FROM memories WHERE user_id ? AND type ? ORDER BY importance DESC, last_accessed_at DESC LIMIT ? , (user_id, type_, limit), ) return [dict(row) for row in cursor.fetchall()] def delete_memory(self, memory_id: int): self.conn.execute(DELETE FROM memories WHERE id ?, (memory_id,)) self.conn.commit()这段代码本身很简单但你品一下里面的两个设计点。一是type字段它让记忆库不只是一个“信息垃圾桶”你可以针对不同业务场景写不同的召回逻辑二是importance DESC, last_accessed_at DESC的排序它保证默认召回时重要的和最近用过的记忆排在前面。3.2 把非结构化文本向量化Embedding 的两个接入方案只有 SQLite 还不够因为用户说话是千奇百怪的。用户上次说“我特别喜欢喝手冲咖啡”这次可能问“帮我推荐个咖啡豆”如果只靠 SQL 的 LIKE 匹配是永远匹配不上的。所以我们需要把记忆内容转成向量做语义检索。Embedding 的接入方案我基于成本和使用场景推荐两个方案 A调托管 API。比如 OpenAI 的 text-embedding-3-small、或者国内各种大模型平台提供的 Embedding 接口。优点是效果稳定、不用自己维护模型缺点是按量计费在批量写回忆时会有一笔开销且依赖网络。方案 B本地跑开源模型。比如BAAI/bge-small-zh-v1.5、sentence-transformers/all-MiniLM-L6-v2。最近两年开源 Embedding 模型的中文效果已经很能打了本地推理一次只需几十毫秒完全够用。缺点是首次需要下载模型对机器内存有一定要求。我给一个兼容两种方案的函数封装你按自己的环境选择即可def get_embedding(text: str) - list[float]: # 方式一调用 API这里以 OpenAI 风格接口为例 # response openai.Embedding.create( # modeltext-embedding-3-small, # inputtext # ) # return response[data][0][embedding] # 方式二本地模型 from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-small-zh-v1.5) return model.encode(text, normalize_embeddingsTrue).tolist()实际项目中你也可以把模型实例全局缓存避免每次调用都加载一遍。这个细节后面在“常见问题”里再细说。3.3 实现语义检索余弦相似度的工程化写法有了向量接下来是检索。在向量数据库里这一步通常由数据库内置能力完成但为了把原理讲清楚我手工实现一个最小版本的余弦相似度检索import numpy as np import sqlite3 class VectorMemoryStore(MemoryStore): def __init__(self, db_path: str agent_memory.db): super().__init__(db_path) self._ensure_vector_table() def _ensure_vector_table(self): self.conn.executescript( CREATE TABLE IF NOT EXISTS memory_vectors ( memory_id INTEGER PRIMARY KEY, vector BLOB NOT NULL, FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE ); ) def add_memory_with_vector(self, user_id: str, type_: str, content: str, metadata: dict None, importance: float 0.5): memory_id self.add_memory(user_id, type_, content, metadata, importance) embedding get_embedding(content) vector_bytes np.asarray(embedding, dtypenp.float32).tobytes() self.conn.execute( INSERT INTO memory_vectors (memory_id, vector) VALUES (?, ?), (memory_id, vector_bytes), ) self.conn.commit() return memory_id def semantic_search(self, query: str, user_id: str, top_k: int 5): query_vec np.asarray(get_embedding(query), dtypenp.float32) rows self.conn.execute( SELECT m.id, m.content, m.type, m.metadata, m.importance, m.last_accessed_at, mv.vector FROM memories m JOIN memory_vectors mv ON m.id mv.memory_id WHERE m.user_id ? , (user_id,), ).fetchall() scored [] for row in rows: vec np.frombuffer(row[vector], dtypenp.float32) # 余弦相似度 score float(np.dot(query_vec, vec) / (np.linalg.norm(query_vec) * np.linalg.norm(vec))) scored.append((score, row)) scored.sort(keylambda x: x[0], reverseTrue) return [ { id: row[id], content: row[content], type: row[type], metadata: json.loads(row[metadata] or {}), importance: row[importance], score: score, } for score, row in scored[:top_k] ]这里有个工程细节要注意每次检索都要全表扫描内存里算一遍相似度在小数据量几千条没问题但数据量大了以后必须换用真正的向量索引比如 pgvector 的 HNSW 索引或者 Chroma/Milvus。我见过有人用这个 demo 代码硬扛一万多条数据结果检索耗时飙升到两秒以上对话体验直接垮掉。3.4 让检索结果更聪明重要度加权和时间衰减很多教程到“向量检索能跑通”就结束了但实际体验往往不理想。原因在于纯语义相似度会把那些相关但其实没用的旧记忆也捞出来。你需要对召回结果做重新排序我常用的重排公式很简单def rerank(self, score: float, importance: float, last_accessed_at: str, alpha: float 0.6, beta: float 0.3) - float: # 时间衰减因子越久没访问权重越低 last_time datetime.fromisoformat(last_accessed_at.replace(Z, 00:00)) days_gap max((datetime.utcnow() - last_time).days, 0) time_decay 1.0 / (1.0 0.1 * days_gap) return alpha * score beta * importance (1 - alpha - beta) * time_decay这个公式背后的直觉是用户最可能需要的记忆往往是“语义相关”且“重要”且“最近被用到过”的。三个维度按 0.6、0.3、0.1 的权重融合实际效果比纯语义检索稳定很多。你可以在你自己的项目里微调这几个超参它们对体验的影响非常直接。4. 实操把记忆模块接进 Agent 主流程让它真的“记住你”4.1 Agent 主循环中加入“回忆-注入-响应-写入”四步有了存储和检索接下里最关键的活儿是把记忆模块接进 Agent 的主流程。这里我给出一个完整的、可以直接改来用的主循环逻辑结构是我目前最推荐的四步法class MemoryAgent: def __init__(self, llm_call, memory_store): self.llm llm_call # 你的 LLM 调用函数 self.memory memory_store # 上面的 VectorMemoryStore def chat(self, user_id: str, message: str) - str: # 第一步回忆。从记忆库中检索与当前消息最相关的历史记忆 relevant_memories self.memory.semantic_search(message, user_id, top_k5) memory_block \n.join( f- [{m[type]}]重要度{m[importance]:.2f}{m[content]} for m in relevant_memories ) # 第二步注入。把记忆拼进系统提示词 system_prompt ( 你是一个拥有长期记忆的AI助手。 以下是关于用户的记忆信息请在回答时自然地参考它们。\n f{memory_block if memory_block else 这是和用户的第一次交流暂无历史记忆} ) # 第三步响应。带上记忆一起调用LLM reply self.llm(system_prompt, message) # 第四步写入。让LLM提炼当前对话中值得记住的新信息 new_facts self._extract_memory_candidates(message, reply) for fact in new_facts: self.memory.add_memory_with_vector( user_iduser_id, type_fact[type], contentfact[content], metadatafact.get(metadata, {}), importancefact.get(importance, 0.5), ) return reply注意看主循环的顺序这四步缺一不可。尤其是第四步“写入”必须在下一次对话前发生否则这轮聊完就忘了。我在早期实现里偷懒只在会话结束时写一次记忆结果用户聊到一半换个话题前一个话题里的关键信息根本没进入记忆库。4.2 记忆写入的时机为什么不能把聊天记录直接全存你可能会问记忆写入为什么要让 LLM 再来提炼一层直接把聊天记录存进去不就行了我试过效果非常差原因有两个方面。一是信息密度低。一个小时的闲聊真正值得长期记住的可能就两三句话。全量存储会让记忆库快速膨胀检索时召回的噪音巨大。二是格式不稳定。原始聊天记录里充满了“嗯”“对”“哈哈哈”这些内容对语义检索是纯干扰项。所以我的标准做法是让 LLM 在每一轮对话后从当前这轮交流中提炼出若干结构化记忆候选memory candidates。我用一个很轻量的 Prompt 做这件事def _extract_memory_candidates(self, user_message: str, assistant_reply: str) - list[dict]: prompt f 用户说{user_message} 助手回答{assistant_reply} 请从以上对话中提炼出值得长期记住的信息。只输出JSON数组不要多余解释。 数组中的每个元素格式如下 {{type: user_fact|preference|event|task_state, content: 精炼后的记忆内容, importance: 0.0-1.0之间的数字}} 如果没有值得记住的信息输出 []。 raw self.llm(prompt, ) # 这里要做健壮性处理LLM输出可能带json包裹需要清洗 return self._parse_json_output(raw)这里有个操作细节_parse_json_output必须做异常兜底。LLM 输出 JSON 时经常会给内容包一层 Markdown 代码块标记或者突然多了一句废话。我建议直接用一个宽松的解析策略先去掉代码块标记再定位第一个[到最后一个]的区间用json.loads解析解析失败就返回空列表宁可不记也不能让对话崩掉。4.3 在回复中主动引用记忆让用户感觉被记住了想做出“被记住”的体验光在后台检索记忆还不够还要让用户能感知到。我常用的手法是在 Prompt 里明确要求当回复参考了某条历史记忆时用轻量自然的方式带出来。比如用户问“上次说的咖啡豆你还有推荐吗”如果 Agent 调出了“用户偏好手冲咖啡喜欢浅烘焙”的记忆可以这么回复我记得你之前说自己更喜欢手冲咖啡而且偏爱浅烘焙的豆子。这次我可以再给你推荐两款耶加雪菲产区的豆子应该会比较合你的口味。从工程上讲要做到这一点你只需在 system prompt 中加一句“如果使用了记忆信息请在回复中自然体现让用户知道你记得 TA”。如果 LLM 能力比较弱还可以在注入记忆时给每条记忆带上编号并让 LLM 在回复中标注[记忆1]这样的来源标签方便调试。这一步看起来只是措辞上的小技巧但用户的感受差异巨大。记忆要被用户“看见”才算真正发挥了价值。4.4 用 SQL 直接检索精准事实比向量检索更快更准最后补充一个我经常用的技巧不是所有记忆召回都要走向量检索。对于“用户叫什么名字”“用户住在哪个城市”这类结构化事实用 SQL 精确查询又快又准完全不需要依赖语义相似度。我一般会在 Agent 主循环里加一个前置步骤先查询高置信度的用户画像字段如果用户消息里出现了“我叫”“我喜欢”“我在”这类标识词直接通过规则或 LLM 抽取后写入专门的事实字段召回时优先从这些字段拿数据拿不到再走语义检索。混合召回策略的实际体验远好于纯向量方案。5. 从 Demo 到生产记忆系统必须解决的四个现实问题5.1 多用户隔离与数据安全如果你的 Agent 不是只有你一个人用那么用户隔离是第一优先级。上面代码里我特意在所有查询都加了WHERE user_id ?这个条件不是可选项而是安全红线。一旦漏掉A 用户可能会检索到 B 用户的记忆这种事故在很多 Agent 应用里真实发生过。更严谨的做法是在数据库访问层做统一封装让 SQL 查询只能通过带user_id条件的方法执行禁止裸传 SQL。另外记忆内容涉及用户隐私入库前要过一遍内容审核和脱敏至少不能把密码、密钥、身份证号这类信息写进记忆库。我还在生产环境里做了一个“记忆管理后台”用户可以看到 Agent 记住的全部内容并且支持单项删除和全部清空。这个功能用户可能不常用但它建立的是信任——用户知道自己随时可以控制 Agent 的记忆才敢放心地让 Agent 记。5.2 记忆膨胀与遗忘策略记忆库是无界增长的不清理迟早出问题。我在实践中采用两级遗忘策略被动遗忘检索时设置时间衰减长期不被命中的记忆权重越来越低最终相当于“翻不到”了。主动清理定期跑一个后台任务删除满足条件的记忆比如“超过 180 天未访问且 importance 低于 0.3”的记录。也可以对同类记忆做合并去重比如用户多次提到“我喜欢深烘焙”合并成一条并递增重要度。记住一个原则AI 的遗忘不是缺陷而是功能。让 Agent 能忘掉不那么重要的信息反而会提升它对重要信息的记忆表现。5.3 Embedding 模型的迭代与向量失效问题生产环境还有一个很少有人提前想到的坑Embedding 模型升级后新旧向量可能不在一个分布里语义检索结果会变得很奇怪。比如你之前用的是bge-small-zh-v1.5后来换成了bge-large-zh-v1.5新旧向量混合在同一个索引里相似度计算会有偏差。我现在的做法是向量字段里记一个embed_model标识检索时只查当前模型产生的向量或者干脆在升级模型时做一次全量向量重建。选型阶段也要尽量选稳定、社区活跃的模型别频繁换。5.4 让记忆可评估怎么判断 Agent 真的“记住了”最后说一个很多人都忽视的问题怎么量化评估记忆系统的效果我在项目里建立了三组测试用例第一组是事实记忆测试先让 Agent 记住一批结构化事实过 24 小时后再询问看正确召回率。第二组是语义联想测试用不同说法表达同一意图检验语义检索能不能正确匹配。比如存储“用户喜欢手冲咖啡”再问“想买咖啡豆”看能不能召回。第三组是长期稳定性测试持续灌入大量噪音记忆后检验关键记忆是否还能稳定召回。这个测试能暴露时间衰减参数和 top_k 设置的问题。有了这三组测试每次改动记忆策略后你都能快速知道效果是变好还是变坏。记忆系统最忌讳凭感觉调参一定要建立回归测试集。6. 常见问题与排查技巧实录我在调试记忆型 Agent 时踩过不少坑下面精选几个属于“教程不会写但实战必踩”的问题做个速查表现象可能原因排查思路与解决建议中文语义检索效果差用了英文为主的 Embedding 模型换用中文优化的模型如bge-small-zh系列检查是否忘记做归一化召回结果与问题无关向量检索噪音太大加入importance和time_decay加权提高 top_k 后做二次重排考虑用 LLM 对召回结果做相关性过滤记忆库无限膨胀写入策略太激进提高记忆写入的过滤阈值把 importance 低于 0.3 的候选直接丢弃定期跑清理任务检索耗时突然飙升数据量超过全表扫描能力迁移到 pgvector 或专用向量库给表建 HNSW 索引模型加载导致首次响应极慢每次调get_embedding都加载一次模型把 SentenceTransformer 模型实例做成全局单例首次加载后常驻内存JSON 解析报错导致对话中断LLM 输出不是死板 JSON解析前做清洗解析失败返回空列表不要抛异常同一记忆重复存储没有去重逻辑写入前先做一次相似度检索如果已有内容相似度大于 0.92 就跳过或合并另外再分享两个我自己的排障小工具。一个是记忆日志每次检索和写入都打点日志里记录 query、召回内容、最终注入 Prompt 的记忆块。出现问题时有日志可查排查效率能翻好几倍。另一个是调试命令我写了一个命令行工具可以直接对某个用户执行“检索关键词”并打印召回到的原始记忆内容不用跑到线上对话里翻测试数据也非常方便。做个简单的经验总结吧。给 Agent 加记忆这事儿技术栈从来不是难点难点在于搞清楚什么时候记、记什么、怎么取回来。我自己走过不少弯路比如一开始堆了一堆 Redis 缓存和向量库结果大部分都是摆设。后来简化成“SQLite 存事实 向量索引做语义召回 重要度加权决定优先级”反而把体验做了上去。另一个让我感触很深的点是记忆功能要克制不是记得越多越好。一个满脑子塞满碎碎念的 Agent比一个安安静静的 Agent 更让人崩溃。记忆的价值在于关键时刻说出来在于让用户感到“它了解我”而不是表演记忆力。我建议你在做第四步记忆写入时反复问自己一个问题这条信息三个月后用户还会希望我记得吗如果答案不确定就不要写进长期记忆。最后分享一个我很喜欢的小技巧适当利用情景记忆可以在每天对话结束时让 Agent 自动生成一份“今日摘要”入库。这样第二天用户一上来Agent 可以说“记得我们昨天聊到 XX 的进展要继续吗”——这种体验一旦做出来用户就真的回不去了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价