Skip to content

Commit 866b0fd

Browse files
authored
Add files via upload
1 parent feeb4e7 commit 866b0fd

3 files changed

Lines changed: 92 additions & 25 deletions

File tree

ragulate_bio/config.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
variables at runtime.
1111
"""
1212

13+
# PubMed cache backends
14+
PUBMED_SQLITE = None # preferred: path to pubmed_cache.sqlite
15+
16+
1317
import os
1418
from pathlib import Path
1519

@@ -137,11 +141,10 @@
137141
EMB_CACHE_META: str = os.path.join(PUBMED_CACHE, EMB_CACHE_BASENAME + ".meta.json")
138142

139143
#: Base output directory for benchmark artefacts (gold standard,
140-
# Project root: .../RAGulate/
141-
PROJECT_ROOT: Path = Path(__file__).resolve().parents[1]
142144
#: CollectRI pickles, metrics, etc.).
143-
OUTPUT_DIR: Path = PROJECT_ROOT / "outputs"
144-
145+
OUTPUT_DIR: Path = Path("../outputs")
146+
# Project root: .../RAGulate/
147+
PROJECT_ROOT: Path = Path(__file__).resolve().parents[2]
145148

146149
# Data directory: .../RAGulate/data/
147150
DATA_DIR: Path = PROJECT_ROOT / "data"

ragulate_bio/embedding.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
module state (modules.state) so that multiple calls to the
99
retriever share the same vectors and avoid recomputation.
1010
"""
11+
import sqlite3
1112

1213
import os
1314
import json
@@ -51,17 +52,52 @@ def _iter_pubmed_pmids(cache_dir: str) -> Iterable[str]:
5152
if not os.path.isdir(cache_dir):
5253
return []
5354
for fn in os.listdir(cache_dir):
54-
if fn.endswith(".json"):
55-
yield os.path.splitext(fn)[0]
55+
if not fn.endswith(".json"):
56+
continue
57+
stem = os.path.splitext(fn)[0]
58+
if stem.isdigit(): # only PMIDs
59+
yield stem
60+
61+
_PUBMED_SQLITE_CONN = None
62+
63+
def _get_pubmed_sqlite_conn():
64+
global _PUBMED_SQLITE_CONN
65+
db = getattr(config, "PUBMED_SQLITE", None)
66+
if not db:
67+
return None
68+
if _PUBMED_SQLITE_CONN is None:
69+
_PUBMED_SQLITE_CONN = sqlite3.connect(db, check_same_thread=False)
70+
return _PUBMED_SQLITE_CONN
5671

5772

5873
def _load_cached_pubmed_json(pmid: str) -> dict:
59-
"""Load a cached PubMed record from disk."""
74+
"""Load a cached PubMed record (SQLite preferred, JSON fallback)."""
75+
76+
# 1) Preferred backend: SQLite (single file)
77+
con = _get_pubmed_sqlite_conn()
78+
if con is not None:
79+
cur = con.cursor()
80+
cur.execute("SELECT title, abstract FROM pubmed WHERE pmid=?", (str(pmid),))
81+
row = cur.fetchone()
82+
if row is not None:
83+
title, abstract = row
84+
return {"title": title or "", "abstract": abstract or ""}
85+
86+
# 2) Legacy backend: per-PMID JSON files
6087
path = os.path.join(config.PUBMED_CACHE, f"{pmid}.json")
6188
with open(path, "r", encoding="utf-8") as f:
6289
return json.load(f)
6390

6491

92+
93+
'''
94+
def _load_cached_pubmed_json(pmid: str) -> dict:
95+
"""Load a cached PubMed record from disk."""
96+
path = os.path.join(config.PUBMED_CACHE, f"{pmid}.json")
97+
with open(path, "r", encoding="utf-8") as f:
98+
return json.load(f)
99+
'''
100+
65101
def _concat_title_abs(rec: dict) -> str:
66102
"""Concatenate the title and abstract of a PubMed record into one string."""
67103
title = (rec.get("title") or "").strip()

ragulate_bio/retrieval.py

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
MMR re-ranking, while keeping a backwards-compatible API for
1010
the rest of the pipeline.
1111
"""
12-
1312
from typing import List, Optional, Dict, Any, Tuple
1413
import os
1514
import re
@@ -18,11 +17,10 @@
1817

1918
from rank_bm25 import BM25Okapi # real BM25
2019

21-
from . import state
2220
from . import config
21+
from . import embedding
2322
from .embedding import (
2423
get_sentence_embedder,
25-
_ensure_global_corpus,
2624
_load_cached_pubmed_json,
2725
_concat_title_abs,
2826
)
@@ -49,7 +47,6 @@
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+
133135
def _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

Comments
 (0)