LlamaIndex 系列【23】检索增强策略:元数据过滤(Metadata Filtering)

发布时间:2026/11/4 22:35:30

LlamaIndex 系列【23】检索增强策略:元数据过滤(Metadata Filtering) 文章目录1. 前言2. 核心概念2.1 元数据2.2 元数据过滤2.3 元数据过滤 vs 相似度排序3. 案例演示3.1 加载文档3.2 默认元数据3.3 添加元数据3.4 元数据过滤器3.5 混合检索 元数据过滤3.6 执行检索 结果分析3.7 完整代码1. 前言在之前我们学习了核心检索技术Keyword Search关键词检索 /BM25根据关键词出现频次、精确匹配度打分排序。Semantic Search语义检索 / 稠密向量检索根据文本语义含义打分排序匹配方式更加灵活不依赖字面相同。Hybrid Search混合检索组合上面多种检索方式把多路召回的文档合并、重新排序输出一份统一的结果列表。接下来我们继续学习元数据过滤Metadata Filtering。2. 核心概念2.1 元数据元数据是挂在文档或节点上的键值对描述它是什么、来自哪、属于谁等。常见的有标题、作者、创建日期、访问权限等。在LlamaIndex 系列【9】RAG 核心对象Document文档中已经详细介绍过文档元数据。2.2 元数据过滤元数据过滤Metadata Filtering是在检索时先用节点上附加的结构化属性做硬性筛选只保留符合条件的那部分节点然后再在剩下的节点里做向量/BM25打分排序。工作流程元数据过滤本身不执行检索打分它是基于用户属性而非提问文本内容对文档做范围裁剪、过滤筛选是RAG检索体系里非常常用的前置过滤手段最常见是权限和范围控制防止LLM看到不该看的内容租户隔离只检索tenant_id A 公司的文档时间范围只检索publish_date 2024-01-01文档类型只检索doc_type 合同语言、版本、是否已审核等2.3 元数据过滤 vs 相似度排序这是两种完全不同的机制别混淆相似度打分BM25/ 向量给每个节点算一个分按分数软排序谁相关谁靠前是排名。元数据过滤用一个布尔条件直接决定要不要这个节点是筛除和相似度无关。典型流程是先过滤缩小候选集→ 再打分在候选集里排序。比如只检索category 部署的节点那售后政策、价格这些节点即使文本再相似也直接被排除。3. 案例演示目标在混合检索BM25稀疏 向量稠密基础上叠加元数据过滤先按结构化条件硬性筛除不符合的节点再在剩余节点里按相似度排序实现「先过滤、后排序」的召回控制。在权限隔离、时间范围、文档类型过滤等场景这是标准做法。3.1 加载文档这里的Document是文档的原始载体还没切块后面会进一步切成检索单元Node。fromllama_index.coreimportSimpleDirectoryReader documentsSimpleDirectoryReader(./data).load_data()print(f读取文档数:{len(documents)})3.2 默认元数据SimpleDirectoryReader读文件时会自动写入一批默认元数据无需手动指定可直接用来做来源、时间、类型过滤。default_metadocuments[0].metadatafork,vindefault_meta.items():print(f{k}{v})实际输出file_path D:\...\data\公司介绍.md # 文件绝对路径 file_name 公司介绍.md # 文件名 file_type text/markdown # 文件类型 file_size 757 # 文件大小字节 creation_date 2026-08-28 # 创建日期 last_modified_date 2026-08-28 # 最后修改日期注意这些默认字段只有文件层面的信息不含业务语义比如这份文档属于什么类别、来自哪个系统、优先级多少。所以下一步要手动补充。3.3 添加元数据业务上通常需要category类别、source来源、priority优先级等自定义字段。关键做法是在分块之前把元数据写到Document.metadata这样SentenceSplitter切出来的每个Node会自动继承这些元数据无需逐个节点设置。fromllama_index.core.node_parserimportSentenceSplitter category_map{公司介绍.md:公司,售后政策与常见问题.md:售后,星云知识库产品说明.md:产品,}priority_map{公司:1,售后:2,产品:3}fordocindocuments:file_namedoc.metadata.get(file_name,)doc.metadata[category]category_map.get(file_name,其他)doc.metadata[source]官网doc.metadata[priority]priority_map.get(doc.metadata[category],0)nodesSentenceSplitter(chunk_size512).get_nodes_from_documents(documents)分块后每个节点的metadata都带上了category / source / priority这是过滤的基础。3.4 元数据过滤器过滤条件用MetadataFilters表达它由「一组MetadataFilter 一个FilterCondition」组成MetadataFilter(key, value, operator)对某个元数据字段做比较默认FilterCondition.AND / OR / NOT多条条件之间的逻辑关系operator支持EQ()、NE(!)、GT、GTE、LT、LTE、IN、NIN、CONTAINS、TEXT_MATCH、IS_EMPTY等。fromllama_index.core.vector_stores.typesimport(MetadataFilters,MetadataFilter,FilterCondition,FilterOperator,)# 单条件category 产品filter_productMetadataFilters(filters[MetadataFilter(keycategory,value产品)],conditionFilterCondition.AND,)# 数值比较priority 2filter_high_priorityMetadataFilters(filters[MetadataFilter(keypriority,value2,operatorFilterOperator.GTE)],conditionFilterCondition.AND,)# 列表category IN {售后, 产品}filter_in_categoriesMetadataFilters(filters[MetadataFilter(keycategory,value[售后,产品],operatorFilterOperator.IN),],conditionFilterCondition.AND,)3.5 混合检索 元数据过滤BM25和向量两路检索器都通过各自的filters参数接收过滤器先各自过滤、再交给QueryFusionRetriever做结果融合。向量那路用as_retriever(filters...)BM25那路用from_defaults(filters...)。defbuild_fusion_retriever(filtersNone):bm25ChineseBM25Retriever.from_defaults(nodesnodes,similarity_top_k3,filtersfilters)vectorvector_index.as_retriever(similarity_top_k3,filtersfilters)returnQueryFusionRetriever(retrievers[vector,bm25],llmllm,num_queries1,# 不做 LLM 查询扩展仅结果融合modereciprocal_rerank,# RRF 融合similarity_top_k3,use_asyncFalse,)fusion_productbuild_fusion_retriever(filtersfilter_product)实现细节重要新版BM25的filters只把不匹配节点的分数置0通过corpus_weight_mask但仍会返回这些节点和向量那路的真剔除不一致。因此要在BM25检索器的_retrieve里补一句「剔除0分结果」否则融合结果尾部会残留被过滤的节点。def_retrieve(self,query_bundle):query_bundle.query_strzh_seg(query_bundle.query_str)resultssuper()._retrieve(query_bundle)ifself.corpus_weight_maskisnotNone:# 有过滤器时剔除被压成 0 分的节点results[rforrinresultsifr.scoreandr.score0]returnresults3.6 执行检索 结果分析说明用同一个查询词对比「不过滤」和「各种过滤」的召回差异验证过滤是否生效。query星云defshow(title,retriever):print(f---{title}---)fori,iteminenumerate(retriever.retrieve(query),1):catitem.node.metadata.get(category)print(f [{i}] 融合分{item.score:.4f}category{cat})show(不过滤,fusion_no_filter)show(过滤 category产品,fusion_product)show(过滤 priority2,fusion_high_priority)show(过滤 category IN [售后,产品],fusion_in_categories)结果不过滤 → 产品 / 公司 / 售后3 个 过滤 category产品 → 产品1 个 过滤 priority2 → 产品 / 售后2 个 过滤 category IN [售后,产品] → 产品 / 售后2 个结果分析查询词「星云」在三份文档正文里都出现所以不过滤时三份都会命中加category产品后其余两份被硬性剔除只剩1条——过滤生效。priority2时「售后」仍被召回但融合分更低。原因是「售后」这份文档语义上和「星云」相关同属星云科技但正文里未必包含「星云」这个关键词——于是向量那路能命中它而BM25关键词那路命中不了。这正体现了混合检索关键词 语义互补的价值。融合分是RRF分数对每个节点在各路检索器的排名rank上累加1/(rank 60)所以同时被两路命中的节点分数更高、排序更靠前。3.7 完整代码 3. 案例演示元数据过滤 在混合检索BM25 稀疏 向量稠密的基础上叠加「元数据过滤」 先按结构化条件category / source / priority 等硬性筛除不符合条件的节点 再在剩余节点里做相似度打分排序实现先过滤、后排序的召回控制。 流程 3.1 准备数据 —— 读取本地文档并分块 3.2 默认元数据 —— 观察 SimpleDirectoryReader 自动写入的元数据 3.3 添加元数据 —— 给文档/节点补充业务元数据 3.4 元数据过滤器 —— 构造 MetadataFilters 3.5 混合检索 元数据过滤 —— BM25/向量两路都带上 filters 后融合 3.6 执行检索 结果分析 —— 对比不过滤与过滤后的召回差异 importcopyimportosimportjiebafromdotenvimportload_dotenvfromllama_index.coreimportSettings,SimpleDirectoryReader,VectorStoreIndexfromllama_index.core.node_parserimportSentenceSplitterfromllama_index.core.retrieversimportQueryFusionRetrieverfromllama_index.core.vector_stores.typesimport(FilterCondition,FilterOperator,MetadataFilter,MetadataFilters,)fromllama_index.core.vector_stores.utilsimportnode_to_metadata_dictfromllama_index.embeddings.openai_likeimportOpenAILikeEmbeddingfromllama_index.llms.openai_likeimportOpenAILikefromllama_index.retrievers.bm25importBM25Retriever# 通用配置 load_dotenv()api_keyos.environ[DASHSCOPE_API_KEY]API_BASEhttps://ws-jfb8j8mx0n7e2k6a.cn-beijing.maas.aliyuncs.com/compatible-mode/v1embed_modelOpenAILikeEmbedding(model_nametext-embedding-v3,api_keyapi_key,api_baseAPI_BASE,)llmOpenAILike(modelqwen3.8-max,api_keyapi_key,api_baseAPI_BASE,is_chat_modelTrue,)Settings.llmllm# 中文 BM25 检索器 defzh_seg(text:str)-str:return .join(jieba.cut(text))classChineseBM25Retriever(BM25Retriever):支持中文分词的 BM25 检索器同时支持 metadata filters。classmethoddeffrom_defaults(cls,nodes,similarity_top_k2,**kwargs):original_nodesnodes segmented_nodes[copy.deepcopy(n)forninnodes]forninsegmented_nodes:n.set_content(zh_seg(n.get_content()))retrieversuper().from_defaults(nodessegmented_nodes,similarity_top_ksimilarity_top_k,token_patternr(?u)\b\w\b,skip_stemmingTrue,**kwargs,)retriever.corpus[node_to_metadata_dict(n)|{node_id:n.node_id}forninoriginal_nodes]returnretrieverdef_retrieve(self,query_bundle):query_bundle.query_strzh_seg(query_bundle.query_str)resultssuper()._retrieve(query_bundle)# BM25 的 filters 只把不匹配节点的分数置 0weight_mask但仍会返回这些节点# 这里剔除 0 分结果使元数据过滤成为硬性筛除而非压分。ifself.corpus_weight_maskisnotNone:results[rforrinresultsifr.scoreandr.score0]returnresults# 3.1 准备数据 documentsSimpleDirectoryReader(./data).load_data()print(f3.1 读取文档数:{len(documents)})fordocindocuments:print(f -{doc.metadata.get(file_name)}长度{len(doc.text)}字)# 3.2 默认元数据 print(\n3.2 SimpleDirectoryReader 自动写入的默认元数据以第一个文档为例:)default_metadocuments[0].metadatafork,vindefault_meta.items():print(f{k}{v})# 3.3 添加元数据 # 按文件名给每个文档补充业务元数据类别 / 来源 / 优先级category_map{公司介绍.md:公司,售后政策与常见问题.md:售后,星云知识库产品说明.md:产品,}priority_map{公司:1,售后:2,产品:3}fordocindocuments:file_namedoc.metadata.get(file_name,)doc.metadata[category]category_map.get(file_name,其他)doc.metadata[source]官网doc.metadata[priority]priority_map.get(doc.metadata[category],0)# 分块节点会继承所属文档的 metadatasplitterSentenceSplitter(chunk_size512)nodessplitter.get_nodes_from_documents(documents)print(\n3.3 补充元数据并分块后各节点的 category / priority:)fornodeinnodes:print(f category{node.metadata.get(category):4}fpriority{node.metadata.get(priority)}f{node.text[:20]!r})# 3.4 元数据过滤器 # 例 1单条件 —— 只要 category 产品filter_productMetadataFilters(filters[MetadataFilter(keycategory,value产品)],conditionFilterCondition.AND,)# 例 2数值比较 —— priority 2filter_high_priorityMetadataFilters(filters[MetadataFilter(keypriority,value2,operatorFilterOperator.GTE),],conditionFilterCondition.AND,)# 例 3IN 列表 —— category 属于 {售后, 产品}filter_in_categoriesMetadataFilters(filters[MetadataFilter(keycategory,value[售后,产品],operatorFilterOperator.IN),],conditionFilterCondition.AND,)print(\n3.4 已构造 3 组过滤器)print( filter_product : category 产品)print( filter_high_priority : priority 2)print( filter_in_categories : category IN [售后, 产品])# 3.5 混合检索 元数据过滤 # 向量索引只建一次两路检索器各自携带 filters 后交给 QueryFusionRetriever 融合vector_indexVectorStoreIndex(nodesnodes,embed_modelembed_model)defbuild_fusion_retriever(filtersNone):构造BM25 向量的融合检索器可选传入 metadata filters。bm25ChineseBM25Retriever.from_defaults(nodesnodes,similarity_top_k3,filtersfilters)vectorvector_index.as_retriever(similarity_top_k3,filtersfilters)returnQueryFusionRetriever(retrievers[vector,bm25],llmllm,num_queries1,# 不做 LLM 查询扩展modereciprocal_rerank,# RRF 融合similarity_top_k3,use_asyncFalse,)fusion_no_filterbuild_fusion_retriever(filtersNone)fusion_productbuild_fusion_retriever(filtersfilter_product)fusion_high_prioritybuild_fusion_retriever(filtersfilter_high_priority)fusion_in_categoriesbuild_fusion_retriever(filtersfilter_in_categories)# 3.6 执行检索 结果分析 query星云defshow(title,retriever):print(f\n---{title}---)resultsretriever.retrieve(query)ifnotresults:print( 无结果)returnfori,iteminenumerate(results,1):catitem.node.metadata.get(category)priitem.node.metadata.get(priority)print(f [{i}] 融合分{item.score:.4f}category{cat}fpriority{pri}{item.node.text[:24]!r})print(f\n3.6 查询词:{query!r}三份文档正文都含「星云」)show(不过滤应返回全部 3 个节点,fusion_no_filter)show(过滤 category产品应只剩 1 个节点,fusion_product)show(过滤 priority2应剩 售后产品 2 个节点,fusion_high_priority)show(过滤 category IN [售后,产品],fusion_in_categories)
延伸阅读

