Registry / vector-search / bm25s
library0.3.11pypypi✓ verified 23d ago

bm25s (BM25-Sparse) is an ultra-fast implementation of the BM25 lexical search algorithm in pure Python, primarily leveraging NumPy for sparse matrix operations. It focuses on high performance and low dependency, providing significant speedups over other Python implementations. The library is actively developed, with version 0.3.3 being the latest release, and receives regular updates including new features and performance enhancements.

pip install bm25s
INSTALL
IMPORT
SIG · BM25S
B
bm25s
vector-searchpythonv0.3.11
Install
Import
Disk
Pass rate
0/ 10
Env Coverage0 / 10
glibc
3.93.13
musl
3.93.13
Install & Compatibility
Where this runs
tested against v0.3.11 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
musl
glibc
py 3.10
2/3 runs
2/3 runs
py 3.11
2/3 runs
2/3 runs
py 3.12
1/3 runs
2/3 runs
py 3.13
2/3 runs
2/3 runs
py 3.9
2/3 runs
2/3 runs
Code
Verified usage

Verified import paths — ran on the pinned version, not inferred.

BM25
from bm25s import BM25
tokenize
from bm25s import tokenize
selection
from bm25s import selection_np
from bm25s import selection
As of v0.3.0, `selection` was renamed internally to `selection_np` to avoid conflicts and signify its NumPy-based nature. Direct imports should be updated.

This quickstart demonstrates how to initialize BM25S, tokenize a corpus (with optional stemming and stopword removal), index the documents, and perform a query to retrieve the top-k most relevant documents. It showcases the core `BM25` class and `tokenize` utility.

