一、为什么需要记忆检索?
记忆检索是解决:记忆无限增长的背景下,如何快速找到"相关"记忆?其面临的挑战包括:
- 语义相关性:我们如何能检索到关键词不匹配,但语义相关的记忆;
- 上下文窗口限制:检索到的记忆可能有几十条,但prompt只能容纳几千字了,应该如何处理?
- 时效性问题:用户的偏好可能随时间变化。
- 多样性覆盖:只检索最相关的可能忽略边缘信息,如何平衡相关性和多样性?
二、检索方法对比
| 向量检索 | 计算embedding余弦相似度 | 语义搜索 | 理解语义、准确率高 | 计算开销大、延迟高 |
| 关键词检索 | BM25/TF-IDF关键词匹配 | 精确匹配 | 快速、精确 | 不理解语义、同义词问题 |
| 混合检索 | 向量+关键词组合 | 通用场景 | 平衡两种方法 | 实现复杂、权重调优 |
| 标量检索 | 结构化字段条件过滤 | 结构化数据 | 精确、可组合 | 需要预定义字段 |
| 时间检索 | 按时间排序或过滤 | 时序敏感 | 符合直觉 | 不考虑内容相关性 |
三、检索方法详述
3.1 向量检索
向量检索的核心思想是:将文字转为向量,用距离判断相似度;向量检索的具体方法,是将用户输入的文案,经过Embedding模型,转化为向量(例如:[0.12, -0.34, 0.78, 0.23, -0.56, 0.91, …] )。其核心原理是模型在大量文本上训练,学习语义关系,其中语义相近的文本,向量距离近;语义不同的文本,向量距离远。
3.1.1 主流向量检索算法
| Brute Force | 暴力搜索 | 遍历所有向量:计算所有向量的距离 | 任意 | O(1) | 100% | ⚡ 慢 | 低 | 小数据量、基准对比 |
| LSH | 局部敏感哈希 | 哈希定位+桶内搜索:相似向量映射到同一桶 | 余弦/汉明 | O(N×D) | 85-95% | ⚡⚡⚡ 快 | 中 | 近似搜索、去重 |
| HNSW | 分层导航小世界图 | 贪婪搜索+层级遍历:构建多层图,上层粗定位下层精搜索 | 任意 | O(N×logN) | 95-99% | ⚡⚡⚡⚡⚡ 极快 | 高 | 大规模、高QPS |
| IVF | 倒排索引 | 聚类中心+桶内搜索:聚类后只搜索部分聚类 | 任意 | O(N×I) | 90-97% | ⚡⚡⚡ 快 | 中 | 中等规模、内存受限 |
| PQ | 乘积量化 | 查表+距离估算:向量分段压缩 | 欧氏/内积 | O(N×D×K) | 85-95% | ⚡⚡⚡⚡ 很快 | 低 | 极大数据量、资源受限 |
| ANNOY | Approximate Nearest Neighbors Oh Yeah | 树遍历:随机投影树 | 欧氏/余弦 | O(N×logN) | 90-95% | ⚡⚡⚡ 快 | 中 | 稀疏向量、磁盘友好 |
| SCA | 随机坐标下降 | 迭代优化:坐标轴方向迭代优化 | 欧氏 | O(N×D) | 92-97% | ⚡⚡⚡ 中 | 低 | 高维向量 |
3.1.2 向量检索优化技巧
- 数据规模与索引选择:1)< 100万向量 → 简单HNSW 或 IVF;2)100万-1000万 → IVF + HNSW;3)> 1000万 → 分层聚类 + HNSW;
- 召回率与速度权衡:1)高召回(>95%) + 低速度 → 增大ef, m参数;2)低召回(90%) + 高速度 → 减小ef, 使用粗量化;3)平衡方案 → 中等参数;
- 内存限制:1)内存有限 → 使用量化(PQ、Binary);2)内存充足 → 使用完整向量 + HNSW;
3.2 关键词检索
关键词检索的核心是通过匹配关键词,找到相关文档;但是在关键词检索的关键问题是:1)如何量化相关性?2)如何排序多个匹配结果?3)如何处理同义词和语义相关?
3.2.1 主流关键词检索算法
关键词检索的原理如下:
| Boolean | 精确匹配AND/OR/NOT | 无量化 | 0/1匹配 | ❌ 无 | ❌ 无 |
| TF-IDF | TF×IDF量化相关性 | TF × IDF | 乘积 | ❌ 无 | ❌ 无 |
| VSM | 文档和查询作为向量计算相似度 | cos(θ) = A·B/( | A | B | |
| BM25 | TF饱和+长度惩罚的概率模型 | IDF × (tf·(k1+1))/(tf+k1·(1-b+b·dl/avgdl)) | 概率权重 | ✅ 有 | ✅ 有 |
| LM | 文档生成查询的概率 | P(query|doc) = ∏P(wi|doc) | 概率乘积 | ✅ 有 | ✅ 有 |
关键词检索的具体适用场景如下:
| 精确匹配 | ✅ 最佳 | ❌ 不适合 | ❌ 不适合 | ❌ 不适合 | ❌ 不适合 |
| 短查询 | ⚠️ 一般 | ✅ 适合 | ✅ 最佳 | ✅ 适合 | ⚠️ 一般 |
| 长文档搜索 | ❌ 不适合 | ❌ 不适合 | ✅ 适合 | ✅ 适合 | ⚠️ 一般 |
| 文档聚类 | ❌ 不适合 | ⚠️ 一般 | ⚠️ 一般 | ⚠️ 一般 | ✅ 最佳 |
| 推荐系统 | ❌ 不适合 | ✅ 适合 | ✅ 最佳 | ✅ 适合 | ✅ 适合 |
| 垂直搜索 | ⚠️ 一般 | ✅ 适合 | ✅ 最佳 | ✅ 适合 | ⚠️ 一般 |
| 跨语言检索 | ❌ 不适合 | ⚠️ 一般 | ⚠️ 一般 | ✅ 适合 | ⚠️ 一般 |
3.2.2 分词器介绍
| 空格分词 | 英文 | "I love China" → ["I", "love", "China"] | 简单,但不处理标点 |
| 正则分词 | 通用 | 按标点和空格分 | 简单,处理标点 |
| n-gram | 通用 | "北京" → ["北京", "京旅", "旅游"] | 解决未登录词,但词汇表大 |
| 分词器(Jieba) | 中文 | "北京旅游" → ["北京", "旅游"] | 需要词典,处理未登录词 |
| BERT分词 | 通用 | 子词分词 | 效果好,但慢 |
3.3 标量检索
标量检索是是基于"标量字段"(非向量)的精确匹配检索。
3.3.1 标量检索与向量检索的区别
| 匹配方式 | 精确匹配 | 近似匹配 |
| 查询类型 | =, <, >, IN, LIKE | 余弦相似度, 欧氏距离 |
| 索引结构 | B树, 哈希, 位图 | HNSW, IVF, PQ |
| 时间复杂度 | O(log N) 或 O(1) | O(log N) 近似 |
| 结果确定性 | 确定性 | 非确定性 |
| 适用字段 | 结构化字段 | 文本、图像 |
| 典型场景 | 过滤、分组、排序 | 语义搜索、相似推荐 |
3.3.2 标量检索算法对比表
| B+树索引 | 等值查询、范围查询(>, <, >=, <=, BETWEEN)、前缀查询(LIKE 'xxx%') | O(log n) | O(n) | 数值/日期范围查询、字符串前缀匹配、需要排序输出的场景 | 支持范围查询、支持排序、适合磁盘存储、范围查找性能稳定 | 范围查询边界有时较慢、内存占用较高、适合低选择性字段 |
| 哈希索引 | 等值查询(=、IN) | O(1) | O(n) | 等值精确匹配、低基数字段、需要快速单值查找 | 等值查询极快(O(1))、实现简单、内存占用低 | 不支持范围查询、不支持排序、无法处理前缀查询、哈希冲突需要处理 |
| 位图索引 | 等值查询、集合查询(AND/OR/NOT)、基数较低字段 | O(k) 或 O(1) | O(n × k / w) | 多值字段(标签、状态、枚举)、组合条件查询、OLAP分析 | 多条件组合查询极快(位运算)、压缩率高、适合低基数字段 | 空间随基数线性增长、高基数字段效率低、更新代价高 |
| 倒排索引 | 等值查询、集合查询、多字段查询 | O(k) 平均 | O(n + m) | 多值字段、文档检索、标签查询、元数据过滤 | 擅长多值字段、支持灵活组合、文档定位快 | 索引构建较慢、内存占用较大、不适合范围查询 |
| 跳表索引 | 等值查询、范围查询 | O(log n) | O(n) | 有序数据、Redis Sorted Set、优先级队列 | 实现简单、适合内存存储、支持范围查找、并发友好 | 空间开销比哈希大(每节点多个指针)、不如B+树磁盘友好 |
| LSM树索引 | 等值查询、范围查询(写入优化) | 写O(1)、读O(log n) | O(n) | 写入密集型场景、时序数据、日志系统 | 写入性能优异、适合批量写入、空间放大可控 | 读取路径可能需要访问多层、读放大问题、合并操作消耗资源 |
3.3.3 标量检索常见的实现方案
| 数据库内置索引 | B+树(MySQL/PostgreSQL)、B树(RocksDB) | 等值、范围、前缀 | 读性能优秀 | 亿级记录以下 | 低 | MySQL InnoDB、PostgreSQL、Oracle |
| 键值存储索引 | 哈希表 + 跳表 | 等值为主 | 等值O(1) | 百万-千万级 | 中 | Redis、Memcached、DynamoDB |
| 搜索引擎索引 | 倒排索引 + FST | 等值、文本、集合 | 多字段查询优秀 | 十亿级文档 | 中 | Elasticsearch、Solr、Meilisearch |
| 列式存储索引 | 位图索引 + 字典编码 | 等值、集合、聚合 | OLAP查询极快 | PB级分析 | 高 | Apache Druid、ClickHouse、Apache Pinot |
| 自定义内存索引 | 红黑树/B树/跳表 | 视具体实现 | 内存访问快 | 百万级以下 | 中 | 内存数据库、缓存层、实时系统 |
| 混合存储索引 | 向量+标量联合索引 | 所有标量类型 | 综合性能最优 | 根据配置 | 高 | Milvus、Pinecone、Weaviate |
具体选择建议如下:
- 等值查询优先选哈希,因为复杂度固定为O(1)
- 范围查询必须选B+树,其他索引类型难以替代
- 多值字段选位图或倒排,取决于基数高低
- 组合查询场景推荐混合使用多种索引或使用专门的搜索引擎
3.4 时间检索
时间检索是基于时间维度进行的数据检索和过滤操作,是标量检索在时间/日期字段上的特化应用。其核心特征是通过时间戳、时间范围、时间窗口等条件来定位和过滤数据。
3.4.1 时间检索类型对比
| 绝对时间检索 | 精确时间点或明确范围 | 固定起止时间戳 | 毫秒级精度 | 静态,范围固定 | 审计日志、交易记录、定时事件查询 |
| 相对时间检索 | 基于参照时间的偏移 | "过去7天"、"上个月"、"本周" | 与当前时间动态计算 | 动态,每次查询重新计算 | 实时监控仪表盘、最近对话检索、趋势分析 |
| 时间序列窗口检索 | 固定或滑动时间窗口 | 窗口大小 + 步长(可选) | 窗口边界对齐 | 半动态,窗口固定但起点滚动 | 指标统计、异常检测、聚合分析 |
| 时间拓扑检索 | 基于时间关系判断 | 关系表达式(包含、相交、前后) | 关系逻辑判断 | 结构化表达 | 事件因果分析、日程冲突检测、会话分割 |
3.4.2 时间检索性能优化
第一,合理选择时间分区粒度。分区粒度太粗会导致查询范围过大,降低效率;分区粒度太细会导致大量小文件,增加管理开销。一般建议按天分区,数据量大的场景可以按小时分区。
第二,使用时间索引跳过无关数据。在查询时,通过时间索引直接定位到相关时间范围,避免全表扫描。这是时间检索性能优化的核心原则。
第三,考虑冷热数据分离。将历史数据和实时数据分离存储,使用不同的存储介质和检索策略。热数据使用内存或SSD,冷数据使用普通磁盘或对象存储。
第四,利用预聚合减少计算量。对于需要频繁查询聚合指标的场景(如每分钟事件数、每小时活跃用户数),提前计算并缓存聚合结果。
第五,组合使用时间过滤和其他检索。时间检索适合作为前置过滤器,缩小候选集后再进行向量检索或全文检索,提高整体检索效率。
