为AI智能体构建长期记忆系统:零配置集成与四通道混合检索实践
在AI智能体(Agent)开发中,状态管理是核心挑战之一,它决定了智能体能否进行连贯、个性化的多轮对话。其基本原理是通过向量化技术将对话历史转换为可存储和检索的表示形式,从而实现跨会话的记忆持久化。这项技术的核心价值在于,它使智能体能够理解用户的历史偏好和上下文,从而提供更精准、更人性化的服务,极大地提升了对话系统的实用性和用户体验。典型的应用场景包括个人助理、客服机器人以及需要长期跟踪用户意图的
1. 项目概述:为AI智能体装上“长期记忆”
在AI智能体(Agent)的开发与使用中,一个长期存在的痛点就是“健忘症”。无论是基于OpenAI API还是本地部署的大模型,标准的对话模式都是无状态的——每次交互对于模型来说都是一次全新的开始。这意味着,你无法与一个智能体建立持续的关系,它无法记住你的偏好、你的历史对话,更无法基于过去的互动进行更深入、更个性化的交流。
这正是 @unforget-ai/openclaw 插件要解决的核心问题。它为OpenClaw框架下的智能体提供了一个开箱即用的长期记忆系统。想象一下,你正在训练一个专属的个人助理,或者构建一个需要理解用户长期意图的客服机器人。这个插件能让你的智能体像人一样,拥有记忆和遗忘的能力,从而实现真正连贯、有上下文的对话。
它的设计哲学非常明确: 零配置、零LLM调用开销、完全本地化 。你不需要申请任何外部API密钥,不需要启动复杂的Docker容器,甚至不需要手动搭建数据库。安装即用,记忆的存储、检索、更新和删除全部在后台自动完成。对于开发者而言,这意味着你可以将精力完全集中在智能体的核心逻辑和业务功能上,而无需为状态管理、向量数据库集成等基础设施问题分心。
2. 核心设计思路与架构拆解
2.1 为什么选择“零LLM写入”?
在传统的记忆系统中,存储一段记忆通常需要两个步骤:1. 使用嵌入模型(Embedding Model)将文本转换为向量;2. 将这个向量存入向量数据库。许多方案在第一步会直接调用像OpenAI的 text-embedding-ada-002 这样的API,或者使用一个本地的大语言模型来生成“记忆摘要”。
@unforget-ai/openclaw 插件摒弃了在写入时使用LLM的做法,这背后有几个关键的考量:
- 成本与延迟 :每次对话回合(Turn)都可能产生需要存储的记忆。如果每次存储都调用一次LLM API或运行一次本地大模型推理,累积下来的成本和响应延迟将是不可忽视的。尤其是在高频交互场景下,这会成为性能瓶颈。
- 确定性 :记忆的存储应该是一个确定性的、可靠的操作。依赖外部API或复杂的模型推理可能会引入不稳定性(如网络波动、模型输出随机性)。插件选择使用一个轻量级、专门优化的嵌入模型
unforget-embed,它只负责将文本转换为向量,这个操作是快速且确定的。 - 专注检索质量 :系统的核心智能体现在“回忆”阶段,而非“存储”阶段。存储时,我们只需要一个高质量的、通用的文本表示(向量)。而在回忆时,系统会动用多通道混合检索策略,从多个维度去理解当前查询与历史记忆的相关性。这种设计将计算资源用在了刀刃上。
因此,插件的写入路径极其轻量:原始对话文本 -> 轻量嵌入模型 -> 向量存储。这保证了记忆操作的实时性和高吞吐量。
2.2 四通道混合检索:超越简单的语义搜索
单一的语义搜索(即基于向量余弦相似度)在记忆检索中存在明显局限。比如,用户问“我上周三提到了什么关于项目预算的事情?”。纯语义搜索可能找到所有关于“项目”和“预算”的对话,但无法精准定位到“上周三”这个时间点。
为此,该插件实现了 四通道混合检索(4-Channel Retrieval) ,并将结果通过 倒数排序融合(Reciprocal Rank Fusion, RRF) 算法进行合并。这是一种在信息检索领域被验证有效的策略,能显著提升召回结果的相关性和多样性。
- 语义搜索(Semantic) :基于
unforget-embed生成的向量,计算与查询向量的相似度。这是理解“意思相似”的基础通道。 - 关键词搜索(BM25) :这是一个经典的文本检索算法,擅长处理精确的词项匹配、短语匹配。当用户使用非常具体的术语或名称时,BM25的效果往往比语义搜索更好。例如,记住了一个产品代号“Project Aurora”,BM25能确保这个精确名称被准确召回。
- 实体搜索(Entity) :系统会从文本中提取命名实体(如人名、地名、组织名、时间、日期等)。这个通道专门用于记忆与特定实体相关的对话。当用户提到“Alex”时,系统能快速找到所有与“Alex”这个实体相关的记忆,即使上下文中没有出现“名字”、“用户”等语义相关的词。
- 时间搜索(Temporal) :每条记忆都带有时间戳。这个通道允许进行基于时间的过滤和排序,例如“找到最近一周的记忆”或“找到在某个日期之后的对话”。这模拟了人类的记忆特性——越近发生的事情印象越深。
RRF算法的作用是,将这四个通道各自的检索结果列表(每个结果都有排名),通过一个公式进行加权合并,产生一个最终的综合排名列表。这样,既保证了语义上的相关性,又兼顾了关键词精确度、实体关联性和时间新鲜度。
2.3 插件与OpenClaw的无缝集成架构
插件的架构设计体现了“非侵入式”和“松耦合”的思想。它通过OpenClaw的插件钩子(Hooks)机制介入智能体的生命周期,而不是修改智能体本身的核心代码。
OpenClaw Agent (你的智能体应用)
│
│ 生命周期钩子: `before_agent_start`, `agent_end`
▼
@unforget-ai/openclaw Plugin (TypeScript/JavaScript)
│
│ HTTP请求 (默认 localhost:9077)
▼
unforget-embed Daemon (Python 后台进程)
├── unforget Core Library (实现4通道检索逻辑)
└── pgserver (内嵌的 PostgreSQL + pgvector 数据库)
工作流程解析:
-
before_agent_start钩子(自动回忆) :在智能体处理用户输入之前,插件被触发。它会将当前的用户查询(可能结合一些上下文)发送给unforget-embed后台进程。后台进程执行四通道混合检索,从数据库中找出最相关的K条历史记忆(autoRecallTopK可配置,默认10条)。 - 记忆注入 :检索到的记忆文本会被格式化(例如,加上“根据我们之前的对话,我记得:...”的前缀),然后作为系统提示(System Prompt)的一部分,或放在用户查询的上下文窗口中,传递给大语言模型(LLM)。这样,LLM在生成回复时,就已经“知道”了相关的历史信息。
-
agent_end钩子(自动保留) :在智能体完成一次完整的对话回合(用户输入+助手回复)后,插件再次被触发。它将这一轮对话的完整内容(或经过简单清洗的内容)发送给unforget-embed进程,由其生成向量并存储到内嵌的PostgreSQL中,形成一条新的记忆。 - 记忆管理 :当用户说出“忘记我喜欢披萨”或“记住我的名字是Kobi”这样的指令时,这些指令会先经过LLM处理(LLM能理解这是记忆操作指令)。然后,LLM的结构化输出(或插件解析的指令)会触发插件对记忆数据库进行相应的删除或更新操作。 关键在于,记忆的增删改查逻辑是由插件和
unforget-embed完成的,LLM只负责理解用户的自然语言意图 ,这解耦了逻辑,提高了可靠性。
这种架构的好处是,作为智能体开发者,你几乎感知不到记忆系统的存在。你只需要像往常一样定义智能体的角色和基础能力,记忆功能会自动附着上去,大大降低了开发复杂度。
3. 从零开始:完整安装与配置指南
3.1 环境准备与依赖安装
虽然项目宣称“零配置”,但一个正确的基础环境是必需的。以下是详细的步骤和注意事项。
首要条件:Python 3.12 unforget-embed 后台进程对Python版本有明确要求。我推荐使用 pyenv 来管理多个Python版本,这对于同时进行多个项目的开发者来说是最佳实践。
# 1. 安装pyenv(以macOS为例,其他系统请参考pyenv官网)
brew update
brew install pyenv
# 2. 将pyenv初始化添加到shell配置文件(如 ~/.zshrc)
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init -)"' >> ~/.zshrc
source ~/.zshrc
# 3. 安装Python 3.12.0 (建议安装具体的小版本号,避免兼容性问题)
pyenv install 3.12.0
pyenv global 3.12.0 # 将其设为全局默认版本,或仅在项目目录下使用 `pyenv local 3.12.0`
# 4. 验证安装
python --version # 应输出 Python 3.12.0
注意 :有些Linux发行版的包管理器可能也提供了Python 3.12,但通过
pyenv安装可以确保环境的纯净和版本的精确控制,避免与系统自带的Python发生冲突。
安装 unforget-embed 官方推荐使用 pipx 来安装 unforget-embed 。 pipx 专门用于安装和运行Python命令行应用,它会为每个应用创建独立的虚拟环境,避免依赖冲突。
# 1. 安装pipx (如果尚未安装)
python -m pip install --user pipx
python -m pipx ensurepath
# 安装后需要重新打开终端或 source 你的shell配置文件
# 2. 使用pipx安装unforget-embed
pipx install unforget-embed
# 3. 验证安装,可以尝试运行其帮助命令
unforget-embed --help
实操心得 :使用
pipx安装后,unforget-embed命令会全局可用。当你第一次启动OpenClaw并触发记忆插件时,插件会自动尝试在后台启动unforget-embed进程。你也可以手动启动它进行调试:unforget-embed serve。默认会在localhost:9077启动服务。
3.2 安装OpenClaw记忆插件
确保你已经在你的OpenClaw项目目录中。OpenClaw的插件管理系统使得安装过程非常简单。
# 在OpenClaw项目根目录下执行
openclaw plugins install @unforget-ai/openclaw
这个命令会:
- 从npm仓库下载
@unforget-ai/openclaw插件包。 - 将其安装到当前OpenClaw项目的插件目录中。
- 更新项目的配置文件(通常是
openclaw.json或openclaw.config.js),将插件添加到插件列表。
安装完成后,你通常不需要做任何其他操作。插件默认是启用的( enabled: true ),并且 autoRetain 和 autoRecall 也默认开启。下次你运行你的OpenClaw智能体时,记忆功能就已经生效了。
3.3 配置文件详解与高级设置
尽管零配置即可运行,但 openclaw.json 中的配置项让你能精细控制记忆系统的行为。配置文件通常位于项目根目录。
{
"plugins": {
"entries": {
"@unforget-ai/openclaw": {
"enabled": true, // 总开关,设为false则完全禁用记忆功能
"config": {
"orgId": "openclaw", // 组织标识符,用于在多租户环境下隔离记忆数据
"autoRetain": true, // 是否在每次对话后自动保留记忆
"autoRecall": true, // 是否在每次对话前自动回忆相关记忆
"autoRecallTopK": 10, // 每次回忆时检索并注入上下文的记忆条数上限
"debug": false // 开启调试模式,会在控制台打印详细的检索和存储日志
}
}
}
}
}
关键配置项解析:
-
orgId: 这是一个非常重要的命名空间概念。所有记忆都会存储在以orgId命名的“桶”里。如果你开发的是多用户应用(例如每个用户有自己的智能体实例),你需要为每个用户或会话设置不同的orgId,以确保他们的记忆完全隔离。对于单用户桌面应用,使用默认值即可。 -
autoRecallTopK: 这个值需要权衡。设置太小(如3),可能无法召回足够相关的记忆,影响智能体的连续性;设置太大(如50),会占用大量的LLM上下文窗口(Token),增加成本并可能稀释核心指令的注意力。 建议从默认值10开始,根据智能体的对话长度和你的上下文窗口大小进行调整。 例如,如果你的LLM上下文窗口是8K Token,每条记忆平均100 Token,那么10条记忆就是1K Token,这是比较安全的占比。 -
debug: true: 在开发阶段强烈建议开启。它会输出类似以下的日志,帮助你理解记忆系统的工作过程:
这能让你确认记忆是否被正确检索和注入。[Unforget] Recalling memories for query: "What's my name?" [Unforget] Retrieved 2 memories via semantic search. [Unforget] Retrieved 1 memory via BM25 search. [Unforget] RRF fused results: 3 memories selected. [Unforget] Injected memory context: “用户曾说过:我的名字是Alex...”
3.4 连接外部Unforget服务器(可选)
如果你已经在另一台机器上运行了Unforget服务,或者希望使用自己管理的、具有更高可用性的PostgreSQL数据库(例如云上的RDS),你可以配置插件连接外部服务器。
{
"plugins": {
"entries": {
"@unforget-ai/openclaw": {
"enabled": true,
"config": {
"apiUrl": "http://your-unforget-server-host:9077", // 指向外部服务地址
"orgId": "your-org-id",
// ... 其他配置
}
}
}
}
}
使用场景:
- 团队共享 :开发团队可以共用一个中央Unforget服务器,所有成员的智能体实例都将记忆存储在同一位置,便于管理和备份。
- 生产环境 :在内嵌的SQLite/PostgreSQL之外,你可能需要企业级的数据库,支持连接池、监控、主从复制等功能。
- 性能分离 :将记忆检索服务部署在性能更强的机器上,与你运行LLM推理的机器分离。
注意事项 :当配置了
apiUrl后,插件将不会自动启动本地的unforget-embed进程,而是直接向指定的URL发送HTTP请求。请确保该服务端已正确安装并运行unforget-embed serve,且网络可达。
4. 实战演练:记忆系统的核心操作与交互
4.1 基础对话流程与记忆的自动流转
让我们通过一个完整的终端会话示例,来直观感受记忆系统如何工作。假设我们有一个名为 MyAssistant 的OpenClaw智能体。
# 启动你的OpenClaw智能体
openclaw run MyAssistant
# 终端进入交互模式
You: 你好,我的名字是王伟,我是一名软件工程师,住在北京。
Agent: 你好,王伟!很高兴认识你,软件工程师。北京是个很棒的城市。今天有什么可以帮你的吗?
# [幕后] `agent_end`钩子触发,本轮对话“名字-王伟,职业-软件工程师,城市-北京”被向量化并存储。
You: 我最近对机器学习特别感兴趣。
Agent: 机器学习是当前非常热门的领域,有很多有趣的方向,比如深度学习、强化学习。你具体想了解哪方面呢?
# [幕后] 新的记忆“对机器学习感兴趣”被存储。
You: 对了,我的职业是什么?我住在哪里?
# [幕后] `before_agent_start`钩子触发。查询“职业是什么?住在哪里?”被发送。
# 四通道检索开始工作:
# - 语义搜索:匹配“职业”、“住”、“哪里”。
# - 实体搜索:识别出“我”可能关联的已存实体“王伟”。
# - BM25:精确匹配“职业”、“住”。
# - 时间:所有记忆时间都很近,权重相似。
# RRF融合后,前两条关于“职业”和“城市”的记忆被选出,注入本次对话的上下文。
Agent: 你之前提到过,你是一名软件工程师,并且住在北京。
这个过程完全自动化。用户和开发者都无需关心记忆是如何被保存和查找的,对话自然地进行,智能体却表现出了“记忆力”。
4.2 主动记忆管理:记住与遗忘
记忆系统不仅能被动记录,还能响应主动的指令。这是通过让LLM理解用户的自然语言指令,然后输出结构化命令给插件来实现的。
“记住”指令:
You: 请记住,我每天早上9点需要喝一杯咖啡,并且对花生严重过敏。
Agent: 明白了,我已经记下:你每天上午9点需要咖啡,并且对花生严重过敏。这些信息很重要,我会在相关场景下留意。
- 底层操作 :LLM识别出这是一个“记忆存储”的意图。插件可能会将整句指令,或由LLM提取出的结构化信息(
{“action”: “remember”, “content”: “用户每天早上9点需要喝咖啡且对花生过敏”})作为一条新记忆存储。更高级的实现中,LLM可能会将信息分解为多条原子记忆(如“偏好-咖啡-时间-9am”、“过敏源-花生”),便于未来更精确的检索。
“忘记”指令:
You: 忘记我住在北京这件事。
Agent: 好的,关于你居住在北京的信息已经被移除了。
- 底层操作 :这是更复杂的操作。插件需要定位到包含“住在北京”这条信息的记忆条目并将其删除。通常,这通过以下步骤完成:
- LLM理解指令,输出如
{“action”: “forget”, “query”: “住在北京”}的指令。 - 插件使用这个
query作为检索词,在记忆库中执行一次搜索。 - 找到最匹配的一条或几条记忆(可能通过相似度阈值控制)。
- 从数据库中物理删除或逻辑标记这些记忆条目为“已遗忘”。
- LLM理解指令,输出如
重要提示 :“忘记”操作是基于检索的,并非精确匹配。如果记忆库中有多条语义相近的记忆(例如“我住在北京朝阳区”和“我的家在北京”),可能都需要被定位和处理。目前这更像是一个“模糊删除”,对于要求精确记忆管理的场景,未来的版本可能会引入记忆的唯一ID或更细粒度的管理接口。
4.3 处理会话与记忆的隔离
在实际应用中,你可能会开启新的对话会话( /new ),或者为不同的任务创建不同的智能体实例。记忆如何在这些场景下工作?
场景一:同一智能体,新会话 正如项目描述所示,使用 /new 开始一个新会话,并不会清空记忆。因为记忆是绑定在 orgId 下的,而不是会话ID。只要 orgId 不变,新的会话依然能访问到之前的所有记忆。
You: /new
New session started.
You: 我喜欢的颜色是什么?(假设之前从未提及)
Agent: 你还没有告诉过我你喜欢的颜色。
这符合预期,因为记忆库中没有相关信息。
场景二:多智能体应用 如果你开发的是一个支持多用户的平台,每个用户有自己的智能体。 关键在于为每个用户设置不同的 orgId 。
// 在你的OpenClaw应用代码中,动态配置插件
const userId = getCurrentUserId(); // 例如 “user_123”
const agent = new OpenClaw({
// ... 其他配置
plugins: {
entries: {
'@unforget-ai/openclaw': {
enabled: true,
config: {
orgId: userId, // 用用户ID作为命名空间
// ... 其他配置
}
}
}
}
});
这样,用户A的记忆完全不会泄露给用户B,实现了数据的天然隔离。
5. 深入原理:四通道检索与RRF算法实战解析
5.1 各检索通道的实现原理与适用场景
为了真正用好这个记忆系统,理解其四个检索通道的底层原理至关重要。这能帮助你在调试和优化时,知道该从哪个角度入手。
-
语义搜索通道
- 原理 :利用
unforget-embed模型将文本(记忆和查询)转换为高维向量(例如768维)。检索时,计算查询向量与所有记忆向量的余弦相似度,取相似度最高的Top K条。 - 优势 :理解同义词和语义关联。例如,查询“编程”,能召回包含“写代码”、“软件开发”的记忆。
- 劣势 :对专有名词、精确术语不敏感;无法处理时间、数字等精确过滤。
- 适用场景 :通用对话、概念性查询、总结性提问。
- 原理 :利用
-
BM25关键词搜索通道
- 原理 :基于经典的Okapi BM25算法。它将文档(记忆)和查询视为词袋(Bag of Words),计算每个词项的权重,考虑词频(TF)和逆文档频率(IDF)。BM25分数反映了查询词项在文档中的匹配程度。
- 优势 :擅长精确匹配。对于产品型号(“iPhone 15 Pro”)、错误代码(“Error 404”)、特定人名/地名等,BM25的召回准确率通常高于语义搜索。
- 劣势 :无法理解语义。查询“ canine companion”无法匹配到包含“dog”的记忆。
- 适用场景 :精确信息查找、代码片段、技术术语、包含数字和符号的查询。
-
实体搜索通道
- 原理 :在记忆存储时,使用一个命名实体识别(NER)模型或规则,从文本中提取实体(如
[PERSON: Alex],[DATE: last Wednesday],[ORG: OpenAI])。检索时,同样从查询中提取实体,然后匹配记忆中的实体标签。 - 优势 :建立实体间的强关联网络。当用户提到一个实体(如“Alex”),系统能快速找到所有与“Alex”相关的记忆,无论上下文如何变化。
- 劣势 :依赖NER的准确性。如果实体识别错误(如把“Apple”公司识别为水果),检索就会出错。
- 适用场景 :基于人物、地点、组织、时间等实体的关系性查询。
- 原理 :在记忆存储时,使用一个命名实体识别(NER)模型或规则,从文本中提取实体(如
-
时间搜索通道
- 原理 :每条记忆都有时间戳。检索时,可以按时间倒序(最近优先)排序,或者支持基于时间范围的过滤(如“
timestamp > ‘2023-10-01’”)。 - 优势 :模拟记忆的时效性和新鲜度。人类更易记住最近的事,智能体也应如此。
- 劣势 :单独使用价值有限,必须与其他通道结合。
- 适用场景 :查询近期事务(“我上周说了什么?”)、或需要时间线上下文的任务。
- 原理 :每条记忆都有时间戳。检索时,可以按时间倒序(最近优先)排序,或者支持基于时间范围的过滤(如“
5.2 RRF融合算法:从多维度结果到统一排名
假设一次查询,四个通道分别返回了以下结果(记忆ID列表):
- 语义通道:
[记忆A, 记忆B, 记忆C] - BM25通道:
[记忆C, 记忆D, 记忆A] - 实体通道:
[记忆B, 记忆E] - 时间通道:
[记忆A, 记忆F, 记忆C](按时间倒序)
RRF算法的核心公式是: score(d) = Σ (1 / (k + rank_i(d))) 其中:
d是某个记忆文档。rank_i(d)是该文档在第i个检索通道结果列表中的排名(从1开始,如果未出现,则忽略此项或赋予一个很大的排名值)。k是一个常数,通常取60,用于平滑排名靠后文档的分数差异。
计算示例: 假设常数 k=60 。
- 记忆A :在语义通道排名1,BM25排名3,时间通道排名1。
- 分数 =
1/(60+1) + 1/(60+3) + 1/(60+1) ≈ 0.0164 + 0.0159 + 0.0164 = 0.0487
- 分数 =
- 记忆C :在语义通道排名3,BM25排名1,时间通道排名3。
- 分数 =
1/(60+3) + 1/(60+1) + 1/(60+3) ≈ 0.0159 + 0.0164 + 0.0159 = 0.0482
- 分数 =
虽然记忆C在两个通道中排名第一,但记忆A在三个通道中都出现了且排名靠前,其RRF总分略高于记忆C。最终,系统会按RRF分数对所有出现过的记忆进行降序排序,取前 autoRecallTopK 条(如10条)注入上下文。
这种融合策略的优势在于 :它不需要预先知道哪个通道更“好”,而是通过民主投票的方式,让在多个通道中都表现良好的记忆脱颖而出,从而得到更稳健、更全面的检索结果。
6. 开发与生产实践:问题排查与性能优化
6.1 常见问题与解决方案速查表
在实际集成和使用过程中,你可能会遇到以下问题。这里提供一个快速排查指南。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 插件安装后,对话无记忆效果。 | 1. 插件未启用。 2. unforget-embed 进程未启动。 3. Python版本不是3.12。 |
1. 检查 openclaw.json 中 enabled 是否为 true 。 2. 查看终端日志,是否有 [Unforget] 开头的日志?没有则进程未启动。尝试手动运行 unforget-embed serve 看是否有报错。 3. 运行 python --version 确认版本。使用 pyenv 切换至3.12。 |
控制台报错: Connection refused 或 Failed to connect 。 |
unforget-embed 服务未在 localhost:9077 上运行。 |
1. 检查 unforget-embed 进程是否存活:`ps aux |
| 记忆似乎不准确或召回无关内容。 | 1. autoRecallTopK 设置过大,注入了噪声。 2. 查询本身模糊。 3. 嵌入模型对特定领域文本效果不佳。 |
1. 将 debug 设为 true ,查看具体召回了哪些记忆。尝试调小 autoRecallTopK (如改为5)。 2. 优化你的智能体提示词,引导用户提出更明确的问题。 3. 目前 unforget-embed 使用的是通用嵌入模型。对于极专业领域(如法律、医学),可关注未来是否支持自定义模型。 |
| “忘记”指令没有生效。 | 1. LLM未能正确解析指令为“遗忘”操作。 2. 检索到的记忆与目标不完全匹配,未达到删除阈值。 |
1. 开启 debug 模式,查看插件收到的指令是什么。可能需要优化你的系统提示词,教导LLM如何格式化记忆操作指令。 2. “忘记”是模糊操作。对于关键信息,考虑在应用层实现更精确的记忆管理界面(如列出记忆并让用户选择删除)。 |
| 随着记忆增多,响应速度变慢。 | 向量检索在全量数据中进行,数据量越大,线性扫描越慢。 | 这是向量数据库的普遍问题。内嵌的PgVector支持使用IVFFlat或HNSW索引加速。你需要关注 unforget-embed 的未来版本,看是否会提供自动索引功能,或者考虑迁移到支持索引的外部PG数据库。 |
| 在多轮复杂对话后,LLM上下文窗口不足。 | 注入的记忆条数 ( autoRecallTopK ) 或单条记忆过长,挤占了对话上下文。 |
1. 减少 autoRecallTopK 。 2. 在存储记忆前,让LLM对长对话进行 摘要 ,只存储摘要而非全文。这需要自定义插件的 autoRetain 逻辑,是进阶用法。 |
6.2 性能优化与进阶调优建议
对于生产环境或高频使用的场景,可以考虑以下优化方向:
-
记忆摘要与压缩 :
- 问题 :原始的对话回合可能很长,直接存储会占用大量数据库空间,且检索时注入的Token数会很高。
- 方案 :在
autoRetain环节介入。可以配置在存储前,调用一次LLM(使用低成本模型如gpt-3.5-turbo)对当前对话回合进行总结,生成一条简洁的“记忆点”再存储。例如,将一段关于编程问题的讨论,总结为“用户正在学习Python装饰器,遇到了理解递归调用的问题”。 - 实现思路 :这需要修改插件代码或通过OpenClaw的中间件(Middleware)在调用插件前对数据进行预处理。
-
记忆分片与元数据 :
- 问题 :所有记忆混在一起,当数据量很大时,即使有索引,检索效率也会下降,且可能召回无关领域的记忆。
- 方案 :为记忆打上标签或分类。例如,在存储时,让LLM或规则引擎为记忆生成标签(如
#工作、#个人偏好、#项目A)。在检索时,可以先根据当前对话的上下文预测一个或几个相关标签,然后只在带有这些标签的记忆中进行检索,大幅缩小搜索范围。 - 实现思路 :这需要对
unforget-embed的存储结构和检索接口进行扩展,目前可能需等待官方功能或自行fork修改。
-
使用外部高性能向量数据库 :
- 问题 :内嵌的PgVector适合轻量级应用,但对于千万级甚至亿级的记忆向量,专业向量数据库(如Qdrant, Milvus, Weaviate)在分布式、索引优化、过滤查询上更有优势。
- 方案 :配置插件连接外部Unforget服务器,而该服务器后端连接的是你部署的、带有高性能向量扩展的PostgreSQL(如TimescaleDB with pgvector),或者直接使用上述专业向量数据库。这需要你自行部署和维护
unforget-embed服务并修改其数据库连接配置。
-
控制记忆的生命周期 :
- 问题 :记忆永远保存可能导致存储膨胀和检索到过时信息。
- 方案 :实现记忆的自动清理策略。例如:
- 基于时间 :自动删除30天前的记忆。
- 基于访问频率 :长期未被检索到的记忆可以归档或删除。
- 基于重要性 :在存储时让LLM对记忆的重要性打分,低分记忆定期清理。
- 实现思路 :需要定期运行一个后台任务,查询数据库并执行清理逻辑。这同样需要扩展
unforget-embed的功能或直接操作数据库。
6.3 调试技巧:利用日志洞察内部运作
将 debug 设置为 true 是最强大的调试手段。以下是一段典型的调试日志分析:
[Unforget] Query: “推荐一家附近的意大利餐厅。”
[Unforget] Semantic search top3: [记忆ID: 103 (score:0.87), 记忆ID: 205 (score:0.76), 记忆ID: 178 (score:0.71)]
[Unforget] BM25 search top3: [记忆ID: 205 (score:12.4), 记忆ID: 301 (score:9.8), 记忆ID: 103 (score:5.2)]
[Unforget] Entity search: [记忆ID: 103 (entity: ‘意大利’)]
[Unforget] Temporal search (last week): [记忆ID: 301, 178]
[Unforget] RRF fused scores: {103: 0.142, 205: 0.138, 301: 0.095, 178: 0.088}
[Unforget] Injected memories (ID: 103, 205): “用户曾说他喜欢意大利菜,尤其是披萨。”, “用户上周在市中心吃了一家不错的餐厅。”
从日志中我们可以读出:
- 语义搜索找到了关于“意大利菜”和“餐厅”的通用记忆(103, 205, 178)。
- BM25搜索因为精确匹配“意大利”这个词,给了记忆205和103很高的分数。
- 实体搜索识别出记忆103中包含“意大利”这个实体。
- 时间搜索找出了最近一周的记忆(301, 178)。
- RRF融合后,记忆103和205胜出,因为它们 在多个通道中都排名靠前 ,综合相关性最强。
- 最终,这两条记忆被注入上下文,智能体就能基于“喜欢意大利菜”和“上周在市中心有不错体验”这两个信息,做出更个性化的推荐。
通过分析这样的日志,你可以清晰地看到是哪个通道主导了检索结果,从而判断记忆系统的行为是否符合你的预期,并为调优提供依据。
记忆系统是构建高级AI智能体的基石之一。 @unforget-ai/openclaw 插件以其零配置、高性能和巧妙的多通道检索设计,大大降低了为OpenClaw智能体添加长期记忆能力的门槛。从简单的个人助手到复杂的多轮对话系统,它都能提供坚实的状态管理支持。理解其背后的架构、检索原理和配置细节,能帮助你在实际项目中更好地驾驭它,打造出真正“善解人意”且“过目不忘”的AI伙伴。
更多推荐




所有评论(0)