聊过本地 AI 助手的人应该都遇到过这个场景:你明明在十分钟前告诉过它"我住在朝阳区,通勤用的是地铁十号线",转过几轮之后它又一脸茫然地问你"请问您平时住在哪个区域"。这就是典型的上下文断裂。我一直在维护的本地优先智能体框架 OpenClaw,早期版本就有这个毛病,长对话撑不过三十轮就开始"失忆",Circle 里塞得越多反而忘得越快。后来我下决心做了一次重构,给 OpenClaw 加了一套三层记忆架构,核心思路很简单:会话里的即时信息、跨会话的稳定事实、历史中的语义片段,分别用三种不同的机制去管理。这篇文章就完整记录这次改造的思路、代码实现和踩过的坑,给同样在做 AI 助手、智能体或者 RAG 应用的人一个可以直接抄作业的参考。
1. 聊着聊着就失忆:OpenClaw 的上下文断裂到底出在哪
很多人以为 AI 失忆是因为"模型太笨",实际上更多是工程问题。OpenClaw 的对话流程一开始是最朴素的那种:把历史消息全部拼进系统提示词,然后一次性发给底层大模型。这种方式在前十几轮还凑合,越往后越不对劲,我逐步排查才看清了三个层面的问题。
1.1 上下文窗口不是无限内存
任何一个底层大模型都有上下文窗口上限,名义上能装几万甚至十几万 token,但窗口越大,两个问题越明显:一是处理时间线性增长,二是模型对早期内容的注意力会显著下降。OpenClaw 早期直接把所有消息原样拼接,很快就逼近窗口上限。我测过一个长对话,跑到第八十轮左右,单次请求耗时从 1.8 秒涨到了 7.6 秒,而且模型开始把中段信息搞混。
更隐蔽的是,OpenClaw 本身还有一些固定的系统指令、工具定义、安全策略要占用窗口。这些内容加起来就有几千 token,留给真正对话记忆的空间比想象中小得多。用生活类比说:窗口就像一个短期工作台,你不断往上面堆文件,不清走旧文件,新文件就放不下,而且最关键的文件反而被埋在底下找不到。
1.2 会话滚动与摘要压缩:信息被"偷走"的瞬间
为了控制窗口大小,很多人会做"滑动窗口":只保留最近 N 轮消息,更早的直接丢掉。OpenClaw 第一版也是这么干的。但滑动窗口的问题在于,用户经常在很早的对话里透露关键信息,比如"下周一到杭州出差",等真到了下周一,这条消息早就被滚出窗口了,助手自然不记得你出差的安排,回答全是基于当下猜测。
还有一类做法是摘要压缩:把旧消息发给模型,让它生成一段摘要放进系统提示词。我试过这个方案,理论上可行,实践上损失惨重。模型会把"用户提到他儿子今年六岁,在某某小学读一年级"这种细节压缩成"用户有一个孩子",等到后面用户问"帮我查一下跟我儿子学校相关的活动",这种摘要已经完全帮不上忙了。摘要不是不可以用,而是不能"一刀切",只保留表层语义、丢掉结构化事实。
1.3 一个真实场景:用户以为它在装傻
排查到最后,我归纳出一个典型场景。用户和 OpenClaw 聊天分三段:第一段聊最近工作压力大、决定辞职创业做餐饮;第二段问有哪些适合小成本启动的品类;第三段隔了一天才回来问"上次你说那个选址策略,能不能再展开讲讲"。理论上,第三段的问题完全依赖第一段和第二段的上下文。但真实发生的情况是:第一段信息已被滑出窗口,第二段被压缩摘要时把"选址"这个话题细节丢了,第三段收到请求时,OpenClaw 只能答出一堆泛泛的餐饮建议。
用户当时的反馈是:"它是不是根本没在听我说话?"这句话让我意识到,所谓的"失忆"不是因为模型能力不行,而是记忆管理的颗粒度不对。所有记忆都堆在一起、用一个简单的 FIFO 策略进出,必然导致关键信息被误伤。这也是我后来决定做分层架构的根本原因。目标很明确:让每一次对话请求,都能在正确的时间、以正确的成本,取回正确的记忆。
1.4 三层记忆架构的初始设想
我最初的设想,是参考人的记忆方式做一个简单的分层:眼前要说的话、最近一直在想的事、长期沉淀下来的知识。后来查阅了不少资料,发现这个方向基本对应认知科学里的"工作记忆—情景记忆—语义记忆"三层结构。OpenClaw 需要的不是把整个对话历史都灌进提示词,而是要把"这一轮要用什么"和"以后可能要用什么"分开存储。
2. 三层记忆架构:从"按轮次记录"到"分层的记忆图谱"
这套架构后来被我在项目里称为"会话短期记忆—工作记忆—长期记忆"三层模型。每层的存储介质、更新策略、召回方式完全不同。说起来不复杂,但每层的细节都值得单独讲清楚。
2.1 第一层:会话短期记忆,管好眼前这几轮
短期记忆解决的是"当下对话的连贯性"。它的存储范围只覆盖当前会话(session)内最近的若干轮消息,核心目的是让 OpenClaw 能承接前后文的指代和省略。例如用户说"那个方案再改一改","那个"指的是什么,只能从最近几轮里找。
实现上,我用了一个带最大长度的循环队列,存最新的 N 轮对话。N 不是拍脑袋定的,我做了预算:假设每轮平均 800 token,底层模型预留 6000 token 给系统指令和工具定义,对话部分最多留 12000 token,那么短期记忆最多能装大约 15 轮。超过这个量,就对最旧的一半做摘要压缩。压缩不是简单的"扔掉",而是要把结构化关键字段留在摘要模板里。
2.2 第二层:工作记忆,记住那些"不能丢"的稳定事实
工作记忆解决的是"跨会话的稳定事实",比如用户的城市、职业、家庭情况、当前目标。这些信息往往在第一次聊天时就确定了,之后每一轮都可能被用到,但又没必要每次重新读取一遍完整的历史记录。
这一层我用的是结构化存储,本质上是三元素组:主体、谓词、宾语,外加置信度和更新时间。例如:
- (用户,所在城市,北京)
- (用户,当前目标,开一家奶茶店)
- (OpenClaw,承诺事项,周四前给出选址报告)
每次新会话开始时,工作记忆中最相关的事实会被填充进系统提示词。这就像你进办公室前瞄了一眼便签,上面写着"现在在做什么、客户是谁、今天有什么截止时间",而不是翻完整个笔记本才想起来自己在哪个项目里。
2.3 第三层:长期记忆,把历史变成可检索的资产
长期记忆解决的是"过去聊过什么、有可能再次用到"的场景。例如三个月前用户提到过"我家猫对某种猫粮过敏",现在突然问"能不能推荐一款不易过敏的猫粮"。这种信息埋在很久以前的历史里,用滑动窗口永远救不回来,唯一可靠的方式是语义检索。
我把所有有价值的对话片段,切成长度在 200 字左右的块,做向量化后存入向量数据库。查询时,把当前问题也向量化,计算余弦相似度,取分数超过阈值的结果回来。这一层本质是 RAG,只不过检索的语料不是外部文档,而是用户自己和 OpenClaw 的历史交互记录。这一步是解决"长程失忆"的关键。
2.4 各层职责对比与协作
用一个表格看这三层的关系最直观:
| 层级 | 存储介质 | 更新策略 | 召回方式 | 生命周期 |
|---|---|---|---|---|
| 会话短期记忆 | 内存中的循环队列 | 每轮追加,超限后压缩 | 全部注入提示词 | 会话内有效 |
| 工作记忆 | 结构化事实库(如 SQLite) | 检测到新事实时写入,冲突时覆盖 | 选最相关的事实注入提示词 | 持续数周甚至数月 |
| 长期记忆 | 向量数据库 | 异步写入,定期合并去重 | 语义相似度检索 | 持续数月到数年 |
三层不是替代关系,而是协作关系。一个请求进来,OpenClaw 先看短期记忆确定当前话题,再看工作记忆确定"用户的稳定画像",最后只有当前问题确实需要历史细节时,才去查长期记忆。这种"逐级放大"的策略既控制了 token 消耗,又保证了记忆的覆盖面。
3. 代码落地:OpenClaw 的三层记忆模块实现实录
架构说清楚之后,剩下的全是脏活。这一节我把实际项目里的关键代码和配置贴出来,不是完整源码,而是能体现核心思路的骨架实现。我的环境是 Python 3.11 + SQLite + 一个本地向量存储库,底层模型通过 OpenAI 兼容接口调用。为了便于复现,去掉了工程无关的噪音。
3.1 基础依赖与环境准备
我建议把记忆相关的代码独立成一个包,不要和对话主流程混在一起。目录结构大致是这样:
openclaw/ ├── memory/ │ ├── __init__.py │ ├── short_term.py # 短期记忆 │ ├── working_memory.py # 工作记忆 │ ├── long_term.py # 长期记忆 │ └── schemas.py # 数据结构定义 ├── main.py # 主流程入口 └── config.yaml # 配置文件依赖方面,我会用到这几个核心库:
pip install openai # 或你对接的其他兼容接口 pip install numpy # 向量计算 pip install sqlite-utils # 方便操作 SQLite安装完成后,配置文件里需要指定底层模型名称、embedding 模型路径、向量维度等。我的 config 长这样:
llm: model: "your-local-llm-name" context_window: 32000 reserved_system_tokens: 6000 memory: short_term_max_turns: 15 short_term_max_tokens: 12000 working_db_path: "./data/working_memory.db" long_term_db_path: "./data/long_term_mem" embedding_model: "your-embedding-model-name" embedding_dim: 768 retrieval_top_k: 5 retrieval_min_score: 0.72这些参数不是死的,我后面会专门讲怎么调。
3.2 短期记忆:循环队列加自动摘要压缩
短期记忆的代码不算难,但有一个细节值得注意:当队列超限时,不能等到"满了再一次性压缩",而是应该在超过阈值后渐进式压缩,否则会出现明显的卡顿。下面是我实际跑通的逻辑:
from collections import deque import json class ShortTermMemory: def __init__(self, max_turns=15, max_tokens=12000): self.turns = deque(maxlen=max_turns * 2) self.summary = "" self.max_tokens = max_tokens def _estimate_tokens(self, text: str) -> int: # 粗略估算:中文字符约等于1个token,英文约4字符1个token return int(len(text) * 0.75) + 32 def add_turn(self, user_msg: str, assistant_msg: str) -> None: self.turns.append({ "role": "user", "content": user_msg, "ts": time.time() }) self.turns.append({ "role": "assistant", "content": assistant_msg, "ts": time.time() }) self._maybe_compress() def _maybe_compress(self) -> None: total_tokens = sum( self._estimate_tokens(t["content"]) for t in self.turns ) total_tokens += self._estimate_tokens(self.summary) if total_tokens > self.max_tokens and len(self.turns) >= 6: # 取最旧的4轮做压缩,而不是全部 old_turns = list(self.turns)[:4] self.turns = deque(list(self.turns)[4:]) new_summary = self._compress_turns(old_turns) self.summary = self._merge_summary(self.summary, new_summary) def _compress_turns(self, turns: list) -> str: text = json.dumps(turns, ensure_ascii=False) prompt = ( "以下是用户与AI助手的一段对话。请生成结构化摘要," "必须保留以下信息:具体时间、具体地点、人名与身份、" "任务状态、明确的用户偏好。不要输出多余解释。\n\n" f"{text}" ) # 这里的 raw_result 来自底层模型调用封装 return call_llm(prompt) def _merge_summary(self, old: str, new: str) -> str: if not old: return new prompt = f"请合并下面两份对话摘要,去除重复,保留所有事实细节。\n\n旧摘要:{old}\n\n新摘要:{new}" return call_llm(prompt) def build_context(self) -> list[dict]: context = [] if self.summary: context.append({"role": "system", "content": f"[对话摘要] {self.summary}"}) context.extend(self.turns) return context我自己踩过的一个明显的坑是:summary 里存的事实一定要足够"结构化",否则模型合并摘要时会自作主张丢掉细节。这个我在第四节专门说。
3.3 工作记忆:结构化事实写入与冲突检测
工作记忆是三层里面最容易偷懒但最不该偷懒的部分。很多人会直接把用户画像写在配置文件里,但真正使用中,用户画像会变,而且往往在一个很长的任务流程里中途变化。我用了 SQLite 加简单的时间戳版本管理。
import sqlite3 from dataclasses import dataclass, asdict from datetime import datetime @dataclass class Fact: subject: str # 主体,如 "user" predicate: str # 谓词,如 "location" object: str # 宾语,如 "北京" confidence: float = 1.0 updated_at: str = "" active: int = 1 class WorkingMemory: def __init__(self, db_path: str): self.conn = sqlite3.connect(db_path) self.conn.execute( """CREATE TABLE IF NOT EXISTS facts ( id INTEGER PRIMARY KEY AUTOINCREMENT, subject TEXT, predicate TEXT, object TEXT, confidence REAL, updated_at TEXT, active INTEGER )""" ) self.conn.commit() def upsert_fact(self, subject: str, predicate: str, object: str, confidence: float = 1.0) -> None: # 先看有没有同主体同谓词的有效记录 row = self.conn.execute( "SELECT id, object FROM facts WHERE subject=? AND predicate=? AND active=1", (subject, predicate) ).fetchone() now = datetime.utcnow().isoformat() if row is None: self.conn.execute( "INSERT INTO facts(subject,predicate,object,confidence,updated_at,active) VALUES(?,?,?,?,?,1)", (subject, predicate, object, confidence, now) ) else: # 如果值不同,说明新旧冲突 if row[1] != object: self.conn.execute( "UPDATE facts SET active=0 WHERE id=?", (row[0],) ) self.conn.execute( "INSERT INTO facts(subject,predicate,object,confidence,updated_at,active) VALUES(?,?,?,?,?,1)", (subject, predicate, object, confidence, now) ) else: self.conn.execute( "UPDATE facts SET confidence=?, updated_at=? WHERE id=?", (confidence, now, row[0]) ) self.conn.commit() def get_relevant_facts(self, subject: str, limit: int = 10) -> list[Fact]: rows = self.conn.execute( "SELECT subject,predicate,object,confidence,updated_at FROM facts " "WHERE subject=? AND active=1 ORDER BY updated_at DESC LIMIT ?", (subject, limit) ).fetchall() return [Fact(*r) for r in rows]调用方式也很直接。对话结束后,我会单独跑一个"事实抽取"步骤,把这一轮里用户说出的稳定事实提取出来写入工作记忆。这一步可以用底层模型做,也可以用规则加模型混合。我目前的做法是给模型一段 prompt,让它输出 JSON 数组,只提取明确的、可验证的事实。
[ {"subject": "user", "predicate": "city", "object": "成都", "confidence": 0.95}, {"subject": "user", "predicate": "job_status", "object": "辞职创业", "confidence": 0.9} ]3.4 长期记忆:embedding 入库与相似度召回
长期记忆模块最依赖外部工具,我选的是轻量级向量存储,没有上重型数据库。代码核心是三个操作:文本切块、写入向量库、向量检索。
import hashlib import numpy as np class LongTermMemory: def __init__(self, embedding_fn, db_path: str, dim: int = 768): self.embedding_fn = embedding_fn self.dim = dim # 这里用了一个简单的持久化 JSON + numpy 文件做 demo self.items = self._load(db_path) self.db_path = db_path def add_text(self, text: str, metadata: dict | None = None) -> None: if len(text) < 40: return # 太短的内容没有长期保存价值 vec = self.embedding_fn(text) item = { "id": hashlib.md5(text.encode()).hexdigest(), "text": text, "vec": vec.tolist(), "ts": time.time(), "access_count": 0, "metadata": metadata or {} } self.items[item["id"]] = item self._persist() def search(self, query: str, top_k: int = 5, min_score: float = 0.72) -> list[dict]: q_vec = np.array(self.embedding_fn(query)) scored = [] for item in self.items.values(): vec = np.array(item["vec"]) score = float(np.dot(q_vec, vec) / (np.linalg.norm(q_vec) * np.linalg.norm(vec))) scored.append((score, item)) scored.sort(key=lambda x: x[0], reverse=True) results = [] for score, item in scored[:top_k]: if score >= min_score: item["access_count"] += 1 results.append({"text": item["text"], "score": score}) self._persist() return results要说明的是,这个items直接放内存只适用于小规模场景,比如单用户、几千条记忆。如果是多用户或者上万条,建议换成正式的向量数据库。原理完全一样,只是存储和索引交给更专业的引擎。
3.5 把三层缝起来:请求处理主流程
模块写得再漂亮,没有主流程编排也白搭。我在main.py里把三层记忆串成了一整套流水线,核心逻辑可以概括为:读取上下文、召回记忆、生成回复、异步沉淀记忆。
class OpenClaw: def __init__(self, short: ShortTermMemory, work: WorkingMemory, long: LongTermMemory): self.short = short self.work = work self.long = long def chat(self, user_input: str, user_id: str = "default") -> str: # 1. 组装系统提示词:基础指令 + 工作记忆中最相关的事实 facts = self.work.get_relevant_facts(user_id, limit=8) fact_text = "\n".join( f"- {f.subject} 的 {f.predicate} 是 {f.object}" for f in facts ) system_prompt = f"你是OpenClaw。\n以下是用户的稳定背景信息:\n{fact_text}" # 2. 组装短期记忆上下文 context = self.short.build_context() # 3. 只在需要时查询长期记忆 long_term_hits = self._decide_long_term_recall(user_input) if long_term_hits: context.append({ "role": "system", "content": f"[相关历史记忆]\n{long_term_hits}" }) # 4. 调用底层模型 messages = [ {"role": "system", "content": system_prompt}, *context, {"role": "user", "content": user_input} ] reply = call_llm_with_messages(messages) # 5. 更新短期记忆 self.short.add_turn(user_input, reply) # 6. 异步沉淀长期记忆与工作记忆 self._async_extract_facts(user_id, user_input, reply) self._async_store_long_term(user_id, user_input, reply) return reply这里最关键的其实是_decide_long_term_recall。我之前犯过"每轮都查长期记忆"的错误,结果不仅慢,而且把不相关的东西全部塞进了上下文,严重干扰回答质量。后来改成"先判断短期窗口里是否已有足够信息,只有涉及具体历史细节的问题才触发检索"。判断思路有两种:
- 规则法:检测用户输入中是否包含"上次、之前、回忆、你说过、那个"等触发词。
- 语义法:让模型判断"当前问题是否需要历史信息",输出一个布尔值。
我用规则法兜底,语义法补充,召回准确率明显提升。这个细节看似不起眼,却决定了整套记忆系统最终是"助力"还是"灾难"。
4. 调优和踩坑:那些文档里永远不会告诉你的细节
代码能跑只是第一步。我连续调了两周,才让三层记忆真正"好用"。这一节全是实战中遇到的问题,按踩坑次数排序。
4.1 向量检索噪音:阈值不是越高越好
长期记忆召回最直接的问题是"检索出一堆看起来相似但实际无用的内容"。比如用户问"帮我看看上次那个店铺选址有没有问题",向量检索出来的可能是之前讨论"奶茶店名字"的内容,因为"店铺""奶茶"这些词在语义空间里距离很近。
我一开始把相似度阈值设得比较高(0.85),结果召回率骤降,很多该记住的内容找不回来。后来一边观察一边调整,最终把阈值定在 0.72,配合 top_k=5。更关键的一个优化是"重排":第一步先用 embedding 粗召回 20 条候选,第二步交给底层模型对候选做一次相关性打分,只取真正有用的两三条。这一步增加了约 300ms 延迟,但回答质量提升非常明显。
经验结论:不要迷信单一阈值。正确做法是"粗召回取大范围 + 重排取精",而不是试图靠一个分数线把噪声完全挡在外面。
4.2 摘要压缩丢信息:给摘要一个"必填模板"
摘要压缩的坑我前面已经铺垫了。最典型的一次:用户长时间聊自己的旅行计划,明天要去西安。摘要生成时,模型输出的内容是"[用户]最近在计划一次旅行,对历史景点感兴趣",完全丢掉了"西安""明天出发"这两个关键事实。等第二天用户问"帮我看看西安明天的天气",OpenClaw 答非所问。
解决办法是给摘要任务一个强约束模板。我把摘要 prompt 改成这样:
请压缩以下对话为结构化摘要。你必须在输出中包含以下字段: - 用户所在地点 - 用户提及的具体时间 - 正在进行的任务 - 明确表达过的偏好 - 已承诺的事项 如果没有对应信息,写"未知"。模板化之后,压缩丢失率显著下降。本质上不是模型能力不够,而是生成摘要时没有给出"哪些信息绝对不能丢"的优先级。
4.3 工作记忆的冲突检测:新事实要会"顶掉"旧事实
用户背景是会变的。有人在第一天告诉你他在上海,第三天告诉你已经搬到杭州了。如果工作记忆里两条都保留,模型反而会糊涂,不知道该信哪个。这就是记忆冲突。
我的处理分两步:
- 写入检测:写入新事实前,先检索同主体同谓词的有效旧事实,若旧值不同,就将旧记录标记为 inactive,同时保留一个"历史版本表"。
- 对话时过滤:读取事实时只取 active=1 的记录,历史版本不注入提示词。
这个"软删除"的好处是,用户如果过几天又说"其实我又回来了",可以通过历史版本快速找回场景,不需要重新建立。代价则是每次 upsert 前的查询操作多消耗一点时间,但完全值得。
4.4 存储膨胀与隐私边界
长期记忆如果什么都存,3 个月就能积累几万条,每次检索都扫描一遍显然不现实。我加了两个策略:
- 定期合并:每周跑一次离线任务,把表达相近、时间接近的记忆片段合并成一条,减少冗余。
- 重要度衰减:每条记忆有 access_count(被检索到的次数)和 last_access_time。连续 60 天没被访问且 access_count 偏低的记忆,自动降级为"归档",不再参与常规检索。
隐私方面也别忽略。本地记忆系统本质上存了大量个人数据,OpenClaw 的对话记录默认只保存在本地磁盘,不主动同步到任何远程服务。我还给记忆数据库加了简单的字段级加密,至少做到"拔了硬盘不能直接读明文"。这个点容易被玩票项目忽略,但对于任何真实场景都至关重要。
4.5 性能开销:检索不能变成重计算
加装了三层记忆后,我最担心的不是效果,而是速度。初期版本每个请求都要查向量库、查事实库、再拼摘要,接口总耗时从 2 秒涨到 4 秒,体验割裂很明显。做了三件事才缓解:
- 异步写入。短期记忆的更新必须同步,因为下一轮立即要用;工作记忆和长期记忆的写入全部丢到后台任务队列。
- 语义检索缓存。同一个会话内,我已经算过的问题向量直接缓存,重复触发只走缓存,不重新 embedding。
- 分级检索。不是每个请求都查长期记忆,只有"确实需要历史"的请求才走最重的链路,平时只跑短期加工作记忆。
改造完,日常简单问题的响应时间回到了 2 秒以内,只有真正触发历史检索时才上浮到 3 秒左右。这个"按需重排"的思路,我认为比任何底层优化都更有效。
5. 效果测试与未来扩展
架构改完,我做了两轮真实场景测试,一轮是长对话压力测试,一轮是跨会话记忆测试。这里记录测试口径和结果,方便你自己复现时对照。
5.1 48 小时长跑测试:从"查过就说忘了"到"像真人一样记得"
测试方案:我用 OpenClaw 连跑 48 小时,模拟一个真实用户一天互动 20 次,话题覆盖工作、生活、旅行、饮食偏好四类。每 50 轮检查一次"关键记忆保持率"——我事先植入 30 条必须记住的信息,比如"用户对花生过敏""周四下午三点有会议""正在准备开咖啡馆的计划书"。
改造前,30 条关键信息保留率在 100 轮之后只有 40% 左右;改造后,短期记忆解决当下的 15 轮,工作记忆负责稳定事实,长期记忆承接更早的语义片段,三轮共测下来保留率稳定在 92% 以上。唯一丢掉的 8%,几乎全部是"表达不清、语义含糊"的早期内容,属于正常遗忘。
跨会话测试更震撼:第一天聊完"准备去大理做义工旅行",第二天直接问"我出发前还需要买什么保险",OpenClaw 能正确接住"大理""义工旅行"这个前情,给出的建议完全是针对这个场景的。这在旧版本里几乎不可能做到。
5.2 还可以朝哪个方向扩展
这套三层架构解决的是通用问题,但还有不少可延展点。我自己接下来的计划:
- 多用户隔离:现在记忆表靠 subject 字段区分用户,真正多用户场景要做成独立数据库,避免查询串号。
- 记忆的"遗忘"机制:给每条记忆加显式过期时间和手动删除入口,用户可以在界面上管理"我不希望你记住这些"。
- 多模态记忆:对话之外,OpenClaw 还能接收图片和文件,长期记忆库需要支持多模态向量化,让图片里的信息也能被检索到。
这些方向不改变三层架构本身,只是让每个层面的能力更深。目前来看,这套方案的通用性很强,换成任何一个智能体框架都可以按同样的思路落地。关键是理解每一层解决什么问题、用什么介质存储、怎么召回,而不是照搬代码。
最后再掏心窝说一句:别想着一次就做完美。我第一版也经历了"短期记忆不够用、压缩丢信息、检索出噪声、用户觉得你在胡扯"的整套流程。三层记忆的优势不在于技术栈有多高级,而在于它把"记忆"从隐性的窗口管理变成显式的工程系统——每一层都可以单独观察、调整、优化。先把最朴素的版本跑起来,再根据日志一点点调,你会发现 AI"失忆"这个问题的解法,其实没有想象中那么神秘。