bm25

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: AGPL-3.0 Imports: 4 Imported by: 0

Documentation

Overview

Package bm25 provides Okapi BM25 ranking for code-aware corpora.

Stability

This package is part of the public import surface of github.com/0xmhha/code-knowledge-graph. The exported API — Scorer, Okapi, NewOkapi, Document, ScoredDoc, Tokenize, and the two default constants — follows semantic versioning:

  • Minor releases may add new types, methods, or fields.
  • Patch releases fix bugs without changing the API surface.
  • The Scorer interface will not gain methods in a minor release; a breaking change to Scorer requires a new major version.

External consumers (ckv, cks) should depend on the Scorer interface and construct via NewOkapi. Direct field access on Okapi (K1, B) is stable for tuning but the unexported fields are not part of the contract.

Typical usage (external consumer)

import "github.com/0xmhha/code-knowledge-graph/pkg/bm25"

scorer := bm25.NewOkapi()
scorer.Index([]bm25.Document{
    {ID: "node-1", Tokens: bm25.Tokenize("HandleDeposit")},
    {ID: "node-2", Tokens: bm25.Tokenize("processWithdraw")},
})
hits := scorer.TopK(bm25.Tokenize("deposit"), 10)

Package bm25 implements Okapi BM25 ranking for code-aware corpora.

The algorithm is the standard Okapi BM25 formulation (Robertson, Walker 1994) with the conventional smoothing IDF (`log(1 + (N-n+0.5)/(n+0.5))`). The implementation is hand-written in Go from the algorithm description alone — no source copied from any library. Behaviour was cross-checked against two reference implementations:

  • github.com/blevesearch/bleve (Apache-2.0) — search/scorer/scorer_term.go
  • github.com/dorianbrown/rank_bm25 (Apache-2.0, Python) — bm25.BM25Okapi

Algorithm authors and reference implementations are credited above; this file contains no derivative code from either project.

Index

Constants

View Source
const (
	DefaultK1 = 1.5
	DefaultB  = 0.75
)

Default Okapi BM25 hyperparameters. K1 controls term-frequency saturation (higher = more weight to repeated matches); B controls length normalization (0 = ignore length, 1 = full normalization). 1.5 / 0.75 is the most widely cited starting point in the literature and matches both bleve and rank_bm25 defaults.

Variables

This section is empty.

Functions

func Tokenize

func Tokenize(s string) []string

Tokenize converts an arbitrary string into BM25 tokens with code-aware splitting. The output preserves the joined identifier (lowercased) AND its sub-words so a query for either form scores. Concretely:

  • "parseFile" → ["parsefile", "parse", "file"]
  • "HTTPServer" → ["httpserver", "http", "server"]
  • "read_file" → ["read_file", "read", "file"]
  • "pkg.Type.Method" → ["pkg", "type", "method"]
  • "json-rpc/v1" → ["json", "rpc", "v1"]

All tokens are lowercased; tokens of length < 2 are dropped (they add noise without helping rank). Caller-supplied tokens are not deduped — repetition is meaningful to BM25's TF term.

Types

type Document

type Document struct {
	ID     string
	Tokens []string
}

Document is one indexable record. Tokens are pre-tokenized — call Tokenize for the package's standard code-aware splitter, or supply custom tokens for a domain-specific corpus.

type Okapi

type Okapi struct {
	K1 float64
	B  float64
	// contains filtered or unexported fields
}

Okapi is the standard BM25Okapi scorer. Concurrent reads (Score, TopK) after a completed Index are safe; do NOT call Index concurrently with a reader. Build once, query many.

func NewOkapi

func NewOkapi() *Okapi

NewOkapi returns a scorer with default hyperparameters. Override K1/B directly on the returned struct before calling Index.

func (*Okapi) Index

func (o *Okapi) Index(docs []Document)

Index registers the corpus. Empty input is allowed — the scorer will return 0 for all queries until a non-empty Index call replaces state.

func (*Okapi) Score

func (o *Okapi) Score(query []string, docID string) float64

Score returns the BM25 score for one document under the given query. Both K1 and B come from the Okapi struct so callers can tune at runtime without recreating the scorer.

func (*Okapi) TopK

func (o *Okapi) TopK(query []string, k int) []ScoredDoc

TopK returns the top-k documents by score, descending. Documents with score 0 (no matching terms) are excluded. k <= 0 means "all matches".

type ScoredDoc

type ScoredDoc struct {
	ID    string
	Score float64
}

ScoredDoc pairs a document ID with its BM25 score for the most recent query. Score is always >= 0; documents with no matching terms are not returned by TopK.

type Scorer

type Scorer interface {
	// Index registers the full corpus. Repeated calls overwrite earlier
	// state — Scorer is not append-only.
	Index(docs []Document)
	// Score returns the BM25 score for one document under one query.
	// Returns 0 when docID is unknown or no query term matches.
	Score(query []string, docID string) float64
	// TopK returns the top-k documents by score, descending. k <= 0
	// returns every matching document.
	TopK(query []string, k int) []ScoredDoc
}

Scorer is the contract every BM25 implementation in this package satisfies. The two-phase pattern (Index then Score / TopK) lets callers build the corpus once per build and reuse for many queries.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL