99MMR re-ranking, while keeping a backwards-compatible API for
1010the rest of the pipeline.
1111"""
12-
1312from typing import List , Optional , Dict , Any , Tuple
1413import os
1514import re
1817
1918from rank_bm25 import BM25Okapi # real BM25
2019
21- from . import state
2220from . import config
21+ from . import embedding
2322from .embedding import (
2423 get_sentence_embedder ,
25- _ensure_global_corpus ,
2624 _load_cached_pubmed_json ,
2725 _concat_title_abs ,
2826)
4947_BM25_PMIDS : List [str ] = []
5048_BM25_TOKENS : List [List [str ]] = []
5149
52-
5350# ---------------------------------------------------------------------
5451# Canonical encoder naming
5552# ---------------------------------------------------------------------
@@ -107,21 +104,25 @@ def _ensure_minilm_corpus(verbose: bool = True) -> None:
107104 """
108105 Ensure that the default MiniLM corpus is built and registered
109106 under encoder_name="minilm".
107+
108+ IMPORTANT:
109+ We treat modules.embedding as the single source of truth for the
110+ global corpus (PMIDs + vectors). Retrieval then registers that
111+ corpus locally under _ENCODER_CORPORA["minilm"].
110112 """
111113 encoder_name = _default_minilm_name ()
112114 if encoder_name in _ENCODER_CORPORA :
113115 return
114116
115- # Build the original global corpus and populate
116- # state.GLOBAL_PMIDS / GLOBAL_VECS / GLOBAL_EMB
117- _ensure_global_corpus (config .EMBED_MODEL_NAME , verbose = verbose )
117+ # Build/load global corpus via embedding module (authoritative)
118+ embedding ._ensure_global_corpus (embedder_name = config .EMBED_MODEL_NAME , verbose = verbose )
118119
119- pmids = list (state .GLOBAL_PMIDS )
120- vecs = state .GLOBAL_VECS
121- emb_model = state .GLOBAL_EMB
120+ pmids = list (embedding . state .GLOBAL_PMIDS )
121+ vecs = embedding . state .GLOBAL_VECS
122+ emb_model = embedding . state .GLOBAL_EMB
122123
123- if vecs is None or emb_model is None :
124- raise RuntimeError ("MiniLM global corpus was not initialised correctly" )
124+ if vecs is None or emb_model is None or len ( pmids ) == 0 :
125+ raise RuntimeError ("MiniLM global corpus was not initialised correctly (0 docs) " )
125126
126127 _ENCODER_CORPORA [encoder_name ] = {
127128 "pmids" : pmids ,
@@ -130,6 +131,7 @@ def _ensure_minilm_corpus(verbose: bool = True) -> None:
130131 _ENCODER_MODELS [encoder_name ] = emb_model
131132
132133
134+
133135def _biobert_cache_paths () -> Tuple [str , str ]:
134136 """
135137 Derive file paths for cached BioBERT corpus embeddings.
@@ -201,7 +203,8 @@ def _ensure_biobert_corpus(verbose: bool = True) -> None:
201203 emb_model = get_sentence_embedder (model_name )
202204 # Update the global sentence embedding model so that RAG context
203205 # construction uses the correct encoder when BioBERT is requested.
204- state .GLOBAL_EMB = emb_model
206+ embedding .state .GLOBAL_EMB = emb_model
207+
205208 _ENCODER_CORPORA [encoder_name ] = {"pmids" : pmids , "vecs" : vecs }
206209 _ENCODER_MODELS [encoder_name ] = emb_model
207210 return
@@ -223,7 +226,8 @@ def _ensure_biobert_corpus(verbose: bool = True) -> None:
223226 print (f"[biobert-corpus] initialising BioBERT encoder: { model_name } " )
224227 emb_model = get_sentence_embedder (model_name )
225228 # Update the global embedding model so sentence selection aligns with BioBERT
226- state .GLOBAL_EMB = emb_model
229+ embedding .state .GLOBAL_EMB = emb_model
230+
227231
228232 texts : List [str ] = []
229233 for pmid in pmids :
@@ -431,9 +435,19 @@ def ensure_pubmed_retriever(
431435 _ENCODER_CORPORA .clear ()
432436 _ENCODER_MODELS .clear ()
433437 _RETRIEVER_CACHE .clear ()
434- state .GLOBAL_PMIDS = []
435- state .GLOBAL_VECS = None
436- state .GLOBAL_EMB = None
438+ embedding .state .GLOBAL_PMIDS [:] = []
439+ embedding .state .GLOBAL_TEXTS [:] = []
440+ embedding .state .GLOBAL_VECS = None
441+ embedding .state .GLOBAL_EMB = None
442+ embedding .state ._SINGLETONS ["global_corpus" ]["built" ] = False
443+ embedding .state ._SINGLETONS ["global_corpus" ]["num_docs" ] = 0
444+ embedding .state ._SINGLETONS ["global_corpus" ]["dim" ] = 0
445+ # reset BM25 caches (otherwise BM25 can stay stale / empty)
446+ global _BM25_INDEX , _BM25_PMIDS , _BM25_TOKENS
447+ _BM25_INDEX = None
448+ _BM25_PMIDS = []
449+ _BM25_TOKENS = []
450+
437451
438452 # Build corpus for this encoder if needed
439453 _ensure_encoder_corpus (enc , verbose = verbose )
@@ -498,6 +512,19 @@ def _ensure_bm25_index(verbose: bool = True) -> None:
498512 # Reuse MiniLM corpus PMIDs / cache
499513 _ensure_minilm_corpus (verbose = verbose )
500514 pmids = _ENCODER_CORPORA [_default_minilm_name ()]["pmids" ]
515+
516+ if verbose and config .VERBOSE >= 3 :
517+ print ("[bm25] encoder corpus pmids:" , len (pmids ))
518+ print ("[bm25] embedding.state pmids:" , len (embedding .state .GLOBAL_PMIDS ))
519+
520+
521+ if not pmids :
522+ _BM25_PMIDS = []
523+ _BM25_TOKENS = []
524+ _BM25_INDEX = None
525+ if verbose and config .VERBOSE >= 1 :
526+ print ("[bm25] no documents available; BM25 index not built" )
527+ return
501528
502529 if verbose and config .VERBOSE >= 1 :
503530 print (f"[bm25] building BM25 index over { len (pmids )} documents" )
@@ -528,7 +555,8 @@ def retrieve_bm25(
528555 """
529556 _ensure_bm25_index (verbose = False )
530557
531- assert _BM25_INDEX is not None
558+ if _BM25_INDEX is None :
559+ return []
532560 query_tokens = _tokenize (query )
533561 scores = _BM25_INDEX .get_scores (query_tokens )
534562
0 commit comments