11import bm25s
22import json , re
3+ import gzip
4+ import os
35import datasets
4- import Stemmer
6+ import Stemmer
7+ import logging
8+
9+ logger = logging .getLogger (__name__ )
510
611def load_corpus (corpus_path : str ):
712 """Load corpus using datasets library"""
@@ -20,35 +25,72 @@ def load_docs(corpus, doc_idxs):
2025 return results
2126
2227class BM25RetrieverLunce :
23- def __init__ (self , corpus_path : str ):
28+ def __init__ (self , corpus_path_or_corpus , is_corpus = False , cache_dir = None ):
29+ """
30+ Args:
31+ corpus_path_or_corpus: Either a path string or a pre-loaded corpus dataset
32+ is_corpus: If True, corpus_path_or_corpus is a pre-loaded corpus
33+ cache_dir: Directory to save/load BM25 index. If None, uses corpus_path + "_bm25_cache"
34+ """
35+ if is_corpus :
36+ self .corpus = corpus_path_or_corpus
37+ self .cache_dir = cache_dir
38+ else :
39+ logger .info ("BM25: Loading corpus..." )
40+ self .corpus = load_corpus (corpus_path = corpus_path_or_corpus )
41+ # Default cache dir based on corpus path
42+ if cache_dir is None :
43+ self .cache_dir = corpus_path_or_corpus + "_bm25_cache"
44+ else :
45+ self .cache_dir = cache_dir
2446
25- self .retriever = self ._build_index (corpus_path )
26- self .corpus = load_corpus (corpus_path = corpus_path )
27-
28- def _build_index (self , corpus_path ):
29- with open (corpus_path ,"r" ) as file :
30- lines = file .readlines ()
31- self .raw_data = []
32- for line in lines :
33- try :
34- data = json .loads (line )
35- self .raw_data .append (data )
36- except :
37- print (f"error when loading: { data } " )
38- corpus = [re .sub (r'[^\w\s]' , '' , data ["contents" ]) for data in self .raw_data ]
3947 self .stemmer = Stemmer .Stemmer ("english" )
40- retriever = bm25s .BM25 () #corpus=corpus
41- retriever .index (bm25s .tokenize (corpus , stopwords = "en" , stemmer = self .stemmer ))
48+ self .retriever = self ._load_or_build_index ()
49+
50+ def _load_or_build_index (self ):
51+ """Load index from cache if exists, otherwise build and save"""
52+ if self .cache_dir and os .path .exists (self .cache_dir ):
53+ logger .info (f"BM25: Loading cached index from { self .cache_dir } ..." )
54+ retriever = bm25s .BM25 .load (self .cache_dir , load_corpus = False )
55+ logger .info ("BM25: Cached index loaded successfully!" )
56+ return retriever
57+ else :
58+ logger .info (f"BM25: No cached index found, building new index..." )
59+ retriever = self ._build_index ()
60+
61+ # Save to cache
62+ if self .cache_dir :
63+ logger .info (f"BM25: Saving index to { self .cache_dir } ..." )
64+ os .makedirs (self .cache_dir , exist_ok = True )
65+ retriever .save (self .cache_dir )
66+ logger .info ("BM25: Index saved to cache!" )
67+
68+ return retriever
69+
70+ def _build_index (self ):
71+ logger .info (f"BM25: Building index for { len (self .corpus )} documents..." )
72+
73+ # Extract texts directly from corpus (more efficient)
74+ corpus_texts = [re .sub (r'[^\w\s]' , '' , doc ["contents" ]) for doc in self .corpus ]
75+
76+ logger .info ("BM25: Tokenizing corpus..." )
77+ tokens = bm25s .tokenize (corpus_texts , stopwords = "en" , stemmer = self .stemmer )
78+
79+ logger .info ("BM25: Indexing tokens..." )
80+ retriever = bm25s .BM25 ()
81+ retriever .index (tokens )
82+
83+ logger .info ("BM25: Index built successfully!" )
4284 return retriever
4385
44- def _search (self ,query : str , num : int ):
86+ def _search (self , query : str , num : int ):
4587 results , scores = self .retriever .retrieve (bm25s .tokenize (query , stopwords = "en" , stemmer = self .stemmer ), k = num )
4688 return results [0 ], scores [0 ]
4789
4890
4991
5092if __name__ == "__main__" :
51- bm25_ = BM25Retriever ("/mnt/nas/alex/deep-research/src/rag_setup/data/corpus/corpus.jsonl" )
93+ bm25_ = BM25RetrieverLunce ("/mnt/nas/alex/deep-research/src/rag_setup/data/corpus/corpus.jsonl" )
5294 print (bm25_ ._search ("Mc Donald" , 5 ))
5395 result = bm25_ ._search (" Donald" , 5 )
5496 print (load_docs (bm25_ .corpus , result [0 ][0 ]))
0 commit comments