OpenClaw 记忆系统原理讲解

  |   0 评论   |   0 浏览

让我用大学老师的方式,带你深入理解 OpenClaw 记忆系统的完整实现逻辑!🎓


📚 OpenClaw 记忆系统原理讲解

一、整体架构(宏观视角)

┌─────────────────────────────────────────────────────────────┐
│                    用户提问(关于历史/决策/上下文)            │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│  memory_search 工具(语义搜索入口)                           │
│  - 输入:query, maxResults, minScore                        │
│  - 输出:带评分的 snippets 列表                               │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│              MemoryManager(记忆管理器)                       │
│  ┌─────────────────────┐    ┌─────────────────────────────┐ │
│  │   向量搜索 (Vector)  │    │   全文搜索 (FTS)            │ │
│  │   LanceDB + Embedding│    │   SQLite FTS5               │ │
│  └──────────┬──────────┘    └──────────────┬──────────────┘ │
│             └──────────────┬───────────────┘                │
│                            │                                 │
│                    ┌───────▼────────┐                        │
│                    │  Hybrid 混合排序 │                        │
│                    └───────┬────────┘                        │
└────────────────────────────┼─────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│  memory_get 工具(精确读取)                                   │
│  - 输入:path, from, lines                                  │
│  - 输出:指定文件的指定行内容                                 │
└─────────────────────────────────────────────────────────────┘

二、核心算法详解

2.1 写入流程(Write Path)

当你调用 memory_searchmemory_get 时,底层发生了什么?

// 伪代码:记忆搜索的核心逻辑
async search(query: string, opts: { maxResults, minScore }) {
    // 步骤 1: 查询预处理
    const cleaned = query.trim();
    if (!cleaned) return [];
  
    // 步骤 2: 混合搜索配置
    const hybrid = this.settings.query.hybrid;
    const candidates = Math.min(200, Math.max(1, 
        Math.floor(maxResults * hybrid.candidateMultiplier)
    ));
  
    // 步骤 3: 向量搜索(语义相似度)
    const queryVec = await this.embedQueryWithTimeout(cleaned);
    const vectorResults = await this.searchVector(queryVec, candidates);
  
    // 步骤 4: 全文搜索(关键词匹配)
    const keywordResults = hybrid.enabled ? 
        await this.searchKeyword(cleaned, candidates) : [];
  
    // 步骤 5: 混合排序(Hybrid Ranking)
    const merged = this.hybridRank(vectorResults, keywordResults);
  
    // 步骤 6: 过滤和截断
    return merged
        .filter(entry => entry.score >= minScore)
        .slice(0, maxResults);
}

关键公式

最终得分 = α × 向量相似度 + (1-α) × 文本匹配度

2.2 向量搜索(Vector Search)

async searchVector(queryVec: number[], limit: number) {
    // 使用 LanceDB 进行近似最近邻搜索 (ANN)
    return (await this.table
        .vectorSearch(queryVec)  // 向量相似度搜索
        .limit(limit)            // 限制返回数量
        .toArray()               // 转换为数组
    ).map(row => {
        // 距离转相似度分数
        const score = 1 / (1 + (row._distance ?? 0));
        return {
            entry: {
                id: row.id,
                text: row.text,
                importance: row.importance,
                category: row.category
            },
            score  // 相似度分数 [0, 1]
        };
    });
}

数学原理

余弦相似度:cos(θ) = (A·B) / (||A|| × ||B||)
欧氏距离转相似度:score = 1 / (1 + distance)

2.3 全文搜索(Full-Text Search)

async searchKeyword(term: string, candidates: number) {
    // 使用 SQLite FTS5 进行全文检索
    const keywords = extractKeywords(term);
    const searchTerms = keywords.length > 0 ? keywords : [term];
  
    // 多关键词搜索
    const resultSets = await Promise.all(
        searchTerms.map(t => this.fts.search(t, candidates))
    );
  
    // 合并结果(取最高分)
    const seenIds = new Map();
    for (const results of resultSets) {
        for (const result of results) {
            const existing = seenIds.get(result.id);
            if (!existing || result.score > existing.score) {
                seenIds.set(result.id, result);
            }
        }
    }
  
    return [...seenIds.values()]
        .sort((a, b) => b.score - a.score);
}

