根据相关元数据限制向量搜索结果是许多LLM应用中的常见需求,例如查找特定日期之后创建的相关文档或特定价格范围的产品。 简单地检索大量向量然后进行过滤(后过滤)通常会浪费计算资源。 预过滤,即在最近邻搜索之前缩小潜在向量候选项的范围,通常更高效,但其效果很大程度上取决于元数据如何与向量本身一起存储和索引。 实现这一目标的有效策略将被讨论。主要难点在于数据存储可能存在分离。 向量索引为高维相似性搜索而优化,可能位于专用引擎(如Faiss、HNSWlib)或专用向量数据库中,而元数据通常存在于传统数据库(SQL、NoSQL)或搜索引擎(如Elasticsearch)中。 跨这些系统进行查询会引入网络延迟和同步复杂性,削弱了低延迟搜索的目标。利用原生向量数据库功能现代向量数据库(例如Milvus、Pinecone、Weaviate、Qdrant)旨在直接解决这种集成难题。 它们通常允许您将元数据(常被称为 payload 或 attributes)直接与每个向量一同存储。 这种共同位置是高效过滤的基础。当元数据原生存储时,向量数据库可以在这些属性上构建二级索引,实现快速过滤。 向量数据库中常见的元数据索引技术包括:倒排索引: 适用于对分类或关键词数据的精确匹配进行过滤(例如,status: "published",tags: ["python", "vector search"])。 该索引将元数据值映射到拥有这些值的向量ID。B-树或类似结构: 对数值或时间戳数据进行范围查询时高效(例如,price > 100.0,timestamp < 1678886400)。 这些有序树结构可以快速识别匹配指定范围的向量。地理空间索引: 用于根据地理位置数据进行过滤的专用索引(如R-树)。通过使用这些二级索引,数据库可以高效地执行预过滤。 当查询同时包含向量嵌入和元数据过滤器时,系统首先使用元数据索引来识别满足过滤条件的向量ID候选集合。 然后,近似最近邻(ANN)搜索只在与这些候选ID对应的向量上执行。 这与搜索整个数据集或执行后过滤相比,大幅减少了所需的距离计算次数。digraph G { rankdir=LR; node [shape=box, style=rounded, fontname="Arial", fontsize=10, color="#495057", fontcolor="#495057"]; edge [fontname="Arial", fontsize=9, color="#868e96", fontcolor="#868e96"]; subgraph cluster_integrated { label = "集成向量数据库"; style=filled; color="#e9ecef"; bgcolor="#f8f9fa"; Query [label="查询\n(向量 + 过滤:\n分类='技术')", shape=cylinder, color="#1c7ed6", fontcolor="#1c7ed6"]; MetadataIndex [label="元数据索引\n(例如,针对“category”的\n倒排索引)", color="#ae3ec9"]; VectorIndex [label="向量索引\n(例如,HNSW)", color="#40c057"]; CandidateIDs [label="候选ID\n(分类='技术')", shape=note, color="#fd7e14"]; FilteredVectors [label="匹配“技术”的向量", shape=cylinder, style=dashed]; Results [label="Top-K结果", shape=cylinder, color="#1c7ed6", fontcolor="#1c7ed6"]; Query -> MetadataIndex [label="1. 过滤"]; MetadataIndex -> CandidateIDs [label="2. 获取ID"]; CandidateIDs -> FilteredVectors [label="3. 识别向量"]; Query -> VectorIndex [label="4. ANN搜索\n(在过滤后的集合上)"]; FilteredVectors -> VectorIndex; VectorIndex -> Results [label="5. 返回"]; } subgraph cluster_separate { label = "分离系统(预过滤效率较低)"; style=filled; color="#e9ecef"; bgcolor="#f8f9fa"; Query2 [label="查询\n(向量 + 过滤:\n分类='技术')", shape=cylinder, color="#1c7ed6", fontcolor="#1c7ed6"]; MetadataDB [label="元数据数据库\n(例如,SQL/NoSQL)", color="#ae3ec9"]; VectorDB [label="向量数据库/索引\n(仅限向量)", color="#40c057"]; CandidateIDs2 [label="候选ID\n(分类='技术')", shape=note, color="#fd7e14"]; Results2 [label="Top-K结果", shape=cylinder, color="#1c7ed6", fontcolor="#1c7ed6"]; Query2 -> MetadataDB [label="1. 过滤元数据"]; MetadataDB -> CandidateIDs2 [label="2. 获取ID"]; CandidateIDs2 -> VectorDB [label="3. 按ID查询向量\n(可能存在延迟)"]; Query2 -> VectorDB [label=" "]; // 隐式查询向量部分 VectorDB -> Results2 [label="4. 返回"]; } }预过滤的查询流程比较。集成系统利用内部元数据索引在ANN搜索之前快速找到候选向量。分离系统则需要额外步骤先查询元数据存储,可能增加延迟。高效索引的模式设计您如何构建元数据会显著影响索引效率和过滤性能。 请考虑以下几点:数据类型: 使用适当的数据类型。 数值字段支持范围查询,而字符串字段适用于关键词或标签匹配。 使用过于宽泛的类型(例如,将数字存储为字符串)可能会阻碍高效索引和查询。基数: 具有非常高基数(许多唯一值,如用户ID或精确时间戳)的字段,如果精确匹配不是主要使用场景,可能不太适合某些索引类型(如简单的倒排索引)。 基于范围的索引或专用结构可能更佳。规范化: 与关系数据库通常追求规范化不同,在向量数据库的背景下,为了性能,通常更推荐将相关的可过滤属性直接反规范化到向量的元数据负载中。 避免连接是很重要的。粒度: 确定合适的详细程度。 索引过于细粒度的元数据可能会增加存储和索引开销,而过滤性能却没有相应的好处。处理分离的向量和元数据存储尽管集成系统通常因性能而受青睐,但有时您可能需要处理向量和元数据分离存储的遗留系统或架构。两阶段查询: 这里最常见的方法包括首先使用过滤条件查询元数据存储(例如,Elasticsearch集群或SQL数据库)以检索相关文档/向量ID列表。 然后,将此ID列表传递给向量搜索引擎,仅在这些特定向量上执行最近邻搜索。 这避免了搜索整个向量空间,但引入了初始元数据查询的延迟和潜在的网络开销。 元数据存储能够非常快速地返回ID是很重要的。后过滤扩增: 如前所述,从向量存储中检索比所需更多的向量(例如,Top $k' = 10 \times k$)。 单独获取这些 $k'$ 候选的元数据。 在您的应用逻辑中根据元数据条件过滤此扩增集合,以得到最终的 Top $k$。 这增加了向量搜索本身的计算开销(查找 $k'$ 个邻居而非 $k$ 个),并且需要获取和处理额外的元数据。两种分离存储方法通常会导致更高的延迟和更低的吞吐量,与具有原生元数据索引的集成系统相比,特别是在需要实时响应的应用中。 两个存储之间的数据同步也成为一个操作上的考量。索引权衡索引元数据并非没有成本。 您需要考虑:存储成本: 元数据索引会在向量和原始元数据之上额外占用磁盘或内存空间。摄入速度: 构建和更新元数据索引会在数据摄入和更新期间增加开销。 写入量大的系统需要平衡索引的完整性和摄入吞吐量。查询速度: 这是您可以获得收益的地方。 精心设计的元数据索引能大幅加快过滤搜索,特别是预过滤。最佳平衡很大程度上取决于您的应用特定需求:读/写比例、查询复杂度、延迟目标以及元数据过滤器的性质。总之,高效地将元数据与向量一同索引,不仅仅是为了方便,更是实现高性能过滤向量搜索的必要条件。 利用现代向量数据库的功能,将向量和元数据存储与适当的二级索引策略结合,通常是在要求严苛的LLM应用中,减少延迟并最大化预过滤操作效率的最有效方法。 在设计您的系统时,请仔细考虑您的模式以及存储、摄入速度和查询性能之间的权衡,以选择合适的索引策略。