BM25 Pipeline Code Explorer

AI Letters #19 — LLM Showdown #10 · Select a framework to see the full pipeline
SynapseKit
LangChain
LlamaIndex
Side-by-Side
SynapseKit — 8 lines (2 imports + 6 functional)
BM25 is hybrid-first. There is no standalone keyword retriever — you must initialise an embedding model and vector store even with bm25_weight=1.0. Zero extra installs, but 5 extra lines compared to LangChain.
SynapseKit — HybridSearchRetriever (bm25_weight=1.0)
8 lines · 0 extra installs
from synapsekit.retrieval import HybridSearchRetriever, Retriever, InMemoryVectorStore
from synapsekit.embeddings import SynapsekitEmbeddings

# Embedding model required even for pure BM25 (hybrid-first design)
emb    = SynapsekitEmbeddings(model="all-MiniLM-L6-v2", use_gpu=False)
r      = Retriever(InMemoryVectorStore(emb))
hybrid = HybridSearchRetriever(r, bm25_weight=1.0, vector_weight=0.0)
hybrid.add_documents(DOCS)
await r.add(DOCS)
results = await hybrid.retrieve(QUERY, top_k=3)
Trade-offs
rank-bm25 installs automatically — no hidden dependencies
✓ BM25 weight configurable 0.0–1.0 for hybrid blending
✗ Cannot do pure BM25 without instantiating an embedding model
async API requires await — extra friction in sync contexts
Silent runtime failure: BM25Retriever is in langchain-community but requires rank-bm25 installed separately. The import succeeds. BM25Retriever.from_texts() raises ModuleNotFoundError: No module named 'rank_bm25' at runtime if missing. Add rank-bm25 to your requirements file explicitly.
LangChain — 3 lines (1 import + 2 functional)
The cleanest BM25 API. from_texts() takes a list of strings directly. invoke() returns Document objects with .page_content. Three lines. But requires pip install rank-bm25 separately.
LangChain — BM25Retriever
3 lines · 1 extra install
# Requires: pip install rank-bm25  (not auto-installed!)
from langchain_community.retrievers import BM25Retriever

r       = BM25Retriever.from_texts(DOCS, k=3)
results = [doc.page_content for doc in r.invoke(QUERY)]
What happens if rank-bm25 is missing
from langchain_community.retrievers import BM25Retriever
# ✓ Import succeeds — class is defined in langchain-community

r = BM25Retriever.from_texts(DOCS, k=3)
# ✗ RuntimeError: ModuleNotFoundError: No module named 'rank_bm25'
# Fails here, not at import. Passes linting, fails in production.
LlamaIndex — 9 lines (3 imports + 6 functional)
Requires llama-index-retrievers-bm25 as a separate package. Fails at import time (honest). Needs Settings.llm = None to suppress LLM/embedding warnings. Documents must be wrapped in Document objects and parsed into nodes before BM25 can index them.
LlamaIndex — BM25Retriever
9 lines · 1 extra install
# Requires: pip install llama-index-retrievers-bm25
# Fails at import if missing (honest failure mode)
from llama_index.retrievers.bm25 import BM25Retriever
from llama_index.core import Document, Settings
from llama_index.core.node_parser import SentenceSplitter

# Suppress MockLLM / MockEmbedding warnings
Settings.llm         = None
Settings.embed_model = None

nodes   = SentenceSplitter(chunk_size=512).get_nodes_from_documents(
              [Document(text=d) for d in DOCS])
r       = BM25Retriever.from_defaults(nodes=nodes, similarity_top_k=3)
results = [n.text for n in r.retrieve(QUERY)]
Dimension SynapseKit LangChain LlamaIndex
Extra pip installs 0 1 (rank-bm25) 1 (llama-index-retrievers-bm25)
Total lines 8 3 9
Failure mode if missing N/A (bundled) ⚠ Runtime error (silent) Import error (explicit)
Input format List[str] List[str] List[Node] (must parse first)
Output format Result objects List[Document] → .page_content List[NodeWithScore] → .text
Standalone BM25 No (hybrid-first) Yes Yes
Underlying algorithm BM25Okapi (rank_bm25) BM25Okapi (rank_bm25) BM25Okapi (rank_bm25)
Result quality Identical (same algorithm) Identical (same algorithm) Identical (same algorithm)
www.engineersofai.com