你现在问本地跑的大模型一句“上周我们定的那个方案叫什么来着”,它大概率会一本正经地给你编一个。不是模型笨,是它压根没有地方存东西:一次会话结束,上下文清零,下次对话从零开始。这篇文章要解决的就是这个很具体的问题——怎么让本地 AI 助手真的“铭记”你们之间定下的约定,把用户偏好、项目背景、历史结论这些东西跨会话留下来,而不是每次都要重新喂一遍。
这件事在云端产品里叫“记忆功能”,在本地部署场景里,对应的是长期记忆系统(Long-term Memory)。它不是一个单独的模型,而是一套组合:向量库负责存和检索、Embedding 模型负责把文本变成向量、LLM 负责抽取和合并事实、编排层负责决定“什么时候该记、该记什么、该忘什么”。本文会把这一整套跑在本地机器上,覆盖从零部署、接口调用、批量导入、效果验证到显存观察的完整链路。
硬件门槛这块先说清楚:记忆系统的存储层(向量库)本身很轻,吃的是内存和磁盘;真正吃显存的是背后的对话模型和 Embedding 模型。7B 级别的对话模型在 4bit 量化下通常能在 8G 显存级别跑起来,Embedding 模型体量更小,几百 MB 显存就够。如果你用 CPU 推理也能跑,只是检索和抽取的响应会慢一截。具体占用必须按你选的模型版本、量化方式和并发量实测,这篇文章不给你编数字。
适合读这篇的人:想把本地助手做成“有记忆的私人助理”的开发者、需要给内部知识助手加用户画像的团队、以及单纯想搞清楚 Mem0、Letta 这类记忆框架到底怎么落地的人。下面按“能力速览 → 架构选型 → 环境 → 部署 → 测试 → 接口 → 性能 → 排错 → 实践”的顺序走。
1. 长期记忆系统核心能力速览
先把关键信息摆出来,判断值不值得动手。
| 能力项 | 说明 |
|---|---|
| 系统类型 | 本地部署的 AI 长期记忆层,非单一模型 |
| 主要功能 | 记忆写入、语义检索、事实抽取与合并、冲突更新、记忆删除、跨会话召回 |
| 推荐硬件 | 对话模型 7B 量化级别建议 8G 显存以上,Embedding 模型显存占用低;CPU 推理可跑但延迟更高 |
| 显存占用 | 取决于对话模型参数量、量化方式、Embedding 模型大小与并发数,需按本机实测 |
| 存储后端 | 向量库(Qdrant / Chroma / Milvus / pgvector 等)+ 可选关系库存元数据 |
| 支持平台 | Windows / Linux / macOS 均可,生产环境建议 Linux |
| 启动方式 | Docker 拉起向量库 + 命令行启动记忆服务 + FastAPI 暴露 HTTP 接口 |
| 是否支持 API | 是,通常以 REST 接口形式对外提供 add / search / delete |
| 是否支持批量任务 | 是,可通过 JSONL 批量导入 + 队列 + 重试机制实现 |
| 适合场景 | 私人助理、客服机器人、项目知识沉淀、多轮业务系统、Agent 记忆 |
| 不适合场景 | 要求毫秒级强一致的实时事务、无授权的大量用户隐私数据聚合 |
这张表里唯一需要你重点确认的是“显存占用”这一行。同一个 7B 模型,用 Q4 量化和用 FP16,显存差距是数量级的;Embedding 模型从 100M 参数换到 500M 参数,又是另一个量级。所以别信任何脱离模型规格的固定数字,包括别人博客里写的那些。
2. 适用场景与使用边界
先讲清楚这东西到底解决什么问题。大模型的上下文窗口再大,也有三个硬伤:一是贵,长上下文每次请求都要重新计费或重新算;二是慢,上下文越长首 token 延迟越高;三是跨会话不连续,关掉窗口就归零。长期记忆系统针对的正是第三点——它把“值得记住的东西”抽出来,落到外部存储里,下一轮对话按需检索回来,只占用很少的上下文预算。
具体适合谁?第一类是个人开发者,想让本地助手记住自己的技术栈偏好、常用路径、项目代号,不用每次开头都重新交代。第二类是团队内部的知识助手,把项目决策、接口约定、历史踩坑记录沉淀下来,新人问一句就能召回。第三类是 Agent 类应用,需要跨任务保存中间结论,避免每一步都从零开始推理。
不适合的场景也要说清楚。如果你的需求是“每次都要读到最新完整文档”,那你要的是 RAG(检索增强生成),不是记忆系统——RAG 索引的是静态知识库,记忆系统索引的是动态交互事实,两者的写入频率、更新策略、生命周期完全不同,别混为一谈。
使用边界这块必须单独强调。记忆系统天然会存下大量个人信息和业务信息,所以在做任何部署之前,先把这几条定下来:
- 授权边界:只记录用户明确知晓并同意被记录的内容,不要把对话里的身份证号、手机号、住址、账号密码自动写进向量库。
- 数据隔离:按
user_id、agent_id、run_id做命名空间隔离,不同用户、不同业务线的记忆不能互相召回。 - 删除权:必须提供按用户维度彻底删除记忆的能力,并且删除要落到向量库和元数据两边,不能只删一边。
- 脱敏:写入前做正则或规则脱敏,把手机号、邮箱、密钥前缀这类内容替换掉,宁可记不住,也不要记错地方。
- 审计:保留写入和检索日志,出问题时能追溯是哪条记忆导致的错误回答。
如果你是给第三方做系统,这些边界不是可选项,是设计前提。记忆系统一旦跑起来,数据的雪球会越滚越大,前期没做隔离,后期补的成本极高。
3. 系统架构与组件选型
记忆系统不是一个软件包,是四到五个组件的组合。下面这张表把每一层拆开,你可以按资源情况自由换件。
| 层级 | 职责 | 常见可选项 | 选型建议 |
|---|---|---|---|
| 对话模型层 | 生成回答、抽取事实、判断是否值得记 | Ollama、llama.cpp、vLLM、各类本地推理框架 | 资源紧张用 Ollama 快速起步,追求吞吐再上 vLLM |
| Embedding 层 | 把记忆文本转成向量 | bge 系列、nomic-embed-text、各类开源向量模型 | 中文场景优先选中文效果好的 bge 系;显存紧张用小参数量版本 |
| 向量存储层 | 存向量、做相似度检索 | Qdrant、Chroma、Milvus、pgvector | 单机起步 Qdrant 或 Chroma 足够;已有 PostgreSQL 可直接用 pgvector |
| 记忆管理层 | 抽取、去重、冲突合并、过期策略 | Mem0、Letta(原 MemGPT)、LangChain Memory、自研 | 想快速验证用现成框架,要精细控制就自研 |
| 编排与接口层 | 对外暴露 HTTP 接口、串流程 | FastAPI、Flask、Node 服务 | Python 生态首选 FastAPI,异步和文档生成都省事 |
架构上我建议先走最小闭环,别一上来就堆组件。最小闭环只需要三件东西:一个能跑的对话模型、一个 Embedding 模型、一个向量库。记忆管理逻辑先用最简单的规则版——直接存原文 + 检索 top_k,跑通了再上“事实抽取 + 冲突合并”的复杂逻辑。
这里解释一下为什么要分“原文记忆”和“事实记忆”两层。原文记忆保真度高,但噪声大、检索命中率低;事实记忆(比如“用户偏好使用 FastAPI”“项目代号叫 Orion”)紧凑、检索准,但抽取过程中会丢信息。实践里通常是两层都留,检索时先召回事实,事实不够再回退到原文片段。这个策略在后面的测试环节会具体验证。
还有一个容易被忽略的点:Embedding 模型必须和向量库的维度对齐。你换了 Embedding 模型,向量维度变了,旧集合直接不可用,必须重建索引。所以选型阶段就要把模型定下来,别中途随便换。如果确实要换,走“新集合双写 + 全量重建 + 灰度切换”的流程,别直接原地覆盖。
4. 本地部署环境准备
环境这块给一份通用检查清单,具体版本号按你实际装的时候的官方要求来,别硬套别人博客里的数字。
操作系统方面,Windows 10/11 能跑,但 Docker Desktop 的 WSL2 后端偶尔会在端口映射上出问题;Linux(Ubuntu 系)最省心;macOS 用 Apple Silicon 跑 Ollama 体验不错,但 Docker 网络层偶尔会有额外延迟。
基础依赖清单:
- Python 3.10 及以上(多数记忆框架要求 3.10+,低于这个版本容易在依赖解析阶段就卡住)
- pip 或 uv 作为包管理,建议用虚拟环境隔离,别往系统 Python 里装
- Docker(用于拉起向量库),或者直接用向量库的嵌入式模式
- GPU 驱动与 CUDA,只有在你要用 GPU 推理时才需要,CPU 模式不需要
- 磁盘空间:向量库本身占用不大,但模型文件动辄几 GB,加上日志和快照,建议预留 30G 以上
端口规划也要提前想好,别等到起冲突了再改。常见占用如下:
| 服务 | 默认端口 | 说明 |
|---|---|---|
| Ollama | 11434 | 本地推理服务 |
| Qdrant | 6333 / 6334 | HTTP 接口 / gRPC 接口 |
| Chroma | 8000 | 独立服务模式下的默认端口 |
| 记忆服务(自建) | 8000 或 8080 | 与 Chroma 默认端口冲突,注意错开 |
前置检查直接跑几条命令确认:
# 检查 Python 版本 python --version # 检查 Docker 是否可用 docker --version docker ps # 检查端口占用情况(Linux / macOS) lsof -i :11434 lsof -i :6333 # Windows 下用 netstat 检查端口 netstat -ano | findstr :6333如果lsof显示某个端口已被占用,先确认占用进程是不是你自己之前没关干净的服务。记忆系统最常见的“启动失败”,八成是上一次进程残留占着端口,而不是配置写错了。
5. 部署与启动流程
按“向量库 → 推理服务 → 记忆服务”的顺序起,每一步都验证一遍再往下走。
5.1 启动向量库
以 Qdrant 为例,Docker 一行拉起,并把数据目录挂出来,避免容器一删数据全丢:
docker run -d \ --name qdrant \ -p 6333:6333 \ -p 6334:6334 \ -v $(pwd)/qdrant_storage:/qdrant/storage \ qdrant/qdrant起完之后访问http://127.0.0.1:6333/dashboard,能看到管理界面就说明服务正常。如果你更想用 Chroma,也可以用它的 Docker 镜像或者嵌入式模式,嵌入式模式不需要单独起服务,直接在 Python 里PersistentClient指定目录即可,适合单机验证。
5.2 启动本地推理服务
用 Ollama 拉取对话模型和 Embedding 模型:
# 拉取对话模型(模型名按你实际选择的替换) ollama pull qwen2.5:7b # 拉取 Embedding 模型 ollama pull nomic-embed-text # 确认模型列表 ollama list拉完之后先单独测一下模型能不能正常生成,别把问题留到上层:
curl http://127.0.0.1:11434/api/generate -d '{ "model": "qwen2.5:7b", "prompt": "用一句话说明什么是向量检索", "stream": false }'能返回正常文本,说明推理层通了。如果这一步就超时,先看显存是不是被别的进程吃满,再确认模型名有没有拼错。
5.3 安装记忆框架
创建一个干净的虚拟环境再装依赖:
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install --upgrade pip pip install mem0ai qdrant-client fastapi uvicorn安装完成后写一个最小配置文件,把向量库、LLM、Embedder 三块接起来。下面这份配置是通用结构,具体字段名以你安装版本的官方文档为准,不同版本在键名上会有差异:
# memory_config.py config = { "vector_store": { "provider": "qdrant", "config": { "host": "127.0.0.1", "port": 6333, "collection_name": "agent_memory" } }, "llm": { "provider": "ollama", "config": { "model": "qwen2.5:7b", "ollama_base_url": "http://127.0.0.1:11434" } }, "embedder": { "provider": "ollama", "config": { "model": "nomic-embed-text", "ollama_base_url": "http://127.0.0.1:11434" } } }5.4 最小可运行验证脚本
在接 HTTP 接口之前,先用脚本确认“写入 → 检索”这条链路通不通:
# smoke_test.py from mem0 import Memory from memory_config import config memory = Memory.from_config(config) # 写入两条记忆,用 user_id 做隔离 memory.add("我习惯用 FastAPI 写后端接口,不喜欢 Flask", user_id="user_001") memory.add("这个项目的代号叫 Orion,数据目录统一放在 /data/orion", user_id="user_001") # 检索 results = memory.search("项目代号是什么", user_id="user_001") for item in results: print(item)跑通的标准是:检索结果里能出现“Orion”相关的条目,并且没有把另一个用户的数据混进来。这一步如果失败,先别急着改代码,去看向量库的 collection 里到底有没有数据——很多时候是 Embedding 调用失败导致写入静默失败。
5.5 启动对外接口服务
用 FastAPI 包一层,把记忆能力暴露成 HTTP 接口:
# api_server.py from fastapi import FastAPI from pydantic import BaseModel from mem0 import Memory from memory_config import config app = FastAPI(title="Local Memory Service") memory = Memory.from_config(config) class AddRequest(BaseModel): text: str user_id: str class SearchRequest(BaseModel): query: str user_id: str limit: int = 5 @app.post("/memory/add") def add_memory(req: AddRequest): memory.add(req.text, user_id=req.user_id) return {"status": "ok"} @app.post("/memory/search") def search_memory(req: SearchRequest): results = memory.search(req.query, user_id=req.user_id, limit=req.limit) return {"status": "ok", "data": results} @app.delete("/memory/{user_id}") def delete_memory(user_id: str): memory.delete_all(user_id=user_id) return {"status": "ok"}启动命令:
uvicorn api_server:app --host 127.0.0.1 --port 8080 --reload注意--host不要随手写成0.0.0.0。记忆服务存的是隐私数据,本地验证阶段就绑127.0.0.1,等确认要做内网共享时再改,并且配合鉴权和访问控制一起上。
6. 功能测试与效果验证
部署跑通只是第一步,真正决定这套系统能不能用的是下面这几组测试。每一组都给出目的、输入、预期和判定标准,照着做就行。
6.1 跨会话召回测试
目的:验证关掉服务重启后,记忆是否还能被检索到。
输入:第一次启动时写入“我的主项目用 PostgreSQL 做向量存储”;然后停掉服务,重新启动;再提问“我的向量存储用的是什么”。
预期:能召回“PostgreSQL”这条记忆。
判定标准:回答中明确包含该事实,且不是模型自己编的(可以故意写一个不常见的词进去,看能不能原样召回)。
常见失败原因:向量库容器没做数据卷挂载,重启后数据清空;写入时用了不同的user_id,导致检索时命名空间不匹配。
6.2 记忆冲突更新测试
目的:验证用户改变主意后,系统是覆盖旧记忆还是并存。
输入:先写入“接口统一用 8080 端口”,再写入“接口端口改成 9090 了”。
预期:检索“接口端口”时,优先返回 9090,或者两条都返回但要能看出时间先后。
判定标准:不会出现只召回旧值、把新值丢掉的情况。
常见失败原因:框架默认只做追加不做合并;没有配置时间戳字段,导致排序失效。这类问题建议在写入时手动加created_at元数据,检索时按时间加权。
6.3 用户隔离测试
目的:验证不同用户的记忆不串。
输入:user_001写入“我偏好深色主题”,user_002写入“我偏好浅色主题”,然后分别以两个身份检索。
预期:各自只拿到自己的偏好。
判定标准:交叉查询时不能返回对方的条目。
常见失败原因:检索时忘记传user_id,或者向量库的过滤条件写成了must之外的模式,导致过滤失效。这个问题在多租户场景里是致命错误,必须测。
6.4 记忆删除测试
目的:验证删除是彻底删除,不是标记删除。
输入:写入三条记忆,调用删除接口,再检索。
预期:检索结果为空,且向量库里对应条目确实消失。
判定标准:不只是接口返回成功,还要去向量库确认条目数变化。
常见失败原因:只删了向量没删元数据,或者只删了元数据没删向量,导致“幽灵记忆”。
6.5 长对话压缩测试
目的:验证多轮对话后,系统能否把零散信息聚成稳定事实。
输入:连续写入 10 到 20 条碎片信息,例如“我在做一个爬虫”“用的语言是 Python”“目标站点需要限速”“每天跑一次”。
预期:检索“我的爬虫项目”时,能召回多条相关事实,最好还能聚成一条概括。
判定标准:召回结果的相关性排序合理,不相关的碎片不会被顶上来。
常见失败原因:Embedding 模型对短文本区分度不够;top_k设置过大,噪声涌入。
6.6 批量导入测试
目的:验证批量任务下系统的稳定性。
输入:准备一个 JSONL 文件,每行一条记忆,200 条起。
预期:全部导入成功,失败条目有日志可查。
判定标准:导入完成后统计条数与源文件一致,向量库条目数对应。
常见失败原因:单条失败导致整批中断;没有限速把推理服务打满;没有断点记录,失败后只能全量重跑。批量这块下一节单独展开。
7. 接口 API 与批量任务实现
接口这块,最小可用集合就是三个:写入、检索、删除。上面的 FastAPI 代码已经把框架搭好了,这里补调用示例和批量处理逻辑。
7.1 接口调用示例
写入:
curl -X POST http://127.0.0.1:8080/memory/add \ -H "Content-Type: application/json" \ -d '{"text": "生产环境数据库统一用 pgvector,版本以运维提供为准", "user_id": "user_001"}'检索:
curl -X POST http://127.0.0.1:8080/memory/search \ -H "Content-Type: application/json" \ -d '{"query": "生产环境向量库用什么", "user_id": "user_001", "limit": 3}'Python 调用:
import requests BASE = "http://127.0.0.1:8080" resp = requests.post( f"{BASE}/memory/add", json={"text": "接口超时统一设置为 30 秒", "user_id": "user_001"}, timeout=60 ) print(resp.status_code, resp.json()) resp = requests.post( f"{BASE}/memory/search", json={"query": "接口超时设置多少", "user_id": "user_001", "limit": 3}, timeout=60 ) print(resp.json())调用时注意两点:一是超时时间别设太短,本地模型首 token 延迟可能到秒级;二是检索接口的limit不要贪大,3 到 5 条通常够用,给多了反而干扰模型判断。
7.2 批量导入设计
批量任务的核心不是“循环调用”,而是“可断点、可重试、可观测”。下面这份脚本把三件事都做了:
# batch_import.py import json import time import logging import requests logging.basicConfig( filename="batch_import.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s" ) BASE = "http://127.0.0.1:8080" SOURCE = "./memory_input.jsonl" DONE = "./done_ids.txt" # 读取已完成的 id,支持断点续跑 try: with open(DONE, "r", encoding="utf-8") as f: done_ids = set(line.strip() for line in f if line.strip()) except FileNotFoundError: done_ids = set() def add_with_retry(text, user_id, retries=3): for attempt in range(1, retries + 1): try: resp = requests.post( f"{BASE}/memory/add", json={"text": text, "user_id": user_id}, timeout=90 ) if resp.status_code == 200: return True logging.warning("status=%s attempt=%s", resp.status_code, attempt) except Exception as e: logging.error("exception=%s attempt=%s", e, attempt) time.sleep(2 * attempt) return False with open(SOURCE, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue item = json.loads(line) item_id = item["id"] if item_id in done_ids: continue ok = add_with_retry(item["text"], item["user_id"]) if ok: with open(DONE, "a", encoding="utf-8") as done_f: done_f.write(item_id + "\n") logging.info("success id=%s", item_id) else: logging.error("failed id=%s", item_id) # 简单限速,避免把推理服务打满 time.sleep(0.2)输入文件格式:
{"id": "m001", "user_id": "user_001", "text": "项目使用 Python 3.10 作为运行时"} {"id": "m002", "user_id": "user_001", "text": "部署环境统一用 Ubuntu 22.04"}这套设计的价值在于:任何一条失败都不会拖垮整批,重跑时直接跳过已完成的,日志里能定位到具体哪条出的问题。批量导入时建议先在 20 条的小样本上跑一遍,确认耗时和失败率,再放大到全量。如果全量有几千条,还得考虑分批提交,别一次性把向量库和推理服务同时压死。
8. 资源占用与性能观察方法
性能这块不给你编数字,只给你观察方法和影响因子。
显存占用怎么观察:Linux 下用nvidia-smi循环刷新,Windows 下用任务管理器的“专用 GPU 内存”或者nvidia-smi命令行。启动记忆服务、跑一次写入、跑一次检索,分别记录三个时间点的显存值,就能看出各组件的大致占比。
# 每秒刷新一次,观察推理过程中的显存波动 nvidia-smi --query-gpu=memory.used,memory.total,utilization.gpu --format=csv -l 1CPU 推理和 GPU 推理的差别主要在延迟上。CPU 模式下写入和检索能跑,但单次请求可能要几秒到十几秒;GPU 模式下通常能压到秒级以内。如果你的场景是后台异步写入、用户侧只做检索,CPU 模式完全可以接受。
影响性能的几个关键因子:
- 模型参数量与量化方式:这是最大变量,换模型比调参有效得多。
top_k取值:检索条数从 3 提到 20,向量库侧压力不大,但下游 LLM 的上下文变长,生成延迟会明显上升。- 批量写入的并发数:并发太高会把推理服务排队打满,反而更慢。建议串行加小间隔,或者用 2 到 4 个 worker。
- 向量库索引参数:Qdrant 在小数据量下默认配置够用,数据量到十万级以上再考虑调 HNSW 参数。
- Embedding 模型的上下文长度:单条记忆文本过长会被截断,截断后语义丢失,检索命中率下降。建议写入前就把单条压到几百字以内。
降低显存占用的常规手段:换更小的量化模型、把 Embedding 换成小参数量版本、降低并发、把向量库放到内存充足的机器上而把推理放到 GPU 机器上分离部署。最后这个方案在团队场景里很常见——向量库和推理服务本来就不该绑在一起。
进程残留是另一个常见坑。Docker 容器停了但端口没释放、uvicorn 用--reload起了多个子进程、Ollama 后台常驻占着显存,这些都会让下一次启动莫名其妙失败。养成习惯:改配置前先docker ps和端口检查走一遍。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动报端口被占用 | 上次进程未退出或容器残留 | lsof -i :端口或docker ps | 杀掉残留进程或换端口 |
| 写入接口返回成功但检索不到 | Embedding 调用失败导致静默写入失败 | 查向量库 collection 条目数 | 补日志,确认 Embedding 服务可用 |
| 检索结果跨用户串数据 | 未传user_id或过滤条件失效 | 用两个身份交叉检索验证 | 检索强制带命名空间过滤 |
| 依赖安装报版本冲突 | 全局环境混装 | 检查pip list | 用独立虚拟环境重装 |
| 显存不足报 OOM | 模型过大或并发过高 | nvidia-smi观察峰值 | 换量化模型、降并发、拆分部署 |
| 批量任务中途卡住 | 单条超时未设上限 | 查批量日志最后一条 | 加超时、加断点、加重试 |
| 召回结果相关性差 | top_k过大或 Embedding 不适配中文 | 缩小top_k看结果变化 | 换中文效果更好的向量模型 |
| 重启后记忆全丢 | 向量库未挂载数据卷 | 检查容器挂载配置 | 重新挂载持久化目录 |
| 删除后仍能召回 | 只删了一侧数据 | 分别查向量库和元数据 | 补全删除逻辑 |
| 接口响应偶发超时 | 推理服务排队 | 看服务端日志时间戳 | 加队列或提高超时阈值 |
排查顺序建议固定下来:先看服务是否真的起来了(端口、健康检查),再看单个组件能不能通(直接 curl 推理服务、直接查向量库),最后看编排层逻辑。绝大多数问题出在第一步和第二步,别一上来就怀疑代码写错了。
10. 最佳实践与使用建议
第一,先跑最小闭环,再上复杂逻辑。原文存储加检索这条链路能跑通,就已经能解决 80% 的“记不住”问题。事实抽取、冲突合并、摘要压缩这些是优化项,不是启动项。
第二,目录结构从一开始就分开。模型文件、配置文件、输入素材、向量库数据、日志,各放各的目录。等数据量上来再整理,迁移成本很高。尤其是向量库的数据目录,一定要挂载到宿主机,别留在容器内部。
第三,记忆写入前做一层过滤。不是所有对话都值得记。建议至少过滤三类:寒暄类无信息量的内容、包含敏感字段的内容、明显是模型自己生成而非用户陈述的内容。写入质量直接决定检索质量,垃圾进垃圾出在记忆系统里体现得特别明显。
第四,给记忆加时间维度和来源标记。每条记忆带上created_at、source、confidence,检索时按时间衰减加权。这样用户改变主意后,新记忆自然压过旧记忆,不需要手动清理。
第五,接口服务一定要限制访问范围。本地验证绑127.0.0.1,内网共享加鉴权和 IP 白名单,别图省事开成公网可访问。记忆库里存的东西,泄露一次的代价远高于省下的那点配置时间。
第六,合规动作前置。涉及个人信息、通话录音、聊天记录、客户资料的内容,写入前必须确认你有权处理;给他人做系统时,要能明确回答“数据存在哪、谁能访问、怎么删除”这三个问题。这已经不是技术问题,是能不能上线的门槛。
第七,建立回归测试集。准备 30 到 50 条固定的“问题—期望记忆”对,每次换模型、换 Embedding、调参数之后跑一遍,看召回率有没有下降。没有回归集,你根本不知道这次优化是变好还是变差。
最后说下一步可以往哪扩。跑通基础版之后,比较自然的三个方向是:把记忆层接到自己的 Agent 框架里做跨任务状态保持;加一层摘要压缩,把长对话定时聚成稳定事实;做一套记忆管理界面,让用户能看到、编辑、删除自己的记忆。这三个方向都不需要换技术栈,只是在现有闭环上加模块。
真要给个优先级,我建议先做第三个——可视化和可删除。因为一旦你能看见系统到底记了什么,前面那些检索不准、召回混乱的问题,很多会自己暴露出来,改起来也有的放矢。