SearXNG BM25 Reranker
English | 中文
An external SearXNG plugin that reranks search results using BM25 text relevance scoring to improve search quality.
This package is part of searxng_rerankers. It installs and runs on its own, and it can cooperate with sem_reranker when both are enabled.
Features
- BM25F Multi-Field Weighting — Title weight 2.0, content weight 1.0, prioritizing title-matching results
- RRF Fusion Ranking — Combines BM25 scores with original engine rankings via Reciprocal Rank Fusion, rather than replacing them
- CJK Tokenization — Built-in zero-dependency CJK tokenizer (unigram + bigram) covering Chinese ideographs and Japanese kana, no jieba required
- HTML-Aware Scoring — Strips markup and decodes entities before tokenizing, so tag noise (
div,href,www) never dilutes relevance - Zero External Dependencies — Core BM25 engine from zerodep/sparse_search, pure standard library
- Plug and Play — Standard SearXNG external plugin, deployable via Docker image or pip install
How It Works
Search request → Engines return results → [post_search hook]
↓
Build temporary BM25F index (title + content)
↓
BM25 retrieval with original query
↓
RRF fusion (engine ranking + BM25 ranking, k=20)
↓
Rewrite positions to influence scoring → Reranked results
The plugin hooks into the post_search phase, after all engine results are collected but before final scores are calculated. By rewriting each result's positions list, it influences SearXNG's built-in calculate_score() formula (weight / position), achieving non-invasive reranking.
Installation
Option 1: Pre-built Docker Image (Recommended)
Use the pre-built image from Docker Hub, which tracks searxng/searxng:latest with the plugin pre-installed:
services:
searxng:
image: oaklight/searxng:latest
restart: always
ports:
- "127.0.0.1:8080:8080"
volumes:
- ./searxng:/etc/searxng:rw
environment:
- SEARXNG_BASE_URL=https://search.example.com
Option 2: Inline Build in Docker Compose
Build from the official SearXNG image with the plugin installed at compose time:
services:
searxng:
build:
dockerfile_inline: |
FROM searxng/searxng:latest
RUN /usr/local/searxng/.venv/bin/python3 -m ensurepip && \
/usr/local/searxng/.venv/bin/python3 -m pip install --no-cache-dir searxng-bm25-reranker && \
/usr/local/searxng/.venv/bin/python3 -m pip uninstall -y pip
restart: always
ports:
- "127.0.0.1:8080:8080"
volumes:
- ./searxng:/etc/searxng:rw
environment:
- SEARXNG_BASE_URL=https://search.example.com
Plugin Registration
For both options, register the plugin in settings.yml:
plugins:
searxng_bm25_reranker.SXNGPlugin:
active: true
Then start or restart the container:
docker compose up -d
Configuration
The plugin works out of the box with no additional configuration. Every value below can be
overridden per instance in settings.yml in a top-level block keyed by the plugin id.
Weights must be numbers >= 0; rrf_k must be a whole number >= 1.
| Parameter | Default | Description |
|---|---|---|
title_weight |
2.0 |
BM25F weight for the title field |
content_weight |
1.0 |
BM25F weight for the content field |
url_weight |
0.5 |
BM25F weight for the host and path field |
rrf_k |
20 |
RRF fusion constant |
engine_weight |
1.0 |
Weight of the original engine ranking in the fusion |
bm25_weight |
1.0 |
Weight of the BM25 ranking in the fusion |
Fixed and not exposed: Okapi BM25 with k1=1.5, b=0.75, delta=0.
The url field indexes the host and path only. Scheme, query string, and fragment are
dropped, and percent escapes are decoded. A slug such as /python/asyncio can match
without adding tracking-parameter noise. Set url_weight to 0 to ignore URLs.
rrf_k defaults to 20 rather than the Cormack et al. value of 60 because that constant was
tuned on retrieval runs with thousands of candidates, while a SearXNG page holds roughly 30
results. At 60, the gap between rank 1 and rank 30 collapses to 1.48x and mutes the relevance
signal. A value far above 20 flattens the differences further and eventually reduces the
rerank to engine order.
bm25_reranker:
rrf_k: 10
bm25_weight: 2.0
The block is top-level and keyed by the plugin id -- SearXNG's convention for
plugin-specific options (the in-tree hostnames plugin works the same way).
PluginCfg only carries active, so putting these keys under plugins:
makes SearXNG fail to start rather than fall back.
Raising bm25_weight lets text relevance outweigh the original engine order more aggressively;
raising engine_weight does the opposite.
URL priority
url_priority multiplies the fused score of every result whose URL matches a
regular expression, so a domain can be pushed up or down without touching the
ranking code. A multiplier below 1.0 demotes, above 1.0 promotes, and 0.0
sends the result to the bottom. Every matching pattern applies, so overlapping
entries compound.
The block goes at the top level of settings.yml, keyed by the plugin id.
It does not go under plugins:, which only accepts active:
bm25_reranker:
url_priority:
'(.*\.)?arxiv\.org$': 1.5
'(.*\.)?github\.com$': 1.2
'(.*\.)?pinterest\.com$': 0.1
'(.*\.)?facebook\.com$': 0.0
plugins:
searxng_bm25_reranker.SXNGPlugin:
active: true
An invalid regular expression, or a multiplier that is not a number or is
negative, is skipped with a warning in the log. A YAML boolean counts as "not a
number", so writing false does not switch a rule off; it is skipped too. A
typo therefore degrades to "no multiplier" instead of breaking search.
Patterns are matched with re.search, so an unanchored pattern matches
anywhere in the URL, query string included. Anchor with $ as the examples do
to mean the whole domain. The setting is read when the plugin loads, so changing
it needs a restart.
Coordination with sem_reranker
SearXNG stores plugins in a set, so post_search order is not stable. Both
plugins rewrite positions, so exactly one plugin may do the work. Set the
coordinator explicitly:
rerankers:
coordinator: bm25_reranker
The coordinator computes BM25 and, when sem_reranker is active, its neural
scores; it fuses both with RRF and rewrites positions once. The other plugin
returns immediately. If the configured coordinator is not active, the active
plugin coordinates. If sem_reranker is not installed, BM25 runs alone.
Semantic escalation gate
When this plugin is the coordinator, it can keep BM25 as the baseline and call
sem_reranker only when a cheap indicator says BM25 is weak:
rerankers:
coordinator: bm25_reranker
escalate:
enabled: true
min_coverage: 0.5
min_margin: 0.1
min_scored: 2
top_k: 10
The gate uses no network and no extra model:
min_coverage: best fraction of query tokens found in one of the toptop_kBM25 results. Below this value, the gate opens.min_margin:(top1 - top2) / top1over BM25 scores. Below this value, the gate opens.min_scored: fewer BM25-scored results opens the gate.top_k: how many BM25 results to inspect for coverage.
Any one indicator can open the gate. With enabled: false (the default), the
gate always opens, so sem_reranker is called on every request when it is
active. Even when the gate opens, sem_reranker.rerank.top_k and
rerank.max_chars cap the request size, and the cache reuses repeated
query-document pairs. The gate only applies when rerankers.coordinator: bm25_reranker.
Project Structure
This package lives in the searxng_rerankers monorepo:
packages/bm25_reranker/
├── pyproject.toml
├── src/searxng_bm25_reranker/
│ ├── __init__.py # SXNGPlugin, post_search, public scoring helpers
│ ├── _tokenizer.py # CJK-aware tokenizer (unigram + bigram)
│ └── _vendor/
│ └── sparse_search.py # BM25 engine from zerodep (vendored)
└── tests/
├── test_rerank.py
├── test_stress_bm25.py
├── test_tokenizer.py
└── test_url_priority.py
Run the tests from the repository root with pytest. The shared conftest.py
installs the SearXNG API stub because SearXNG is not a dependency. Run
make stress for the large-input and failure-path tests.
Acknowledgements
- SearXNG — Privacy-respecting metasearch engine
- zerodep/sparse_search — Zero-dependency BM25 full-text search engine
License
Metadata
Release files for searxng-bm25-reranker 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| searxng_bm25_reranker-0.4.0.tar.gz | 48.2 kB | With provenance | Details for searxng_bm25_reranker-0.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| searxng_bm25_reranker-0.4.0-py3-none-any.whl | Python 3 | none | any | Details for searxng_bm25_reranker-0.4.0-py3-none-any.whl |
Total release size: 86.0 kB
Release files / searxng_bm25_reranker-0.4.0.tar.gz
| Download URL | searxng_bm25_reranker-0.4.0.tar.gz |
|---|---|
| Size | 48.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
670779ba80ea1577e0152728b43e751da87d0319b52363e7192a6a651355a61e
|
|
BLAKE2b-256 checksum How to use checksums |
9dcfedf3c69d1f4aedf93bff59942558e6910d245651f157d382c33982490662
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / searxng_bm25_reranker-0.4.0-py3-none-any.whl
| Download URL | searxng_bm25_reranker-0.4.0-py3-none-any.whl |
|---|---|
| Size | 37.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
039b0a5743f6930ea15f133bbe83eac8601115423acbde4d51aaa832753c8909
|
|
BLAKE2b-256 checksum How to use checksums |
10e35ba523876092e1746b34d793c9c6d09b283ad64f14a9018e55462f09fbcf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logSecurity for searxng-bm25-reranker 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for searxng_bm25_reranker-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.