1. OpenClaw记忆系统架构解析
OpenClaw作为一款开源的AI Agent网关平台,其核心创新点在于构建了一套完整的记忆系统。这个系统不是简单的聊天记录存储,而是一个融合了多种检索算法和时间维度的智能记忆引擎。让我们先来看看它的整体架构设计。
1.1 三层架构设计
记忆系统采用典型的三层架构,位于src/memory/目录下,由约70个TypeScript源文件组成:
-
基础层(Storage Layer)
- 负责数据的物理存储和基础CRUD操作
- 使用SQLite作为存储引擎,包含embedding_cache等核心表
- 实现文件监控、增量同步等基础功能
-
中间层(Embedding Layer)
- 文本向量化处理核心
- 支持多种嵌入模型提供商(OpenAI、Gemini等)
- 实现向量生成、缓存和更新机制
-
应用层(Retrieval Layer)
- 提供面向业务的记忆检索接口
- 整合BM25、向量搜索等多种算法
- 实现时间衰减、MMR重排等业务逻辑
这种分层设计使得系统各模块职责清晰,便于扩展和维护。比如要新增一个嵌入模型提供商,只需要在中间层添加对应的适配器即可,不会影响其他层的逻辑。
1.2 数据流设计
记忆系统的数据流动遵循明确的管道模式:
code复制[数据源] → [文本分块] → [向量生成] → [索引构建] → [混合检索] → [结果处理]
- 数据源:支持Markdown记忆文件和JSONL格式的会话日志
- 文本分块:按语义段落切分,保持上下文完整性
- 向量生成:调用嵌入模型API或本地模型
- 索引构建:同时构建FTS5全文索引和向量索引
- 混合检索:并行执行多种检索算法
- 结果处理:应用时间衰减、去重等后处理
这种管道设计确保了数据处理流程的清晰性和可扩展性。每个阶段都可以独立优化或替换,比如改进分块策略或更换嵌入模型,而不会影响整体流程。
提示:在实际部署时,建议对大于1MB的Markdown文件采用更细粒度的分块策略(如每300字符分块),可以显著提升检索精度。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 混合搜索机制详解
OpenClaw记忆系统的核心创新在于其混合搜索机制,它巧妙地结合了传统全文检索和现代向量搜索的优势。
2.1 BM25全文检索实现
系统使用SQLite的FTS5扩展实现BM25算法,这是一种经典的全文检索算法。其核心公式为:
code复制score(D,Q) = ∑(idf(t) × tf(t,D) × (k1 + 1)) / (tf(t,D) + k1 × (1 - b + b × |D|/avgdl))
其中:
idf(t)是逆文档频率tf(t,D)是词项频率k1和b是可调参数|D|是文档长度avgdl是平均文档长度
在OpenClaw中的具体实现:
typescript复制// 创建FTS5虚拟表
db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS fts_memory USING fts5(
content,
metadata,
tokenize='porter unicode61'
)`);
// 执行BM25查询
const results = db.prepare(`
SELECT rowid, bm25(fts_memory) as score
FROM fts_memory
WHERE fts_memory MATCH ?
ORDER BY score DESC
LIMIT ?
`).all(query, limit);
2.2 向量语义检索实现
向量检索使用余弦相似度计算查询向量与文档向量的相似度:
code复制similarity = (A·B) / (||A|| × ||B||)
OpenClaw支持多种嵌入模型,以OpenAI为例:
typescript复制async function getEmbedding(text: string): Promise<number[]> {
const response = await openai.embeddings.create({
input: text,
model: "text-embedding-3-small"
});
return response.data[0].embedding;
}
// 计算余弦相似度
function cosineSimilarity(a: number[], b: number[]): number {
const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
const normA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
const normB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
return dotProduct / (normA * normB);
}