import bm25s import Stemmer # Ensure 'pip install PyStemmer' is run for this corpus = [ "a cat is a feline and likes to purr", "a dog is the human's best friend and loves to play", "a bird is a beautiful animal that can fly", "a fish is a creature that lives in water and swims", ] # Optional: create a stemmer and tokenizer # For optimal results, ensure PyStemmer is installed or provide your own tokenizer. stemmer = Stemmer.Stemmer("english") tokenized_corpus = bm25s.tokenize(corpus, stemmer=stemmer, stopwords="english") # Create the BM25 model and index the corpus retriever = bm25s.BM25(corpus=corpus) retriever.index(tokenized_corpus) # Query the corpus and get top-k results query = "does the fish purr like a cat?" tokenized_query = bm25s.tokenize(query, stemmer=stemmer, stopwords="english") results, scores = retriever.retrieve(tokenized_query, k=2) # Print the ranked results print(f"Query: '{query}'") print("Top results:") for i in range(results.shape[0]): doc_id = results[i, 0] score = scores[i, 0] print(f" (score: {score:.2f}): {corpus[doc_id]}")
Debug
Known issues
breakingStarting with version 0.3.0, `scipy` is no longer a required dependency. The library now uses a pure NumPy-based CSC matrix builder by default. If you relied on SciPy's CSC builder, you must install `scipy` separately and pass `csc_backend="scipy"` to the `BM25()` constructor, or install `bm25s` with the `[indexing]` extra (e.g., `pip install bm25s[indexing]`).
fix
Explicitly install `scipy` and pass `csc_backend="scipy"` to `BM25()`, or install with `pip install bm25s[indexing]`.
affects: >=0.3.0
breakingThe internal import path for the `selection` module changed in version 0.3.0. If you were directly importing `selection` (e.g., `from bm25s import selection`), you should update your imports to `from bm25s import selection_np`.
fix
Update import statements from `from bm25s import selection` to `from bm25s import selection_np`.
affects: >=0.3.0
gotchaFor simpler, beginner-friendly usage and a high-level API that includes all necessary dependencies, the project now recommends using the separate `bm25` package (i.e., `pip install bm25`). Users should be aware of the distinction if seeking a fully batteries-included experience.
fix
Consider `pip install bm25` for a higher-level API, or explicitly manage `bm25s` dependencies (like `PyStemmer`) for advanced control.
affects: All
gotchaAchieving optimal performance and relevance often requires optional dependencies such as `PyStemmer` for stemming, `numba` for JIT compilation, or `jax[cpu]` for accelerated top-k selection. Without these, performance or search quality might not meet expectations, especially on large datasets.
fix
Install recommended optional dependencies like `pip install PyStemmer`, `pip install numba`, or `pip install jax[cpu]` based on your performance and feature needs.
affects: All
Errors
Common errors & fixes
TypeError: 'int' object is not subscriptable
The `BM25.retrieve` method's `k` argument (for the number of results) is intended to be passed as a keyword argument (e.g., `k=value`), but users often pass it as a positional argument. This causes `bm25s` to misinterpret the integer `k` as the `corpus` argument, which expects an iterable, leading to a `TypeError` when it attempts to subscript an integer.
fix
Ensure the `k` argument is passed as a keyword argument when calling `retrieve`.
```python
import bm25s

corpus = ["doc one", "document two"]
tokenized_corpus = bm25s.tokenize(corpus)

retriever = bm25s.BM25()
retriever.index(tokenized_corpus)

query_tokens = bm25s.tokenize(["query"]) # Query must also be tokenized

# Incorrect (would cause the error):
# results, scores = retriever.retrieve(query_tokens, 1)

# Correct:
results, scores = retriever.retrieve(query_tokens, k=1, corpus=corpus)
print(results)
```
ModuleNotFoundError: No module named 'bm25s'
The `bm25s` library has not been installed in the current Python environment, or the Python interpreter being used does not have access to the installed package.
fix
Install the library using pip. For full functionality, including stemming, use the `[full]` extra.
```bash
pip install bm25s
# For full features, including stemming and Numba backend (if available):
pip install bm25s[full]
```
AttributeError: 'Tokenizer' object has no attribute 'save_vocab'
The `bm25s.tokenizer.Tokenizer` class and the output of `bm25s.tokenize()` do not expose a public method named `save_vocab`. The `bm25s` library is designed to manage its vocabulary internally within the `BM25` index object, which can be saved and loaded.
fix
Instead of attempting to call `save_vocab` on a `Tokenizer` object, save the entire `BM25` retriever object, which encapsulates the vocabulary and other index data. If you specifically need to access the vocabulary for inspection or external use, you can obtain it from a `Tokenizer` instance.
```python
import bm25s
from bm25s.tokenizer import Tokenizer

corpus = ["apple banana", "orange pear"]

# If you create a Tokenizer instance directly and need its vocab:
tokenizer_instance = Tokenizer()
tokenized_corpus = tokenizer_instance(corpus)

# To get the vocabulary:
vocabulary_map = tokenizer_instance.get_vocab() # Returns a dict-like object
# You can then process/save this vocabulary manually if needed:
# import json
# with open("my_vocab.json", "w") as f:
#     json.dump(list(vocabulary_map.keys()), f)

# The recommended way: The BM25 object handles saving the full index, including vocabulary.
retriever = bm25s.BM25()
retriever.index(tokenized_corpus)

# To save the entire index (which includes the vocabulary):
# retriever.save("my_bm25_index")
# To load it later:
# loaded_retriever = bm25s.BM25.load("my_bm25_index")
```
Incorrect handling of 'corpus' argument in BM25 constructor or retrieve method (e.g., retrieve returning indices instead of text).
Users sometimes pass the raw document list (`corpus`) to the `bm25s.BM25` constructor, expecting it to be indexed and available for retrieval. However, the `BM25` constructor primarily expects the *tokenized* corpus (`corpus_tokens`). The `retrieve` method's `corpus` argument is typically used to map the retrieved document *indices* back to the original document *strings* for display, not for indexing itself. Confusion arises if the `BM25` object is initialized without the raw `corpus` internally, but then the `retrieve` method is called without providing the `corpus` argument, leading to it returning just document IDs.
fix
Always tokenize your corpus first. Initialize `bm25s.BM25` without the raw corpus, then explicitly call `retriever.index(tokenized_corpus)`. When calling `retriever.retrieve()`, pass the original raw `corpus` if you want the actual document strings to be returned; otherwise, it will return document indices.
```python
import bm25s

corpus = [
    "Machine learning is a subset of AI",
    "Deep learning uses neural networks",
    "Natural language processing handles text"
]

# 1. Tokenize the corpus
tokenized_corpus = bm25s.tokenize(corpus)

# 2. Initialize BM25 without passing the raw corpus to the constructor
retriever = bm25s.BM25()

# 3. Index the tokenized corpus
retriever.index(tokenized_corpus)

query = "What is AI"
tokenized_query = bm25s.tokenize([query])

# To get original document strings back:
results_docs, scores = retriever.retrieve(tokenized_query, k=1, corpus=corpus)
print("Retrieved documents:", results_docs)

# To get document indices back:
results_indices, scores = retriever.retrieve(tokenized_query, k=1)
print("Retrieved indices:", results_indices)
```
Upgrade
Version history
0.3.11latest on PyPI · released Aug 25, 2026
Audit
Dependencies
numpyrequiredCore dependency for sparse matrix operations.
scipyoptionalOptional dependency for its CSC matrix builder (was required before 0.3.0).
PyStemmeroptionalOptional dependency for efficient stemming, recommended for better search results.
numbaoptionalOptional dependency for JIT compilation, providing speedups for certain operations.
jaxoptionalOptional dependency for accelerated top-k selection.
richoptionalOptional dependency for enhanced Command-Line Interface (CLI) UI.
Agent activity
31 hits · last 30 days
node
26
OpenAI (training)
1
Resources
bm25s — pip install bm25s · libregistry