2.4 混合排序(Hybrid Ranking)

这是最核心的算法!

hybridRank(vectorResults, keywordResults) {
    const seen = new Map();
  
    // 1. 合并两个结果集
    for (const r of [...vectorResults, ...keywordResults]) {
        const existing = seen.get(r.id);
        if (!existing) {
            seen.set(r.id, r);
        } else {
            // 2. 分数融合(Reciprocal Rank Fusion 变体)
            const vectorWeight = 0.7;  // 向量搜索权重
            const keywordWeight = 0.3; // 关键词权重
            r.score = existing.score * vectorWeight + r.score * keywordWeight;
        }
    }
  
    // 3. 按综合分数排序
    return [...seen.values()]
        .sort((a, b) => b.score - a.score);
}

三、文件读取逻辑(memory_get)

async readFile(relPath: string, from?: number, lines?: number) {
    // 1. 解析文件路径
    const fullPath = path.join(this.workspacePath, relPath);
  
    // 2. 安全检查(防止路径遍历攻击)
    if (!fullPath.startsWith(this.workspacePath)) {
        throw new Error("Invalid path");
    }
  
    // 3. 读取文件
    const content = await fs.readFile(fullPath, 'utf-8');
    const allLines = content.split('\n');
  
    // 4. 按需截取(节省 Token)
    const start = from ? from - 1 : 0;  // 1-indexed 转 0-indexed
    const end = lines ? start + lines : allLines.length;
    const snippet = allLines.slice(start, end).join('\n');
  
    return {
        path: relPath,
        text: snippet,
        totalLines: allLines.length,
        returnedLines: end - start
    };
}

四、实际调用示例

场景:用户问"我记得上周讨论过 Token 认证的事情"

┌─────────────────────────────────────────────────────────┐
│ Step 1: 用户提问                                         │
│ "我记得上周讨论过 Token 认证的事情"                         │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│ Step 2: 调用 memory_search                               │
│ query: "Token 认证 讨论"                                  │
│ maxResults: 5                                           │
│ minScore: 0.5                                           │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│ Step 3: 向量搜索(找到语义相似的内容)                      │
│ - "智图 Token 认证规范" (score: 0.89)                    │
│ - "SecurityUtils 获取 Token" (score: 0.82)              │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│ Step 4: 返回结果给 Agent                                  │
│ [                                                       │
│   {path: "memory/zhitu-token-auth.md", score: 0.89},   │
│   {path: "memory/security-utils-guide.md", score: 0.82}│
│ ]                                                       │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│ Step 5: Agent 调用 memory_get 读取详细内容                  │
│ path: "memory/zhitu-token-auth.md"                      │
│ from: 1, lines: 50                                      │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│ Step 6: 生成最终回答                                      │
│ "根据记忆,Token 认证的获取接口是..."                       │
└─────────────────────────────────────────────────────────┘

五、性能优化技巧

5.1 候选集放大(Candidate Multiplier)

// 如果用户要 5 条结果,实际搜索 50 条候选
const candidates = maxResults * 10;  // 放大 10 倍
// 然后在内存中做精细排序,取前 5 条

5.2 分数截断(Score Cutoff)

// 低于阈值的直接过滤,节省后续处理
results.filter(r => r.score >= minScore)

5.3 增量读取(Chunked Reading)

// 不是一次性读取整个 MEMORY.md
// 而是按需读取指定行
memory_get(path, from=100, lines=50)

六、关键设计决策

设计点选择原因
存储格式Markdown人类可读 + 机器可解析
搜索方式向量 + 全文混合兼顾语义和精确匹配
分数融合加权平均简单有效,可调节
读取策略按需截取节省 Token,提高效率

七、作业(思考题)📝

  1. 如果 MEMORY.md 文件超过 10 万行,如何优化搜索性能?
  2. 为什么向量搜索和全文搜索要做混合,而不是只用一种?
  3. minScore 参数设置过高或过低会有什么影响?

标题:OpenClaw 记忆系统原理讲解
作者:michael
地址:https://blog.junxworks.cn/articles/2026/03/24/1774344560334.html