ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LlamaIndex BM25Retriever 完全指南:基于 bm25s 的稀疏词法检索器实现原理与实战

LlamaIndex BM25Retriever 完全指南:基于 bm25s 的稀疏词法检索器实现原理与实战 LlamaIndex BM25Retriever 完全指南基于 bm25s 的稀疏词法检索器实现原理与实战【免费下载链接】llama_indexLlamaIndex is the leading document agent and OCR platform项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index本文围绕 LlamaIndex 官方 API 参考中 BM25 检索器文档页 所指向的核心类BM25Retriever展开系统讲解该检索器的构造参数、分词与词干处理机制、元数据过滤的权重掩码实现、磁盘持久化协议以及如何将其接入 RAG 检索链路。读完后你可以独立配置一个带词干提取与元数据过滤的 BM25 检索器并将其序列化后在生产环境中加载复用。1. BM25Retriever 的定位RAG 中的词法检索组件BM25Best Matching 25是一种在 TF-IDF 基础上扩展的排名函数它额外考虑了词频饱和term frequency saturation与文档长度归一化能够根据查询词在语料中的出现频率与稀有程度对文档排序。这一描述来自仓库官方示例 bm25_retriever.ipynb 的说明部分也是理解该组件价值的关键稠密向量检索擅长语义匹配但对专有名词、代码标识符、错误码等“字面必须命中”的查询容易失准BM25 词法检索恰好互补它要求查询词在文档中实际出现因此常被与向量检索组合成混合检索hybrid retrieval。在 LlamaIndex 的模块化架构中BM25Retriever并不在llama-index-core中实现而是作为独立集成包发布源码位于 base.py包元数据定义在 pyproject.toml。它与向量检索器如VectorIndexRetriever同样继承自 BaseRetriever因此可以无缝替换进任何接受BaseRetriever的查询引擎包括RouterRetriever、QueryFusionRetriever仓库中有 reciprocal_rerank_fusion.ipynb 与 relative_score_dist_fusion.ipynb 等将 BM25 与向量检索做 RRF/分数融合的典型用法。2. 安装与依赖安装方式为标准 pip 安装pip install llama-index-retrievers-bm25从 pyproject.toml 可以确认当前版本0.7.1的运行前提与三方依赖依赖版本约束作用llama-index-core0.13.1,0.15提供BaseRetriever、QueryBundle、NodeWithScore、MetadataFilters等基础抽象bm25s0.2.7.post1底层 BM25 索引库负责分词、倒排索引构建与检索打分pystemmer2.2.0.1,3提供Stemmer.Stemmer用于对语料和查询做词干提取Python3.10,4.0运行时版本要求值得注意的是BM25Retriever的类名在 core 包的 CLI 组件映射 mappings.json 中注册为BM25Retriever: llama_index.retrievers.bm25即 LlamaIndex 的命令行工具通过该映射解析这个集成包中的实现而非 core 内置实现。3. 语料构建从 Node 到倒排索引BM25Retriever.__init__的核心逻辑base.py#L71-L154分为两条互斥路径路径一直接传入已建好的索引。若提供了existing_bm25一个bm25s.BM25对象则直接复用其索引并从existing_bm25.corpus恢复语料字典列表。这是持久化加载时的走法。路径二从节点现场建索引。若未提供existing_bm25则必须传入nodes否则抛出ValueError(Please pass nodes or an existing BM25 object.)。建索引过程依次做三件事语料序列化对每个节点调用node_to_metadata_dict(node)把节点序列化为可 JSON 化的字典并附加node_id存入self.corpus。这一步保证了检索结果可以无损还原回BaseNode含元数据分词对每个节点的内容使用MetadataMode.EMBED模式即正文文本而非拼接了元数据的字符串调用bm25s.tokenize传入停用词语言stopwordslanguage默认en、词干提取器除非skip_stemmingTrue以及token_pattern建倒排索引self.bm25 bm25s.BM25()后调用self.bm25.index(corpus_tokens)。由此可以看出一个实现细节语料侧的分词发生在构造时一次性完成而查询侧的分词发生在每次_retrieve调用时见第 6 节两侧使用同一套stemmer与token_pattern保证词形对齐。4. 构造参数详解BM25Retriever构造函数签名base.py#L71-L86的完整参数如下参数类型 / 默认值说明nodesList[BaseNode]可选待建索引的节点列表。未提供existing_bm25时为必传stemmerStemmer.Stemmer可选词干提取器默认Stemmer.Stemmer(english)languagestr默认enbm25s去停用词所用的语言代码existing_bm25bm25s.BM25可选已构建好的 BM25 索引对象与nodes二选一similarity_top_kint默认DEFAULT_SIMILARITY_TOP_K即 2定义于 constants.py#L12每次retrieve返回的节点数上限callback_managerCallbackManager可选回调管理器用于事件/追踪体系默认Noneobjects/object_map可选对象检索相关IndexNode列表 / id→node 映射默认Nonetoken_patternstr默认r(?u)\b\w\w\b分词正则只保留长度 ≥2 的单词 token\b\w\w\b会丢弃单字符词skip_stemmingbool默认False为True时语料与查询均不做词干提取filtersMetadataFilters可选元数据过滤条件触发权重掩码机制见第 5 节corpus_weight_maskList[int]可选直接指定语料权重掩码若同时传了filters会被过滤结果覆盖verbosebool默认False是否显示分词/索引进度条几个参数值得额外说明token_pattern默认模式(?u)\b\w\w\b是bm25s生态的惯用模式(?u)表示 unicode 模式\w\w要求至少两个字符。对于包含大量单字母标识符如变量名x的代码语料可以推断放宽该模式会有助于召回但这属于自行调参场景需自行验证。skip_stemming与stemmer的组合skip_stemmingTrue时构造与查询两侧的stemmer参数都会传None给bm25s.tokenizebase.py#L107 与 base.py#L233因此两侧行为严格一致不建议一侧启用、一侧禁用的配置组合当前实现也不支持这种非对称用法。5. 元数据过滤的实现权重掩码而非结果后筛BM25Retriever对MetadataFilters的支持是一个有巧思的设计。源码base.py#L129-L147显示过滤不是在检索完之后“筛掉不命中的节点”而是在检索前把过滤条件编译成一个 0/1 的corpus_weight_mask_query_filter_fn build_metadata_filter_fn( lambda node_id: _corpus_dict[node_id], filters ) self.corpus_weight_mask [ int(_query_filter_fn(corpus_token[node_id])) for corpus_token in self.corpus ]即对语料中每个节点求值过滤条件命中为 1、不命中为 0。检索时该掩码作为weight_mask参数传入self.bm25.retrieve不命中的文档在打分阶段被压为 0 分。这种“软掩码”的好处是返回结果的结构保持不变仍返回k条含 0 分节点对上层融合检索器更友好。此外有一处防御性检查若掩码全为 0所有节点都被过滤掉构造函数直接抛出ValueError(All nodes were filtered out by the metadata filters. ...)避免静默返回空结果。测试用例 test_metadata_filtering 精确验证了这一行为5 本书的语料在author J.K. Rowling过滤下检索不加分词时 5 个节点全部得到正分加过滤后恰好只有 2 个罗琳的两本《哈利·波特》得分大于 0其余 3 个被压为 0 分。6. 检索流程与 top-k 的边界保护_retrieve方法base.py#L229-L260的调用链为取query_bundle.query_str用与建索引相同的stemmer/token_pattern调用bm25s.tokenize得到查询 token调用self.bm25.retrieve(tokenized_query, kself.similarity_top_k, weight_mask...)得到索引与分数逐个把结果索引映射回节点索引可能是 int指向self.corpus中的下标也可能是字典bm25s 直接返回的节点字典统一经metadata_dict_to_node还原为BaseNode最终包装成NodeWithScore(node..., scorefloat(score))列表返回。关于similarity_top_k的边界处理base.py#L114-L126若语料文档数小于similarity_top_k会打印警告并把similarity_top_k强制改写为实际文档数因为bm25s要求k num_docs若语料数为 0直接抛出ValueError(No nodes added to the retriever kindly add more data.)。这一点在测试 test_large_value_of_top_k 中被覆盖对单文档语料传入similarity_top_k20检索返回的节点数等于实际节点数而非 20且不会报错。7. from_defaultsindex / nodes / docstore 三选一工厂方法from_defaultsbase.py#L156-L201约束必须且只能传入index、nodes、docstore三者之一sum(bool(val) for val in [index, nodes, docstore]) ! 1即抛错传indexVectorStoreIndex取其index.docstore作为节点来源传docstore直接list(docstore.docs.values())取全部文档传nodes直接使用。统一解析后都以nodes...调用构造函数。这意味着从 docstore 建 BM25 索引时索引的是 docstore 中的全量节点快照索引本身不随 docstore 后续写入自动更新——需要重建或持久化后重新加载。另外该方法保留了一个已废弃参数tokenizer传入时仅打印logger.warning提示改用 PyStemmer 的stemmer参数并在后续版本移除base.py#L172-L176。8. 磁盘持久化persist 与 from_persist_dirBM25Retriever支持把“BM25 索引 语料 检索器配置”一并落盘便于服务间共享、避免重复建索引bm25_retriever.persist(./bm25_retriever) loaded BM25Retriever.from_persist_dir(./bm25_retriever)结合源码base.py#L203-L227持久化目录中实际有两部分内容bm25s 索引文件self.bm25.save(path, corpusself.corpus)保存倒排索引及语料字典retriever.json常量DEFAULT_PERSIST_FILENAME以 JSON 保存get_persist_args()返回的检索器参数。由DEFAULT_PERSIST_ARGS映射可知落盘的字段为similarity_top_k、verbose、corpus_weight_mask三项。加载时from_persist_dir先bm25s.BM25.load(path, load_corpusTrue)恢复索引与语料再读取retriever.json将其展开为关键字参数构造cls(existing_bm25bm25, **retriever_data)。测试 test_persist_and_load 完整覆盖了“persist → from_persist_dir”的往返过程。需要留意stemmer、language、skip_stemming不在持久化参数中因为它们只影响建索引时的分词而加载时索引已建好若加载后的语料要重新 tokenize当前实现不会才需关心这两者的取值。9. 实战示例以下示例整理自官方示例 bm25_retriever.ipynb展示三种典型构造方式与元数据过滤用法。方式一从节点列表构建并做磁盘持久化import Stemmer from llama_index.core import SimpleDirectoryReader from llama_index.core.node_parser import SentenceSplitter from llama_index.retrievers.bm25 import BM25Retriever documents SimpleDirectoryReader(./data/paul_graham).load_data() splitter SentenceSplitter(chunk_size512) nodes splitter.get_nodes_from_documents(documents) bm25_retriever BM25Retriever.from_defaults( nodesnodes, similarity_top_k2, # 可选词干提取器与停用词语言默认均为英文 stemmerStemmer.Stemmer(english), languageenglish, ) # 持久化与加载 bm25_retriever.persist(./bm25_retriever) loaded_bm25_retriever BM25Retriever.from_persist_dir(./bm25_retriever) retrieved_nodes loaded_bm25_retriever.retrieve(What happened at Viaweb and Interleaf?) for node in retrieved_nodes: print(node.node.text[:200], node.score)方式二基于 docstore 构建支持远程 docstorefrom llama_index.core.storage.docstore import SimpleDocumentStore docstore SimpleDocumentStore() docstore.add_documents(nodes) # 也可以传入 docstore... 或 index...三者互斥 bm25_retriever BM25Retriever.from_defaults( docstoredocstore, similarity_top_k2, stemmerStemmer.Stemmer(english), languageenglish, )由于docstore抽象支持 mongodb、redis、postgres 等实现从源码结构看这条路径允许 BM25 检索器以“共享 docstore 为数据源”的方式部署索引本身仍保存在各进程的持久化目录中。方式三带元数据过滤的检索from llama_index.core import Document from llama_index.core.storage.docstore import SimpleDocumentStore from llama_index.core.vector_stores.types import ( MetadataFilter, MetadataFilters, FilterOperator, ) documents [ Document(textHello, world!, metadata{key: 1}), Document(textHello, world! 2, metadata{key: 2}), Document(textHello, world! 3, metadata{key: 3}), Document(textHello, world! 2.1, metadata{key: 2}), ] splitter SentenceSplitter(chunk_size512) nodes splitter.get_nodes_from_documents(documents) docstore SimpleDocumentStore() docstore.add_documents(nodes) bm25_retriever BM25Retriever.from_defaults( docstoredocstore, similarity_top_k2, stemmerStemmer.Stemmer(english), languageenglish, filtersMetadataFilters( filters[ MetadataFilter(keykey, operatorFilterOperator.EQ, value2) ] ), ) retrieved_nodes bm25_retriever.retrieve(Hello world) for node in retrieved_nodes: # key ! 2 的节点分数会被权重掩码压为 0 print(node.metadata, node.score)10. 测试验证与行为核对集成包自带测试套件 test_retrievers_bm25_retriever.py 覆盖了本文所述的四个关键行为可作为行为核对清单测试验证点test_classBM25Retriever的 MRO 中包含BaseRetriever保证可被查询引擎当普通检索器使用test_scores3 篇文档 similarity_top_k2检索 llamaindex llm返回 2 条且分数均大于 0test_large_value_of_top_ktop_k超过语料大小时自动收敛为语料大小不抛异常test_metadata_filteringMetadataFilters以 0/1 权重掩码方式生效被过滤节点分数为 0test_persist_and_loadpersist与from_persist_dir可成功往返11. 适用边界小结适用场景以关键词/术语命中为核心的检索法规条文、错误码、代码符号、与向量检索做 RRF 或分数融合的混合召回、需要零 embedding 成本与零外部向量服务的轻量部署。语言限制停用词移除依赖language参数与bm25s内置停用词表词干提取依赖pystemmer支持的语言默认面向英文中文语料可推断应设置skip_stemmingTrue并考虑调整token_pattern具体效果需自行验证。索引静态性BM25 索引在构造时一次性建成docstore 后续变更不会自动反映到已建索引数据更新后需重新from_defaults建索引并重新persist。以上全部行为与参数均可在 集成包源码、官方示例 Notebook 与 测试文件 中逐行对照验证。【免费下载链接】llama_indexLlamaIndex is the leading document agent and OCR platform项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表