What BM25Engine Does
BM25Engine implements BM25 lexical scoring inside an ODC app. It tokenizes text, stems tokens, and scores candidate chunks against a query using the BM25 formula. It does not perform vector search, hybrid retrieval, RRF fusion, reranking, caching, or synonym expansion. Persistence, entity ownership, and workflow orchestration are the consuming app's responsibility.
Server Actions
TokenizeText
IndexChunk
ChunkIndexResult
TokenCount
TermFrequency
Term
Frequency
ScoreQuery
PostingCandidate
ChunkId
TermFrequencyInChunk
DocumentFrequency
ChunkTokenCount
TotalChunks
avgChunkLength
K1
B
BM25ScoreResult
Score
IDF(t) = ln((N - df(t) + 0.5) / (df(t) + 0.5) + 1)
Installation
BM25Term
Id
BM25Posting
BM25TermId
DocumentChunkId
BM25Stats
TotalTokenCount
DocumentChunk
TokenizerVersion
v2-stemmed-porter
Configuring the Ingestion Workflow
Create an IndexChunks Server Action or Workflow that runs after embedding. For each DocumentChunk row:
IndexChunks
BM25Engine.IndexChunk
After all chunks are indexed, trigger RecomputeBM25Stats manually from the ODC Portal before running any search.
RecomputeBM25Stats
Configuring the RecomputeBM25Stats Timer
Create a Timer called RecomputeBM25Stats in your app. Its action must:
COUNT(DISTINCT DocumentChunkId) GROUP BY BM25TermId
This timer must run at least once after the first index. If BM25Stats has no row, ScoreQuery divides by zero. Schedule it to run periodically to keep DocumentFrequency current after new ingestion.
Using BM25Engine at Query Time
BM25Engine.TokenizeText
BM25Engine.ScoreQuery
Tokenizer Version Management
The TokenizerVersion Site Property (default v2-stemmed-porter) tracks which tokenizer pipeline produced the current index. BM25Engine does not detect version mismatches. If the tokenizer changes in a future release:
Skipping the reindex after a tokenizer change causes query stems to stop matching indexed stems. Recall degrades silently with no error.
BM25 Tuning Parameters
Scale Boundary
ScoreQuery performs a linear scan over the candidate posting set. The scale ceiling has not been load-tested. Posting fan-out for common terms will eventually become the bottleneck. Run load tests against your own corpus size before using this in production at scale.