更多相关文章

2026/10/30 7:51:53

GB 46859 儿童手表跨品牌加好友:BLE 抓包协议逐帧分析

孩子手里的小米手表,怎么和同桌的华为手表加上好友?放在三年前,答案基本是"不能"。小天才封闭社交、各家碰一碰只认自家。直到 2025-12-02,GB 46859-2025《儿童手表安全技术要求》正式发布,2027-01-01 强制实…

2026/10/23 17:12:11

与技术人员以及技术人员对外的沟通指南

与技术人员以及技术人员对外的沟通指南 一、写在前面 技术人员往往性格直白、表达直接,习惯用技术可行性和自身能力边界来判断问题。这不是缺点,而是职业训练带来的思维习惯。 沟通卡在「看起来很简单,对方却觉得做不了」或「技术上对了&…

2026/10/23 15:45:01

Homepage 项目中 Kopia 备份服务 Widget 的配置指南与实现原理

Homepage 项目中 Kopia 备份服务 Widget 的配置指南与实现原理 【免费下载链接】homepage A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations. 项目地址: https://gitcode.com/GitHub_Trending/ho/homepage …

2026/11/5 16:26:13

Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 13:55:19

GD32E10x官方例程入门:从GPIO点灯到串口调试

简介:这份GD32E10x系列官方例程面向嵌入式硬件与单片机开发者,基于ARM Cortex-M3内核,汇集兆易创新GD32E10x的示例代码,适合工业控制、消费电子、通信设备等场景的学习、验证与二次开发。压缩包共935个文件,约11.16MB&…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