作者:小小工匠
文章目录
记忆(Memory)记忆文件(Markdown)何时写入记忆自动记忆刷新(压缩前提醒)向量记忆搜索
QMD 后端(实验性)额外记忆路径Gemini 嵌入(原生)记忆工具的工作原理索引内容(及时机)混合搜索(BM25 + 向量)为什么使用混合搜索?结果合并方式(当前设计)嵌入缓存会话记忆搜索(实验性)SQLite 向量加速(sqlite-vec)本地嵌入自动下载自定义 OpenAI 兼容端点示例
记忆(Memory)
OpenClaw 的记忆是代理工作区中的纯 Markdown 文件。文件是唯一的事实来源;模型只"记住"写入磁盘的内容。
记忆搜索工具由活跃的记忆插件提供(默认:memory-core)。可通过 plugins.slots.memory = "none" 禁用记忆插件。
记忆文件(Markdown)
默认工作区布局使用两个记忆层:
memory/YYYY-MM-DD.md
每日日志(仅追加)。会话启动时读取今天和昨天的日志。
- MEMORY.md(可选)
精心整理的长期记忆。仅在主私有会话中加载(不在群组上下文中加载)。
这些文件位于工作区目录下(agents.defaults.workspace,默认 ~/.openclaw/workspace)。完整布局请参阅 Agent workspace。
何时写入记忆
决策、偏好和持久性事实写入 MEMORY.md。日常笔记和运行上下文写入 memory/YYYY-MM-DD.md。如果有人说"记住这个",就写下来(不要只保留在内存中)。这个领域仍在发展中。提醒模型存储记忆会有帮助;模型知道该怎么做。如果你想让某些内容持久保存,让机器人将其写入记忆。
自动记忆刷新(压缩前提醒)
当会话接近自动压缩时,OpenClaw 会触发一个静默的代理轮次,提醒模型在上下文被压缩之前写入持久记忆。默认提示词明确表示模型可以回复,但通常 NO_REPLY 是正确的响应,这样用户永远不会看到这个轮次。
通过 agents.defaults.compaction.memoryFlush 控制:- {
- agents: {
- defaults: {
- compaction: {
- reserveTokensFloor: 20000,
- memoryFlush: {
- enabled: true,
- softThresholdTokens: 4000,
- systemPrompt: "Session nearing compaction. Store durable memories now.",
- prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.",
- },
- },
- },
- },
- }
复制代码 详细说明:
软阈值:当会话 token 估计值超过 contextWindow - reserveTokensFloor - softThresholdTokens 时触发刷新。默认静默:提示词包含 NO_REPLY,因此不会向用户发送任何内容。两个提示词:一个用户提示词加一个系统提示词附加提醒。每个压缩周期仅刷新一次(在 sessions.json 中跟踪)。工作区必须可写:如果会话以沙箱模式运行且 workspaceAccess: "ro" 或 "none",则跳过刷新。
完整的压缩生命周期请参阅 Session management + compaction。
向量记忆搜索
OpenClaw 可以在 MEMORY.md 和 memory/*.md 上构建小型向量索引,使语义查询即使在措辞不同时也能找到相关笔记。
默认设置:
默认启用。监视记忆文件的变化(防抖处理)。在 agents.defaults.memorySearch 下配置记忆搜索(不是顶层的 memorySearch)。
- 默认使用远程嵌入。如果未设置 memorySearch.provider,OpenClaw 自动选择:
如果配置了 memorySearch.local.modelPath 且文件存在,则使用 local。如果可以解析 OpenAI 密钥,则使用 openai。如果可以解析 Gemini 密钥,则使用 gemini。如果可以解析 Voyage 密钥,则使用 voyage。否则记忆搜索保持禁用状态直到配置完成。
本地模式使用 node-llama-cpp,可能需要 pnpm approve-builds。可用时使用 sqlite-vec 加速 SQLite 中的向量搜索。
远程嵌入需要嵌入提供商的 API 密钥。OpenClaw 从认证配置文件、models.providers.*.apiKey 或环境变量中解析密钥。Codex OAuth 仅覆盖 chat/completions,不满足记忆搜索的嵌入需求。对于 Gemini,使用 GEMINI_API_KEY 或 models.providers.google.apiKey。对于 Voyage,使用 VOYAGE_API_KEY 或 models.providers.voyage.apiKey。使用自定义 OpenAI 兼容端点时,设置 memorySearch.remote.apiKey(以及可选的 memorySearch.remote.headers)。
QMD 后端(实验性)
设置 memory.backend = "qmd" 可将内置 SQLite 索引器替换为 QMD:一个本地优先的搜索 sidecar,结合了 BM25 + 向量 + 重排序。Markdown 仍然是事实来源;OpenClaw 通过 shell 调用 QMD 进行检索。
前置条件
默认禁用。通过配置启用(memory.backend = "qmd")。需单独安装 QMD CLI(bun install -g https://github.com/tobi/qmd 或下载发布版本),并确保 qmd 二进制文件在网关的 PATH 中。QMD 需要支持扩展的 SQLite 构建(macOS 上使用 brew install sqlite)。QMD 通过 Bun + node-llama-cpp 完全在本地运行,首次使用时自动从 HuggingFace 下载 GGUF 模型(无需单独的 Ollama 守护进程)。网关在 ~/.openclaw/agents/<agentId>/qmd/ 下运行 QMD 的自包含 XDG 主目录,通过设置 XDG_CONFIG_HOME 和 XDG_CACHE_HOME。操作系统支持:安装 Bun + SQLite 后,macOS 和 Linux 开箱即用。Windows 最好通过 WSL2 支持。
Sidecar 运行方式
网关在 ~/.openclaw/agents/<agentId>/qmd/ 下写入自包含的 QMD 主目录(配置 + 缓存 + SQLite 数据库)。通过 qmd collection add 从 memory.qmd.paths(加上默认工作区记忆文件)创建集合,然后在启动时和可配置的间隔(memory.qmd.update.interval,默认 5 分钟)运行 qmd update + qmd embed。网关现在在启动时初始化 QMD 管理器,因此在第一次 memory_search 调用之前就已启动定期更新计时器。启动刷新现在默认在后台运行,不会阻塞聊天启动;设置 memory.qmd.update.waitForBootSync = true 可保持之前的阻塞行为。搜索通过 memory.qmd.searchMode 运行(默认 qmd search --json;也支持 vsearch 和 query)。如果所选模式在你的 QMD 构建上拒绝标志,OpenClaw 会用 qmd query 重试。如果 QMD 失败或二进制文件缺失,OpenClaw 自动回退到内置 SQLite 管理器,确保记忆工具继续工作。OpenClaw 目前不暴露 QMD 嵌入批量大小调优;批量行为由 QMD 自身控制。首次搜索可能较慢:QMD 可能在首次 qmd query 运行时下载本地 GGUF 模型(重排序器/查询扩展)。
配置项(memory.qmd.*)
command(默认 qmd):覆盖可执行文件路径。searchMode(默认 search):选择哪个 QMD 命令支持 memory_search(search、vsearch、query)。includeDefaultMemory(默认 true):自动索引 MEMORY.md + memory/**/*.md。paths[]:添加额外的目录/文件(path,可选 pattern,可选稳定 name)。sessions:选择加入会话 JSONL 索引(enabled、retentionDays、exportDir)。update:控制刷新频率和维护执行(interval、debounceMs、onBoot、waitForBootSync、embedInterval、commandTimeoutMs、updateTimeoutMs、embedTimeoutMs)。limits:限制召回负载(maxResults、maxSnippetChars、maxInjectedChars、timeoutMs)。scope:与 session.sendPolicy 相同的 schema。默认仅限私聊(deny 全部,allow 直接聊天);放宽以在群组/频道中显示 QMD 命中结果。
引用与回退
memory.citations 无论后端如何都适用(auto/on/off)。当 qmd 运行时,我们标记 status().backend = "qmd" 以便诊断显示哪个引擎提供了结果。如果 QMD 子进程退出或 JSON 输出无法解析,搜索管理器记录警告并返回内置提供商(现有 Markdown 嵌入),直到 QMD 恢复。
额外记忆路径
如果你想索引默认工作区布局之外的 Markdown 文件,可添加显式路径:- agents: {
- defaults: {
- memorySearch: {
- extraPaths: ["../team-docs", "/srv/shared-notes/overview.md"]
- }
- }
- }
复制代码 说明:
路径可以是绝对路径或工作区相对路径。目录会递归扫描 .md 文件。仅索引 Markdown 文件。忽略符号链接(文件或目录)。
Gemini 嵌入(原生)
将 provider 设置为 gemini 以直接使用 Gemini 嵌入 API:- agents: {
- defaults: {
- memorySearch: {
- provider: "gemini",
- model: "gemini-embedding-001",
- remote: {
- apiKey: "YOUR_GEMINI_API_KEY"
- }
- }
- }
- }
复制代码 说明:
remote.baseUrl 是可选的(默认为 Gemini API 基础 URL)。remote.headers 允许你在需要时添加额外的请求头。默认模型:gemini-embedding-001。
如果你想使用自定义 OpenAI 兼容端点(OpenRouter、vLLM 或代理),可以使用 OpenAI provider 的 remote 配置:- agents: {
- defaults: {
- memorySearch: {
- provider: "openai",
- model: "text-embedding-3-small",
- remote: {
- baseUrl: "https://api.example.com/v1/",
- apiKey: "YOUR_OPENAI_COMPAT_API_KEY",
- headers: { "X-Custom-Header": "value" }
- }
- }
- }
- }
复制代码 如果不想设置 API 密钥,使用 memorySearch.provider = "local" 或设置 memorySearch.fallback = "none"。
回退机制:
memorySearch.fallback 可以是 openai、gemini、local 或 none。回退提供商仅在主嵌入提供商失败时使用。
批量索引(OpenAI + Gemini + Voyage):
默认禁用。设置 agents.defaults.memorySearch.remote.batch.enabled = true 以启用大规模语料库索引(OpenAI、Gemini 和 Voyage)。默认行为等待批量完成;可调整 remote.batch.wait、remote.batch.pollIntervalMs 和 remote.batch.timeoutMinutes。设置 remote.batch.concurrency 控制并行提交的批量作业数(默认:2)。批量模式在 memorySearch.provider = "openai" 或 "gemini" 时适用,并使用相应的 API 密钥。
记忆工具的工作原理
memory_search 对来自 MEMORY.md + memory/**/*.md 的 Markdown 块(约 400 token 目标,80 token 重叠)进行语义搜索。返回片段文本(上限约 700 字符)、文件路径、行范围、分数、提供商/模型,以及是否从本地回退到远程嵌入。不返回完整文件内容。memory_get 读取特定的记忆 Markdown 文件(工作区相对路径),可选从起始行读取 N 行。MEMORY.md / memory/ 之外的路径会被拒绝。两个工具仅在 memorySearch.enabled 对代理解析为 true 时启用。
索引内容(及时机)
文件类型:仅 Markdown(MEMORY.md、memory/**/*.md)。索引存储:每个代理的 SQLite 位于 ~/.openclaw/memory/<agentId>.sqlite(可通过 agents.defaults.memorySearch.store.path 配置,支持 {agentId} 令牌)。新鲜度:MEMORY.md + memory/ 上的监视器标记索引为脏(防抖 1.5 秒)。同步在会话启动时、搜索时或按间隔调度,异步运行。会话记录使用增量阈值触发后台同步。重新索引触发器:索引存储嵌入的 provider/model + 端点指纹 + 分块参数。如果其中任何一个发生变化,OpenClaw 自动重置并重新索引整个存储。
混合搜索(BM25 + 向量)
启用后,OpenClaw 结合:
向量相似度(语义匹配,措辞可以不同)BM25 关键词相关性(精确 token,如 ID、环境变量、代码符号)
如果你的平台上全文搜索不可用,OpenClaw 回退到仅向量搜索。
为什么使用混合搜索?
向量搜索擅长"这意味着同一件事":
“Mac Studio 网关主机” vs “运行网关的机器”“防抖文件更新” vs “避免每次写入都索引”
但对于精确的高信号 token 可能较弱:
ID(a828e60、b3b9895a…)代码符号(memorySearch.query.hybrid)错误字符串(“sqlite-vec unavailable”)
BM25(全文)则相反:擅长精确 token,弱于释义。混合搜索是务实的折中方案:同时使用两种检索信号,使"自然语言"查询和"大海捞针"查询都能获得良好结果。
结果合并方式(当前设计)
实现概要:
从两侧检索候选池:
向量:按余弦相似度取前 maxResults * candidateMultiplier 个。BM25:按 FTS5 BM25 排名取前 maxResults * candidateMultiplier 个(越低越好)。
将 BM25 排名转换为 0…1 范围的分数:
textScore = 1 / (1 + max(0, bm25Rank))
按 chunk id 合并候选并计算加权分数:
finalScore = vectorWeight * vectorScore + textWeight * textScore
说明:
vectorWeight + textWeight 在配置解析中归一化为 1.0,因此权重表现为百分比。如果嵌入不可用(或提供商返回零向量),仍运行 BM25 并返回关键词匹配。如果无法创建 FTS5,保持仅向量搜索(不会硬失败)。
配置:- agents: {
- defaults: {
- memorySearch: {
- query: {
- hybrid: {
- enabled: true,
- vectorWeight: 0.7,
- textWeight: 0.3,
- candidateMultiplier: 4
- }
- }
- }
- }
- }
复制代码 嵌入缓存
OpenClaw 可以在 SQLite 中缓存块嵌入,这样重新索引和频繁更新(尤其是会话记录)不会重新嵌入未更改的文本。
配置:- agents: {
- defaults: {
- memorySearch: {
- cache: {
- enabled: true,
- maxEntries: 50000
- }
- }
- }
- }
复制代码 会话记忆搜索(实验性)
你可以选择索引会话记录并通过 memory_search 呈现它们。此功能受实验性标志控制。- agents: {
- defaults: {
- memorySearch: {
- experimental: { sessionMemory: true },
- sources: ["memory", "sessions"]
- }
- }
- }
复制代码 说明:
会话索引是可选加入的(默认关闭)。会话更新经过防抖处理,在超过增量阈值后异步索引(尽力而为)。memory_search 永远不会阻塞索引;在后台同步完成之前结果可能略有延迟。结果仍然只包含片段;memory_get 仍限于记忆文件。会话索引按代理隔离(仅索引该代理的会话日志)。会话日志存储在磁盘上(~/.openclaw/agents/<agentId>/sessions/*.jsonl)。任何具有文件系统访问权限的进程/用户都可以读取它们,因此将磁盘访问视为信任边界。要实现更严格的隔离,请在不同的操作系统用户或主机下运行代理。
增量阈值(显示默认值):- agents: {
- defaults: {
- memorySearch: {
- sync: {
- sessions: {
- deltaBytes: 100000, // ~100 KB
- deltaMessages: 50 // JSONL 行数
- }
- }
- }
- }
- }
复制代码 SQLite 向量加速(sqlite-vec)
当 sqlite-vec 扩展可用时,OpenClaw 将嵌入存储在 SQLite 虚拟表(vec0)中,并在数据库中执行向量距离查询。这使搜索保持快速,无需将每个嵌入加载到 JS 中。
配置(可选):- agents: {
- defaults: {
- memorySearch: {
- store: {
- vector: {
- enabled: true,
- extensionPath: "/path/to/sqlite-vec"
- }
- }
- }
- }
- }
复制代码 说明:
enabled 默认为 true;禁用时,搜索回退到对存储嵌入的进程内余弦相似度计算。如果 sqlite-vec 扩展缺失或加载失败,OpenClaw 记录错误并继续使用 JS 回退(无向量表)。extensionPath 覆盖捆绑的 sqlite-vec 路径(适用于自定义构建或非标准安装位置)。
本地嵌入自动下载
默认本地嵌入模型:hf:ggml-org/embeddinggemma-300m-qat-q8_0-GGUF/embeddinggemma-300m-qat-Q8_0.gguf(约 0.6 GB)。当 memorySearch.provider = "local" 时,node-llama-cpp 解析 modelPath;如果 GGUF 缺失,会自动下载到缓存(或 local.modelCacheDir,如果已设置),然后加载。下载支持断点续传。原生构建要求:运行 pnpm approve-builds,选择 node-llama-cpp,然后 pnpm rebuild node-llama-cpp。回退:如果本地设置失败且 memorySearch.fallback = "openai",自动切换到远程嵌入(openai/text-embedding-3-small,除非被覆盖)并记录原因。
自定义 OpenAI 兼容端点示例
- agents: {
- defaults: {
- memorySearch: {
- provider: "openai",
- model: "text-embedding-3-small",
- remote: {
- baseUrl: "https://api.example.com/v1/",
- apiKey: "YOUR_REMOTE_API_KEY",
- headers: {
- "X-Organization": "org-id",
- "X-Project": "project-id"
- }
- }
- }
- }
- }
复制代码 说明:
remote.* 优先于 models.providers.openai.*。remote.headers 与 OpenAI 请求头合并;冲突时 remote 优先。省略 remote.headers 则使用 OpenAI 默认值。
原文地址:https://blog.csdn.net/yangshangwei/article/details/158432933 |