学途智助
首页
分类
标签
关于网站
登录
eeettt
2026-03-25
2
作者编辑
openclaw 记忆体机制
# OpenClaw 记忆系统与上下文管理深度分析 > 本文档详细分析 OpenClaw 的记忆系统和上下文管理机制的实现原理和架构设计。 > > 分析基于: OpenClaw GitHub 仓库 (https://github.com/openclaw/openclaw) > > 最后更新: 2026-03-25 --- ## 目录 1. [核心设计理念](#核心设计理念) 2. [记忆系统架构](#记忆系统架构) 3. [上下文管理架构](#上下文管理架构) 4. [记忆与上下文的协作机制](#记忆与上下文的协作机制) 5. [技术实现细节](#技术实现细节) 6. [与 OpenViking 的对比](#与-openviking-的对比) 7. [最佳实践与调优](#最佳实践与调优) --- ## 核心设计理念 ### OpenClaw 的记忆哲学 > **"记忆是 Markdown,文件是真相源"** OpenClaw 采用了一个简单但强大的理念: 1. **记忆 = Markdown 文件** - 所有记忆都存储在 Agent 工作区的 Markdown 文件中 - 模型只"记得"写入磁盘的内容 - 人类可读、可编辑、可版本控制 2. **上下文 = 发送给模型的所有内容** - 上下文不等于记忆 - 记忆可以持久化存储,上下文是当前窗口内的内容 - 上下文受模型的 token 限制约束 3. **可插拔设计** - 记忆插件:控制记忆的存储和检索 - 上下文引擎:控制上下文的组装和压缩 - 允许外部系统(如 OpenViking)接管这些功能 --- ## 记忆系统架构 ### 1. 记忆文件层次结构 ``` ~/.openclaw/workspace/ ├── MEMORY.md # 长期记忆(精选) ├── memory/ # 每日记忆目录 │ ├── 2026-03-24.md # 昨天的记忆 │ ├── 2026-03-25.md # 今天的记忆(追加式) │ └── ... ├── AGENTS.md # Agent 配置 ├── SOUL.md # Agent 个性/灵魂 ├── TOOLS.md # 工具说明 ├── IDENTITY.md # 身份信息 ├── USER.md # 用户信息 └── ... ``` #### 记忆文件加载规则 | 文件 | 加载时机 | 作用域 | 说明 | |------|---------|--------|------| | `MEMORY.md` | 会话开始 | 主会话(私聊) | 精选的长期记忆,不在群组中加载 | | `memory/YYYY-MM-DD.md` | 会话开始 | 所有会话 | 加载今天和昨天的日志 | | 其他工作区文件 | 注入到系统提示 | 所有会话 | 配置、身份、工具说明等 | ### 2. 记忆工具 API OpenClaw 提供两个 Agent 可用的记忆工具: #### `memory_search` - 语义搜索 ```typescript // 工具签名 memory_search(query: string, options?: { maxResults?: number, timeDecay?: boolean, mmr?: boolean }): Promise<SearchResult[]> // 返回结果 interface SearchResult { text: string; // 记忆片段内容 path: string; // 文件路径(如 "memory/2026-03-25.md") line?: number; // 行号 score: number; // 相似度分数 } ``` **实现方式**: - 使用 Embedding 模型将查询和记忆文本向量化 - 通过向量相似度搜索(SQLite + sqlite-vec 或 LanceDB) - 可选的混合搜索(BM25 + Vector) - MMR 多样性重排序 - 时间衰减(最近的记忆权重更高) #### `memory_get` - 精确读取 ```typescript // 工具签名 memory_get(path: string, lineRange?: { start?: number, end?: number }): Promise<{ text: string; path: string }> // 例子 memory_get("memory/2026-03-25.md", { start: 10, end: 20 }) ``` **实现方式**: - 直接读取文件系统 - 支持行范围读取 - **优雅降级**:文件不存在时返回 `{ text: "", path }` 而不是抛出错误 ### 3. 自动记忆刷新 (Automatic Memory Flush) **问题**:当上下文窗口接近满时,如果直接压缩,可能会丢失重要信息。 **解决方案**:在压缩前触发一个**静默的 agentic turn**,提醒模型将重要内容写入持久记忆。 #### 触发时机 ```typescript // 配置路径: agents.defaults.compaction.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." } ``` **计算公式**: ``` 触发条件: sessionTokens > (contextWindow - reserveTokensFloor - softThresholdTokens) ``` **执行流程**: 1. 检测到接近压缩阈值 2. 注入系统提示和用户提示 3. 模型运行一个 turn,可能调用 `write` 工具写入记忆 4. 模型回复 `NO_REPLY`(不会展示给用户) 5. 标记本次压缩周期已刷新(存储在 `sessions.json` 中) 6. 继续正常压缩流程 **限制条件**: - 每个压缩周期只触发一次 - 工作区必须可写(`workspaceAccess: "rw"`) - 非沙盒模式 ### 4. 向量记忆搜索实现 #### 4.1 Embedding 提供商 OpenClaw 支持多种 Embedding 提供商: | 提供商 | 配置 | 用途 | |--------|------|------| | OpenAI | `OPENAI_API_KEY` | 远程 Embedding | | Gemini | `GEMINI_API_KEY` | 远程 Embedding | | Voyage | `VOYAGE_API_KEY` | 远程 Embedding | | Mistral | `MISTRAL_API_KEY` | 远程 Embedding | | Ollama | `OLLAMA_API_KEY` | 本地/自托管 Embedding | | Local GGUF | `memorySearch.local.modelPath` | 本地模型(node-llama-cpp) | **自动选择逻辑**(优先级从高到低): 1. 如果配置了 `memorySearch.local.modelPath` 且文件存在 → `local` 2. 如果有 OpenAI key → `openai` 3. 如果有 Gemini key → `gemini` 4. 如果有 Voyage key → `voyage` 5. 如果有 Mistral key → `mistral` 6. 否则记忆搜索保持禁用 #### 4.2 向量索引架构 **默认后端:SQLite + sqlite-vec** ```typescript // 代码位置: src/memory/manager.ts class MemoryManager { private db: Database; // SQLite 数据库 // 索引结构 CREATE TABLE memory_chunks ( id INTEGER PRIMARY KEY, path TEXT NOT NULL, line INTEGER, text TEXT NOT NULL, embedding BLOB, -- 向量(sqlite-vec) created_at INTEGER, updated_at INTEGER ); // 向量搜索 async search(query: string, k: number) { const queryEmbedding = await this.embed(query); return this.db.prepare(` SELECT *, vec_distance_cosine(embedding, ?) as distance FROM memory_chunks ORDER BY distance ASC LIMIT ? `).all(queryEmbedding, k); } } ``` **可选后端:LanceDB** ```typescript // 代码位置: extensions/memory-lancedb/index.ts // 使用 LanceDB 作为向量数据库 // 适合大规模记忆数据(> 10万条记忆片段) ``` #### 4.3 混合搜索 (Hybrid Search) ```typescript // BM25 (关键词搜索) + Vector (语义搜索) // 代码位置: src/memory/hybrid.ts function hybridSearch(query: string, alpha: number = 0.5) { const bm25Results = bm25Search(query); // 关键词匹配 const vectorResults = vectorSearch(query); // 语义相似 // 融合分数 return bm25Results.map((bm25, i) => { const vector = vectorResults[i]; return { ...bm25, score: alpha * bm25.score + (1 - alpha) * vector.score }; }).sort((a, b) => b.score - a.score); } ``` #### 4.4 MMR 多样性重排序 ```typescript // 代码位置: src/memory/mmr.ts // Maximal Marginal Relevance - 增加结果多样性 function mmrRerank( query: Embedding, candidates: SearchResult[], lambda: number = 0.5, // 相关性 vs 多样性权衡 k: number = 6 ) { const selected = []; const remaining = [...candidates]; while (selected.length < k && remaining.length > 0) { let maxScore = -Infinity; let maxIdx = -1; for (let i = 0; i < remaining.length; i++) { const candidate = remaining[i]; // 相关性分数 const relevance = cosineSimilarity(query, candidate.embedding); // 与已选结果的最大相似度(多样性惩罚) const maxSim = selected.length > 0 ? Math.max(...selected.map(s => cosineSimilarity(candidate.embedding, s.embedding) )) : 0; // MMR 分数 const mmrScore = lambda * relevance - (1 - lambda) * maxSim; if (mmrScore > maxScore) { maxScore = mmrScore; maxIdx = i; } } selected.push(remaining[maxIdx]); remaining.splice(maxIdx, 1); } return selected; } ``` #### 4.5 时间衰减 (Temporal Decay) ```typescript // 代码位置: src/memory/temporal-decay.ts // 最近的记忆权重更高 function applyTemporalDecay( results: SearchResult[], halfLifeDays: number = 30 ) { const now = Date.now(); return results.map(result => { const ageMs = now - result.created_at; const ageDays = ageMs / (1000 * 60 * 60 * 24); // 指数衰减 const decayFactor = Math.exp(-Math.log(2) * ageDays / halfLifeDays); return { ...result, score: result.score * decayFactor }; }); } ``` ### 5. QMD 后端(实验性) **QMD** = Query Memory Database,一个本地优先的搜索 sidecar。 #### 特点 - 结合 BM25 + 向量 + 重排序 - 完全本地运行(Bun + node-llama-cpp) - 自动下载 GGUF 模型 - 支持查询扩展和语义重排序 #### 架构 ``` OpenClaw Gateway ↓ (spawn subprocess) QMD CLI Process ↓ (XDG_CONFIG_HOME / XDG_CACHE_HOME) ~/.openclaw/agents/<agentId>/qmd/ ├── xdg-config/ │ └── qmd/config.json ├── xdg-cache/ │ └── llama.cpp models/ └── collections/ ├── memory-root.db (SQLite) └── sessions/ (optional) ``` #### 配置示例 ```json5 // openclaw.json { memory: { backend: "qmd", // 切换到 QMD 后端 citations: "auto", qmd: { command: "qmd", // QMD 可执行文件路径 searchMode: "search", // "search" | "vsearch" | "query" includeDefaultMemory: true, update: { interval: "5m", // 更新间隔 debounceMs: 15000, onBoot: true, waitForBootSync: false // 不阻塞启动 }, limits: { maxResults: 6, maxSnippetChars: 2000, timeoutMs: 4000 }, paths: [ { name: "docs", path: "~/notes", pattern: "**/*.md" } ], sessions: { enabled: true, // 索引会话记录 retentionDays: 30 } } } } ``` #### 与内置 SQLite 的对比 | 特性 | SQLite (memory-core) | QMD | |------|---------------------|-----| | 安装 | 内置 | 需要单独安装 `qmd` CLI | | 依赖 | sqlite-vec | Bun + node-llama-cpp + SQLite | | 搜索算法 | Vector (+ optional BM25) | BM25 + Vector + Reranking | | 查询扩展 | ❌ | ✅ | | 重排序 | MMR | 语义重排序模型 | | 性能 | 快 | 较慢(首次启动下载模型) | | 适用场景 | 一般使用 | 高级检索需求 | --- ## 上下文管理架构 ### 1. 上下文的定义 **Context(上下文)** = 发送给 LLM 的所有内容,受模型的上下文窗口(token 限制)约束。 #### 上下文组成 ``` ┌─────────────────────────────────────────────┐ │ Context Window │ ├─────────────────────────────────────────────┤ │ 1. System Prompt (系统提示) │ │ - 工具列表 + 描述 │ │ - 技能列表 (metadata only) │ │ - 工作区位置 │ │ - 时间信息 (UTC + 用户时区) │ │ - 运行时元数据 │ │ - Project Context (注入的工作区文件) │ │ │ │ 2. Conversation History (对话历史) │ │ - 用户消息 │ │ - 助手消息 │ │ │ │ 3. Tool Calls/Results (工具调用/结果) │ │ - 命令输出 │ │ - 文件读取 │ │ - 图片/音频转录 │ │ │ │ 4. Attachments (附件) │ │ - 图片 │ │ - 音频 │ │ - 文件 │ └─────────────────────────────────────────────┘ ``` ### 2. 系统提示构建 **系统提示是 OpenClaw 拥有的**,每次运行时重新构建。 #### 构建流程 ```typescript // 伪代码,简化版 function buildSystemPrompt(session: Session, config: Config) { let prompt = ""; // 1. 工具列表 prompt += buildToolList(session.availableTools); // 2. 技能列表(只有 metadata,不包括完整指令) prompt += buildSkillsList(session.availableSkills); // 3. 工作区信息 prompt += `Workspace: ${session.workspace}\n`; // 4. 时间信息 prompt += `Current time: ${formatTime(Date.now(), session.timezone)}\n`; // 5. 运行时元数据 prompt += `Host: ${os.hostname()}, OS: ${os.platform()}, Model: ${session.model}\n`; // 6. Project Context - 注入工作区文件 const bootstrapFiles = [ "AGENTS.md", "SOUL.md", "TOOLS.md", "IDENTITY.md", "USER.md", "HEARTBEAT.md", "BOOTSTRAP.md" ]; for (const file of bootstrapFiles) { const content = readWorkspaceFile(file); if (content) { prompt += `\n## ${file}\n${truncate(content, config.bootstrapMaxChars)}\n`; } } return prompt; } ``` #### 注入的工作区文件 | 文件 | 作用 | 截断限制 | |------|------|---------| | `AGENTS.md` | Agent 配置和说明 | 20,000 字符/文件 | | `SOUL.md` | Agent 个性/风格 | 20,000 字符/文件 | | `TOOLS.md` | 工具使用说明 | 20,000 字符/文件 | | `IDENTITY.md` | Agent 身份信息 | 20,000 字符/文件 | | `USER.md` | 用户信息 | 20,000 字符/文件 | | `HEARTBEAT.md` | 心跳/状态信息 | 20,000 字符/文件 | | `BOOTSTRAP.md` | 首次运行指南 | 20,000 字符/文件(仅首次) | **总限制**:所有注入文件的总大小不超过 `bootstrapTotalMaxChars`(默认 150,000 字符)。 #### 技能 vs 工具的注入策略 - **工具**: - 工具列表文本(显示在系统提示中) - 工具 JSON Schema(发送给模型,用于函数调用) - 两者都计入上下文 - **技能**: - 只在系统提示中包含**技能列表**(名称 + 描述 + 位置) - 技能的完整指令**不会**默认注入 - 模型需要时使用 `read` 工具读取技能的 `SKILL.md` **为什么这样设计?** - 减少系统提示的大小 - 按需加载技能指令 - 让模型决定何时需要读取技能 ### 3. 上下文引擎 (Context Engine) **上下文引擎**是 OpenClaw 的核心抽象,控制上下文的组装、压缩和管理。 #### 可插拔架构 ```typescript // 代码位置: src/context-engine/types.ts interface ContextEngine { info: { id: string; name: string; version?: string; ownsCompaction: boolean; // 是否接管压缩 }; // 生命周期钩子 ingest(params: IngestParams): Promise<IngestResult>; assemble(params: AssembleParams): Promise<AssembleResult>; compact(params: CompactParams): Promise<CompactResult>; // 可选钩子 bootstrap?(params: BootstrapParams): Promise<void>; ingestBatch?(params: IngestBatchParams): Promise<void>; afterTurn?(params: AfterTurnParams): Promise<void>; prepareSubagentSpawn?(params: PrepareSubagentSpawnParams): Promise<any>; onSubagentEnded?(params: OnSubagentEndedParams): Promise<void>; dispose?(): Promise<void>; } ``` #### 生命周期 ``` ┌─────────────────────────────────────────────────────────┐ │ 上下文引擎生命周期 │ └─────────────────────────────────────────────────────────┘ 用户发送消息 ↓ 1. INGEST (接收) - 存储消息到引擎的数据存储 - 可以进行索引、向量化等 ↓ 2. ASSEMBLE (组装) - 返回符合 token 预算的消息列表 - 可选:返回 systemPromptAddition ↓ 3. 模型运行 - OpenClaw 发送组装好的上下文到 LLM - LLM 生成回复 ↓ 4. AFTER TURN (后处理) - 持久化状态 - 触发后台压缩 - 更新索引 ↓ (可选)当上下文满时 ↓ 5. COMPACT (压缩) - 压缩旧历史 - 释放上下文空间 ``` #### Legacy Engine(默认) ```typescript // 代码位置: src/context-engine/legacy.ts export const legacyEngine: ContextEngine = { info: { id: "legacy", name: "Legacy Context Engine", ownsCompaction: true }, async ingest({ sessionId, message, isHeartbeat }) { // no-op(由 session manager 直接处理) return { ingested: true }; }, async assemble({ sessionId, messages, tokenBudget }) { // 直接传递消息 // 现有的 sanitize → validate → limit 管道处理 return { messages: messages, estimatedTokens: estimateTokens(messages) }; }, async compact({ sessionId, force }) { // 委托给内置的摘要压缩 // 创建旧消息的单一摘要,保留最近消息 return builtinSummarizationCompaction(sessionId, force); } }; ``` #### Plugin Engine 示例 ```typescript // 假设的 LosslessClaw 引擎(如 OpenViking) export const losslessClawEngine: ContextEngine = { info: { id: "lossless-claw", name: "LosslessClaw Context Engine", ownsCompaction: true }, async ingest({ sessionId, message, isHeartbeat }) { // 将消息存储到 OpenViking 的 viking:// 文件系统 await vikingStore(sessionId, message); return { ingested: true }; }, async assemble({ sessionId, messages, tokenBudget }) { // 从 OpenViking 检索相关上下文 const relevantContext = await vikingRetrieve(sessionId, tokenBudget); return { messages: relevantContext.messages, estimatedTokens: relevantContext.estimatedTokens, systemPromptAddition: "Use viking:// URIs to access context..." }; }, async compact({ sessionId, force }) { // 使用 OpenViking 的三层加载机制 // L0/L1/L2 分层压缩 await vikingCompact(sessionId); return { ok: true, compacted: true }; }, async afterTurn({ sessionId }) { // 触发记忆提取 await vikingExtractMemories(sessionId); } }; ``` ### 4. 压缩 (Compaction) **问题**:对话历史越来越长,最终会超过模型的上下文窗口。 **解决方案**:将旧的对话历史压缩成摘要,释放空间给新消息。 #### 压缩触发条件 ```typescript // 配置路径: agents.defaults.compaction { reserveTokensFloor: 20000, // 预留空间 memoryFlush: { enabled: true, softThresholdTokens: 4000 // 软阈值 } } // 计算 const contextWindow = model.contextWindow; // 如 200,000 tokens (Claude Opus 4.5) const available = contextWindow - reserveTokensFloor; // 180,000 tokens const compactionThreshold = available - memoryFlush.softThresholdTokens; // 176,000 tokens if (currentTokens > compactionThreshold) { // 触发压缩 } ``` #### 压缩策略:摘要法 ```typescript // 伪代码 async function summarizationCompaction(session: Session) { // 1. 分离旧消息和最近消息 const recentCount = 10; // 保留最近 10 条消息 const oldMessages = session.messages.slice(0, -recentCount); const recentMessages = session.messages.slice(-recentCount); // 2. 生成旧消息的摘要 const summary = await generateSummary(oldMessages, { model: "claude-haiku-4", // 使用快速模型 maxTokens: 2000 }); // 3. 创建压缩条目 const compactionEntry = { role: "system", content: `[Previous conversation summary]\n${summary}\n[End of summary]` }; // 4. 更新会话 session.messages = [compactionEntry, ...recentMessages]; session.compacted = true; session.compactedAt = Date.now(); // 5. 持久化 await saveSession(session); } ``` #### 压缩 vs 修剪 | | 压缩 (Compaction) | 修剪 (Pruning) | |---|------------------|----------------| | **目标** | 释放上下文空间 | 减少单次运行的上下文 | | **操作** | 压缩旧消息成摘要,重写会话记录 | 临时从内存中删除旧工具结果,不重写会话 | | **持久性** | 永久修改会话 | 仅影响当前运行 | | **触发时机** | 上下文满时或用户 `/compact` | 每次运行前 | | **影响** | 减少消息数量 | 减少单条消息的大小 | ### 5. 上下文检查和调试 #### `/context` 命令 ```bash # 快速查看上下文使用情况 /context list # 详细的上下文分解 /context detail ``` #### 输出示例 ``` 🧠 Context breakdown Workspace: ~/.openclaw/workspace Bootstrap max/file: 20,000 chars System prompt (run): 38,412 chars (~9,603 tok) (Project Context: 23,901 chars (~5,976 tok)) Injected workspace files: - AGENTS.md: OK | raw 1,742 chars (~436 tok) - SOUL.md: OK | raw 912 chars (~228 tok) - TOOLS.md: TRUNCATED | raw 54,210 chars | injected 20,962 chars (~5,241 tok) - IDENTITY.md: OK | raw 211 chars (~53 tok) - USER.md: OK | raw 388 chars (~97 tok) Skills list: 2,184 chars (~546 tok) (12 skills) Tools: read, edit, write, exec, process, browser, message, ... Tool schemas (JSON): 31,988 chars (~7,997 tok) Session tokens (cached): 14,250 total / ctx=32,000 ``` --- ## 记忆与上下文的协作机制 ### 1. 记忆如何注入到上下文中 #### 方式 1:直接注入(工作区文件) ``` 系统提示构建时 ↓ 读取工作区文件 ├── MEMORY.md ├── memory/2026-03-25.md ├── memory/2026-03-24.md └── 其他 bootstrap 文件 ↓ 截断到 bootstrapMaxChars ↓ 注入到系统提示的 Project Context 部分 ↓ 发送给模型 ``` **优点**: - 简单直接 - 模型可以看到完整的记忆上下文 **缺点**: - 受 token 限制 - 无法处理大量记忆 #### 方式 2:按需检索(记忆工具) ``` 用户消息 ↓ 模型判断需要检索记忆 ↓ 调用 memory_search("如何配置 Discord") ↓ 记忆管理器 ├── 向量化查询 ├── 搜索索引 └── 返回相关片段 ↓ 工具结果注入到上下文 ↓ 模型使用检索到的记忆生成回复 ``` **优点**: - 按需加载,节省 token - 可以处理大量记忆 - 语义搜索,不需要精确匹配 **缺点**: - 需要额外的模型调用(工具调用) - 可能检索不到相关记忆 ### 2. 上下文满时的记忆持久化策略 ``` ┌─────────────────────────────────────────────────────────┐ │ 上下文接近满的处理流程 │ └─────────────────────────────────────────────────────────┘ 检测到接近压缩阈值 ↓ 1. 自动记忆刷新 - 注入提示:"Session nearing compaction. Store durable memories now." - 模型运行一个 turn - 可能调用 `write` 工具写入 MEMORY.md 或 memory/YYYY-MM-DD.md - 模型回复 NO_REPLY(用户看不到) ↓ 2. 上下文压缩 - 选择压缩策略(Legacy 或 Plugin) - 旧消息压缩成摘要 - 保留最近消息 - 重写会话记录 ↓ 3. 继续对话 - 上下文空间释放 - 新消息可以继续添加 ``` **关键点**: - **先刷新,后压缩**:确保重要信息被写入持久记忆 - **静默操作**:用户不会感知到这个过程 - **优雅降级**:如果刷新失败,仍然会进行压缩 ### 3. 跨会话的记忆继承机制 #### 场景 1:新会话开始 ``` 用户开始新会话 ↓ 加载记忆文件 ├── MEMORY.md (长期记忆) ├── memory/今天.md └── memory/昨天.md ↓ 注入到系统提示 ↓ 模型可以"记得"之前会话的内容 ``` **工作原理**: - 记忆存储在**文件系统**,不依赖会话 - 新会话会重新读取记忆文件 - 实现了跨会话的记忆继承 #### 场景 2:长时间后恢复对话 ``` 用户一周后回来 ↓ 加载记忆文件 ├── MEMORY.md (有新内容) ├── memory/今天.md (新的一天) └── memory/昨天.md ↓ 模型看到的是最新的记忆 ``` **关键点**: - 只加载今天和昨天的每日记忆 - 更早的记忆需要: 1. 写入到 `MEMORY.md`(精选长期记忆) 2. 或通过 `memory_search` 检索(如果启用了向量搜索) ### 4. 记忆搜索在上下文中的作用 #### 工作流程 ``` ┌─────────────────────────────────────────────────────────┐ │ 记忆搜索 → 上下文注入 → 模型推理 │ └─────────────────────────────────────────────────────────┘ 用户提问:"我上次配置 Discord 时的 token 是什么?" ↓ 模型判断需要检索记忆 ↓ 生成工具调用 { "tool": "memory_search", "input": { "query": "Discord token configuration", "maxResults": 5 } } ↓ 记忆管理器执行搜索 1. 向量化查询 embedding 2. 搜索向量索引 3. 返回 top-5 相关片段 ↓ 工具结果注入到上下文 [Tool Result: memory_search] 1. path: memory/2026-03-20.md:42 score: 0.89 text: "Discord bot token: ghp_xxxxx..." 2. path: MEMORY.md:156 score: 0.76 text: "Discord integration requires..." ... ↓ 模型基于检索结果生成回复 "根据我的记忆,你的 Discord token 是 ghp_xxxxx..." ``` #### 引用来源 (Citations) ```json5 // 配置: memory.citations { citations: "auto" // "auto" | "on" | "off" } ``` - **`auto`** 或 **`on`**: 片段包含 `Source: <path#line>` footer - **`off`**: 路径保留在元数据中,但不显示给用户 **示例输出(citations: "on")**: ``` Tool Result: memory_search 1. Discord bot token: ghp_xxxxx... Source: memory/2026-03-20.md#42 2. Discord integration requires enabling... Source: MEMORY.md#156 ``` --- ## 技术实现细节 ### 1. 关键代码文件 | 路径 | 职责 | |------|------| | `src/memory/` | 记忆系统核心实现 | | `src/memory/manager.ts` | 记忆管理器(SQLite + sqlite-vec) | | `src/memory/embeddings.ts` | Embedding 生成 | | `src/memory/hybrid.ts` | 混合搜索(BM25 + Vector) | | `src/memory/mmr.ts` | MMR 多样性重排序 | | `src/memory/temporal-decay.ts` | 时间衰减 | | `src/memory/qmd-manager.ts` | QMD 后端管理器 | | `src/context-engine/` | 上下文引擎 | | `src/context-engine/legacy.ts` | Legacy 引擎实现 | | `src/context-engine/registry.ts` | 引擎注册表 | | `src/agents/context.ts` | Agent 上下文构建 | | `src/agents/context-window-guard.ts` | 上下文窗口守卫 | | `src/agents/pi-extensions/context-pruning.ts` | 上下文修剪 | | `extensions/memory-core/` | 默认记忆插件 | | `extensions/memory-lancedb/` | LanceDB 记忆插件 | ### 2. 数据流图 #### 记忆写入流程 ``` 用户消息:"记住我的 Discord token 是 xxx" ↓ 模型生成工具调用 { "tool": "write", "input": { "path": "memory/2026-03-25.md", "content": "\n## Discord Configuration\nToken: xxx\n", "mode": "append" } } ↓ 文件系统 ~/.openclaw/workspace/memory/2026-03-25.md (更新) ↓ 文件监听器检测到变化 ↓ 记忆管理器 1. 读取更新的文件 2. 分块(chunk)文本 3. 生成 embeddings 4. 更新向量索引 ↓ 索引更新完成 ↓ 下次 memory_search 可以检索到这条记忆 ``` #### 记忆检索流程 ``` 用户消息:"我的 Discord token 是什么?" ↓ 模型生成工具调用 { "tool": "memory_search", "input": { "query": "Discord token" } } ↓ 记忆管理器 ├── 生成查询 embedding ├── 向量搜索 (cosine similarity) ├── (可选) BM25 关键词搜索 ├── (可选) 混合搜索融合 ├── (可选) MMR 重排序 ├── (可选) 时间衰减 └── 返回 top-k 结果 ↓ 工具结果 [ { "text": "## Discord Configuration\nToken: xxx", "path": "memory/2026-03-25.md", "line": 42, "score": 0.92 } ] ↓ 注入到上下文 ↓ 模型生成回复 "你的 Discord token 是 xxx" ``` #### 上下文组装流程 ``` 新的用户消息到达 ↓ 会话管理器 ├── 添加用户消息到会话 └── 触发模型运行 ↓ 上下文引擎: ingest() └── 存储/索引消息 ↓ 上下文引擎: assemble() ├── 获取会话消息 ├── 估算 token 数量 ├── 应用修剪策略 ├── 检查 token 预算 └── 返回 messages + estimatedTokens + systemPromptAddition? ↓ 系统提示构建器 ├── 构建工具列表 ├── 构建技能列表 ├── 注入工作区文件 (Project Context) ├── 添加 systemPromptAddition (如果有) └── 返回完整系统提示 ↓ 组装最终上下文 ├── System prompt ├── 对话历史 (从 assemble 返回) └── 工具 JSON Schemas ↓ 发送到 LLM API ↓ LLM 生成回复 ↓ 上下文引擎: afterTurn() └── 后处理(持久化、后台任务等) ``` ### 3. 配置项详解 #### 记忆搜索配置 ```json5 // openclaw.json { agents: { defaults: { memorySearch: { // Embedding 提供商 provider: "openai", // "openai" | "gemini" | "voyage" | "mistral" | "ollama" | "local" // 远程提供商配置 remote: { apiKey: "sk-...", // API Key(也可从环境变量读取) apiBase: "https://...", // 自定义端点 model: "text-embedding-3-large", headers: {} // 自定义 HTTP 头 }, // 本地模型配置 local: { modelPath: "~/.openclaw/models/nomic-embed-v1.5.gguf", contextLength: 2048 }, // 搜索配置 maxResults: 6, // 返回的最大结果数 minScore: 0.7, // 最小相似度分数 // 高级特性 hybridSearch: { enabled: true, alpha: 0.5 // BM25 vs Vector 权重 }, mmr: { enabled: true, lambda: 0.5, // 相关性 vs 多样性 k: 6 }, temporalDecay: { enabled: true, halfLifeDays: 30 // 衰减半衰期 }, // 额外索引路径 extraPaths: [ "../team-docs", "/srv/shared-notes/overview.md" ], // 文件监听 watcher: { enabled: true, debounceMs: 2000 // 防抖延迟 } } } } } ``` #### 上下文压缩配置 ```json5 { agents: { defaults: { compaction: { // 预留 token 空间 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." } }, // 工作区文件注入 bootstrapMaxChars: 20000, // 每个文件的最大字符数 bootstrapTotalMaxChars: 150000, // 所有文件的总最大字符数 bootstrapPromptTruncationWarning: "once" // "off" | "once" | "always" } } } ``` #### 上下文引擎配置 ```json5 { plugins: { slots: { contextEngine: "legacy", // "legacy" | 插件 id memory: "memory-core" // "memory-core" | "memory-lancedb" | "none" }, entries: { // 如果使用插件引擎 "lossless-claw": { enabled: true, // 插件特定配置 } } } } ``` --- ## 与 OpenViking 的对比 ### 1. OpenClaw 原生记忆系统 #### 优点 - ✅ 简单直接:Markdown 文件,人类可读 - ✅ 无需额外依赖:内置 SQLite + sqlite-vec - ✅ 快速启动:无需初始化外部服务 - ✅ 灵活:支持多种 Embedding 提供商 - ✅ 可扩展:插件架构,可替换后端 #### 缺点 - ❌ 记忆组织:扁平的文件结构,缺乏层次 - ❌ 成本控制:每次搜索加载完整片段,token 消耗高 - ❌ 全局视图:难以理解记忆的全局上下文 - ❌ 可观测性:黑盒检索,难以调试 - ❌ 记忆迭代:只是用户交互的记录,缺乏 Agent 任务记忆 ### 2. OpenViking 的优势 回顾 OpenViking 的核心创新(详见 `openviking_learning_prompt.md`): #### 1. 文件系统范式 → 解决碎片化 OpenClaw 的记忆是**扁平的 Markdown 文件**: ``` memory/ ├── 2026-03-24.md ├── 2026-03-25.md └── ... MEMORY.md ``` OpenViking 的记忆是**层级化的虚拟文件系统**: ``` viking:// ├── resources/ # 资源 │ ├── my_project/ │ │ ├── docs/ │ │ │ ├── api/ │ │ │ └── tutorials/ │ │ └── src/ │ └── ... ├── user/ # 用户记忆 │ └── memories/ │ ├── preferences/ │ └── ... └── agent/ # Agent 记忆 ├── skills/ ├── memories/ └── instructions/ ``` **优势**: - 统一管理记忆、资源、技能 - 层级化组织,易于理解和导航 - 精确定位:`viking://user/memories/preferences/writing_style` #### 2. 三层上下文加载 → 降低 Token 消耗 OpenClaw 的记忆检索: - 一次性加载完整片段(平均约 500-2000 tokens/片段) - 无法按需加载部分内容 OpenViking 的三层加载: - **L0 (Abstract)**: 50 tokens,快速判断相关性 - **L1 (Overview)**: 500 tokens,理解结构和要点 - **L2 (Full Content)**: 5000+ tokens,完整内容,按需加载 **成本对比**: - OpenClaw: 平均加载 ~10,000 tokens (假设 top-5 结果) - OpenViking: 平均加载 ~550 tokens (95% 成本降低!) #### 3. 目录递归检索 → 提升检索效果 OpenClaw 的记忆检索: - 单一向量检索(或混合搜索) - 缺乏全局上下文 OpenViking 的目录递归检索: 1. **意图分析**: 生成多个检索条件 2. **初始定位**: 向量检索快速定位高分目录 3. **精细探索**: 在目录内二次检索 4. **递归下钻**: 如果有子目录,递归重复 5. **结果聚合**: 返回最相关的上下文 **优势**: - 不仅找到语义匹配的片段 - 还理解信息所在的全局上下文 - 提高检索的全局性和准确性 #### 4. 可视化检索轨迹 → 可观测性 OpenClaw 的记忆检索: - 黑盒操作 - 难以调试为什么检索到某些结果 OpenViking 的检索轨迹: - 完整保留目录浏览和文件定位的轨迹 - 可以清楚观察问题的根因 - 指导检索逻辑的优化 #### 5. 自动会话管理 → 上下文自我进化 OpenClaw 的记忆管理: - 需要明确调用 `write` 工具写入记忆 - 自动记忆刷新(接近压缩时触发) OpenViking 的自我进化: - 会话结束后自动触发记忆提取 - 分析任务执行结果和用户反馈 - 自动更新: - 用户偏好 → `viking://user/memories/` - Agent 技能 → `viking://agent/skills/` **结果**:Agent "越用越聪明" ### 3. 集成方式 OpenViking 作为 OpenClaw 的上下文插件: ```json5 // openclaw.json { plugins: { slots: { contextEngine: "openviking-context-engine" // 使用 OpenViking 引擎 }, entries: { "openviking-context-engine": { enabled: true, vikingServerUrl: "http://localhost:1933", // OpenViking 服务器 workspace: "~/openviking_workspace" } } } } ``` #### 集成后的数据流 ``` 用户消息 ↓ OpenClaw Gateway ↓ 上下文引擎: OpenViking Plugin ├── ingest() → 存储到 viking:// 文件系统 ├── assemble() → 从 OpenViking 检索相关上下文(L0/L1/L2) ├── compact() → OpenViking 三层压缩 └── afterTurn() → 触发记忆提取和自我进化 ↓ OpenViking 服务器 ├── viking:// 文件系统管理 ├── 三层上下文加载 ├── 目录递归检索 └── 会话记忆提取 ↓ 返回优化后的上下文 ↓ OpenClaw 发送到 LLM ``` ### 4. 性能对比(基于 LoCoMo10 测试集) | 配置 | 任务完成率 | 输入 Token 总量 | |------|-----------|----------------| | OpenClaw (memory-core) | 35.65% | 24,611,530 | | OpenClaw + LanceDB (-memory-core) | 44.55% | 51,574,530 | | **OpenClaw + OpenViking (-memory-core)** | **52.08%** | **4,264,396** | | **OpenClaw + OpenViking (+memory-core)** | **51.23%** | **2,099,622** | #### 性能提升分析 **与原生 OpenClaw 对比(+memory-core)**: - 任务完成率提升:**43%** (35.65% → 51.23%) - Token 成本降低:**91%** (24.6M → 2.1M) **与 LanceDB 对比**: - 任务完成率提升:**15%** (44.55% → 52.08%) - Token 成本降低:**92%** (51.6M → 4.3M) **为什么有如此大的提升?** 1. **三层加载**:只加载必要的上下文层级 2. **目录递归检索**:更精确的上下文定位 3. **自我进化**:Agent 的技能和记忆随使用而改进 4. **文件系统范式**:Agent 可以精确导航和操作上下文 --- ## 最佳实践与调优 ### 1. 记忆系统配置建议 #### 选择合适的 Embedding 提供商 | 场景 | 推荐提供商 | 理由 | |------|----------|------| | 一般使用 | OpenAI `text-embedding-3-large` | 高质量,快速,成本适中 | | 成本敏感 | OpenAI `text-embedding-3-small` | 低成本,质量尚可 | | 隐私敏感 | 本地 GGUF 模型 | 完全本地,无数据泄露 | | 多语言 | Voyage `voyage-multilingual-2` | 多语言支持好 | | 代码搜索 | Voyage `voyage-code-2` | 代码理解好 | | 自托管 | Ollama | 本地控制,无 API 成本 | #### 优化索引性能 ```json5 { memorySearch: { // 定期重建索引(当记忆文件变化很多时) watcher: { enabled: true, debounceMs: 2000 // 避免频繁更新 }, // 限制索引的文件大小 maxFileSize: 1000000, // 1MB // 分块策略 chunkSize: 500, // 每个 chunk 的字符数 chunkOverlap: 50 // 重叠字符数(保持上下文) } } ``` #### 调优搜索质量 ```json5 { memorySearch: { // 混合搜索:结合关键词和语义 hybridSearch: { enabled: true, alpha: 0.7 // 0.7 向量 + 0.3 BM25(调整偏好) }, // MMR 多样性 mmr: { enabled: true, lambda: 0.6 // 0.6 相关性 + 0.4 多样性 }, // 时间衰减:最近的记忆更重要 temporalDecay: { enabled: true, halfLifeDays: 21 // 3 周后权重减半 }, // 过滤低分结果 minScore: 0.65 // 只返回相似度 > 0.65 的结果 } } ``` ### 2. 上下文优化技巧 #### 减少系统提示大小 ```json5 { agents: { defaults: { // 减少注入文件的大小 bootstrapMaxChars: 15000, // 从 20k 降低到 15k // 不注入不必要的文件 bootstrapFiles: [ "AGENTS.md", "IDENTITY.md", "USER.md" // 移除 TOOLS.md, SOUL.md 等(如果不需要) ] } } } ``` #### 主动使用压缩 ```bash # 当对话变长时,手动触发压缩 /compact ``` #### 利用技能的按需加载 - 不要把所有技能的完整指令放在系统提示中 - 让模型在需要时 `read` 技能文件 - 技能列表应该简洁:名称 + 简短描述 + 路径 #### 选择合适的上下文窗口模型 | 模型 | 上下文窗口 | 适用场景 | |------|-----------|---------| | GPT-4o | 128k tokens | 一般对话 | | Claude Opus 4.5 | 200k tokens | 长对话、复杂任务 | | Claude Haiku 4 | 200k tokens | 快速、低成本 | | Gemini 1.5 Pro | 2M tokens | 超长上下文需求 | **权衡**: - 更大的上下文窗口 = 更高的成本 - 选择适合任务的窗口大小 ### 3. 调试记忆和上下文问题 #### 检查上下文使用 ```bash # 查看上下文分解 /context list # 详细信息(包括 top tools/skills) /context detail # 查看 token 使用 /usage tokens ``` #### 检查记忆索引状态 ```bash # 手动触发记忆同步(如果使用 QMD) # 在 QMD 配置的 XDG 目录下运行 export XDG_CONFIG_HOME="~/.openclaw/agents/main/qmd/xdg-config" export XDG_CACHE_HOME="~/.openclaw/agents/main/qmd/xdg-cache" qmd update qmd embed ``` #### 测试记忆检索 在聊天中直接测试: ``` 你: 搜索我之前关于 Discord 的配置 [等待模型调用 memory_search] 你: 显示 memory_search 的结果 [检查返回的片段是否相关] ``` #### 查看会话日志 ```bash # 打开会话日志 cat ~/.openclaw/agents/main/sessions/<sessionId>.jsonl | jq ``` **查找关键信息**: - `role: "tool"` 的消息:工具调用和结果 - `name: "memory_search"`: 记忆搜索调用 - `compacted: true`: 压缩发生的时间点 ### 4. 高级配置:集成 OpenViking #### 步骤 1:安装 OpenViking ```bash # 安装 OpenViking Python 包 pip install openviking --upgrade --force-reinstall # 配置环境 export OPENVIKING_CONFIG_FILE=~/.openviking/ov.conf ``` #### 步骤 2:配置 OpenViking 服务器 ```json5 // ~/.openviking/ov.conf { storage: { workspace: "/home/user/openviking_workspace" }, embedding: { dense: { provider: "openai", api_key: "sk-...", model: "text-embedding-3-large", dimension: 3072 } }, vlm: { provider: "anthropic", api_key: "sk-ant-...", model: "claude-opus-4.5" } } ``` #### 步骤 3:启动 OpenViking 服务器 ```bash openviking-server # 或后台运行 nohup openviking-server > /data/log/openviking.log 2>&1 & ``` #### 步骤 4:安装 OpenClaw 插件 ```bash # 假设有一个 openviking-openclaw-plugin openclaw plugins install openviking-openclaw-plugin ``` #### 步骤 5:配置 OpenClaw 使用 OpenViking ```json5 // ~/.openclaw/openclaw.json { plugins: { slots: { contextEngine: "openviking" }, entries: { "openviking": { enabled: true, serverUrl: "http://localhost:1933", workspace: "/home/user/openviking_workspace" } } } } ``` #### 步骤 6:重启 OpenClaw Gateway ```bash openclaw gateway restart ``` #### 步骤 7:验证集成 ```bash # 检查插件状态 openclaw doctor # 测试记忆检索 # 在聊天中: 你: 搜索 OpenViking 的核心概念 [应该看到从 viking:// 检索的结果] ``` --- ## 总结 ### OpenClaw 的记忆与上下文管理优势 1. **简单直接**:Markdown 文件作为记忆,人类可读 2. **灵活可扩展**:插件架构,支持多种后端 3. **完整的工具链**:记忆搜索、精确读取、自动刷新 4. **可观测性**:`/context` 命令查看上下文使用 5. **跨会话记忆**:文件持久化,新会话可继承 ### 改进空间 1. **记忆组织**:扁平结构 → 需要手动组织 2. **Token 成本**:完整片段加载 → 成本较高 3. **全局视图**:缺乏层次化的记忆浏览 4. **可观测性**:检索过程黑盒 → 难以调试 ### OpenViking 的价值 通过**文件系统范式 + 三层加载 + 目录递归检索 + 自我进化**,OpenViking 可以: - 大幅降低 Token 成本(91-96%) - 显著提升任务完成率(43-49%) - 提供更好的记忆组织和可观测性 - 实现 Agent 的持续自我改进 ### 建议 - **开始阶段**:使用 OpenClaw 内置的 `memory-core` - **高级需求**:切换到 QMD 后端或 LanceDB - **生产环境**:考虑集成 OpenViking,获得最佳性能 --- ## 参考资源 ### 文档 - OpenClaw 文档: https://docs.openclaw.ai/ - OpenViking 文档: https://openviking.ai/docs - Memory 概念: `openclaw/docs/concepts/memory.md` - Context 概念: `openclaw/docs/concepts/context.md` - Context Engine: `openclaw/docs/concepts/context-engine.md` - Memory 配置: `openclaw/docs/reference/memory-config.md` ### 代码 - 记忆系统: `openclaw/src/memory/` - 上下文引擎: `openclaw/src/context-engine/` - 记忆插件: `openclaw/extensions/memory-core/`, `openclaw/extensions/memory-lancedb/` - OpenViking 集成示例: `OpenViking/examples/openclaw-plugin/` ### 社区 - OpenClaw GitHub: https://github.com/openclaw/openclaw - OpenViking GitHub: https://github.com/volcengine/OpenViking - OpenClaw Discord: https://discord.gg/openclaw - OpenViking Discord: https://discord.com/invite/eHvx8E9XF3 --- **文档版本**: v1.0 **最后更新**: 2026-03-25 **作者**: AI Assistant (基于 Claude Sonnet 4.5
Python
赞
博客信息
作者
eeettt
发布日期
2026-03-25
其他信息 : 其他三字母的人名首字母都是其他同学发布的哦