Agent 记忆系统的存储优化:向量检索与全文搜索的混合索引策略

Agent 记忆系统的存储优化:向量检索与全文搜索的混合索引策略
Agent 记忆系统的存储优化向量检索与全文搜索的混合索引策略一、Agent 记忆系统的特殊需求Agent 的记忆系统不是通用的向量数据库。它需要同时满足三种检索模式语义相似上次开会说了什么关于预算的事、精确匹配2025年Q3的营收数字、时间序列最近三天跟进的客户。纯向量检索在精确匹配上天然弱势——Embedding 模型把42和四十二映射到相近向量但对收入 100 万和收入一百万可能产生偏差。混合索引架构的设计目标是语义检索用向量索引覆盖模糊查询全文检索用倒排索引覆盖精确匹配两者通过融合排序合并结果。graph TD A[用户查询] -- B[查询分析器] B -- C{查询类型判断} C --|语义查询| D[向量检索引擎] C --|精确匹配| E[全文检索引擎] C --|混合查询| F[并行双路检索] D -- G[Embedding 模型编码] E -- H[分词 倒排索引] F -- D F -- E G -- I[向量相似度排序 Top-K] H -- J[BM25 评分排序 Top-K] I -- K[融合排序 RRF] J -- K K -- L[返回 Top-N 结果]二、双重索引的存储设计存储层的核心数据结构type MemoryStore struct { // 全文索引倒排索引 invertedIndex *inverted.Index // 向量索引HNSW 图 vectorIndex *hnsw.Index // 原始数据存储 documents map[string]*Document // Embedding 客户端 embedder Embedder } type Document struct { ID string Content string Metadata map[string]string Timestamp time.Time Embedding []float32 Tokens []string // 预分词结果用于全文索引 }写入路径的优化关键向量编码和分词并行执行。func (s *MemoryStore) Insert(doc *Document) error { var ( wg sync.WaitGroup embErr error tokErr error ) // 并行执行 Embedding 编码和分词 wg.Add(2) go func() { defer wg.Done() doc.Embedding, embErr s.embedder.Encode(doc.Content) }() go func() { defer wg.Done() doc.Tokens, tokErr s.tokenizer.Tokenize(doc.Content) }() wg.Wait() if embErr ! nil { return fmt.Errorf(embedding failed: %w, embErr) } if tokErr ! nil { return fmt.Errorf(tokenization failed: %w, tokErr) } s.mu.Lock() defer s.mu.Unlock() s.documents[doc.ID] doc s.vectorIndex.Insert(doc.ID, doc.Embedding) s.invertedIndex.Index(doc.ID, doc.Tokens) return nil }三、混合检索的融合排序混合检索的难点在于两种排序分数的归一化——向量相似度的余弦距离是 [0,1]BM25 分数是无界的。需要先归一化再融合。Reciprocal Rank Fusion (RRF) 是最简单的融合算法RRF_score(doc) sum over all rankers: 1 / (k rank(doc, ranker))其中 k 是平滑参数通常 60rank 是文档在单个排序器中的排名。func (s *MemoryStore) HybridSearch(query string, topK int) ([]*SearchResult, error) { // 并行执行两路检索 var ( vectorResults []*ScoredDoc textResults []*ScoredDoc wg sync.WaitGroup ) wg.Add(2) go func() { defer wg.Done() embedding, _ : s.embedder.Encode(query) vectorResults s.vectorIndex.Search(embedding, topK*2) }() go func() { defer wg.Done() textResults s.invertedIndex.Search(query, topK*2) }() wg.Wait() // RRF 融合 scores : make(map[string]float64) k : 60.0 for rank, doc : range vectorResults { scores[doc.ID] 1.0 / (k float64(rank1)) } for rank, doc : range textResults { scores[doc.ID] 1.0 / (k float64(rank1)) } // 按融合分数排序 var results []*SearchResult for id, score : range scores { doc : s.documents[id] results append(results, SearchResult{ Document: doc, Score: score, }) } sort.Slice(results, func(i, j int) bool { return results[i].Score results[j].Score }) if len(results) topK { results results[:topK] } return results, nil }四、内存管理与存储压缩混合索引的存储开销是单索引的 2-3 倍。向量索引HNSW 图的内存占用与被索引的向量数成正比每个 1536 维的 float32 向量占 6KB。10 万条记忆约 600MB 向量数据 300MB 倒排索引 ≈ 1GB。压缩策略向量量化使用 PQ (Product Quantization) 将 1536 维压缩到 96 维内存减少 16 倍召回率损失 3%倒排索引剪枝对低频词合并存储对超高频词停用词跳过索引分层存储热数据最近 7 天完整索引温数据30 天内仅向量索引冷数据归档仅保留摘要type TieredMemoryStore struct { hot *MemoryStore // 完整双重索引 warm *MemoryStore // 仅向量索引全文搜索降级到扫描 cold *ArchiveStore // 文件归档按需加载 } func (t *TieredMemoryStore) Promote() { now : time.Now() t.mu.Lock() defer t.mu.Unlock() // 每天凌晨执行分层迁移 for id, doc : range t.hot.documents { if now.Sub(doc.Timestamp) 7*24*time.Hour { t.warm.Insert(doc) t.hot.Delete(id) } } for id, doc : range t.warm.documents { if now.Sub(doc.Timestamp) 30*24*time.Hour { t.cold.Archive(doc) t.warm.Delete(id) } } }五、总结Agent 记忆系统的检索需求跨越了语义相似和精确匹配两个维度单一索引无法同时满足。混合索引架构通过向量检索 全文检索双路并行、RRF 融合排序兼顾召回率和准确率。存储优化的关键是向量量化压缩、倒排索引剪枝、按时间分层的三级存储架构。实现复杂度可控核心代码约 500 行适合需要自建 Agent 记忆系统的场景。

最新新闻

日新闻

周新闻

月新闻