Document Scoring Methods

When aggregating chunk-level search results into document-level scores, LocalVectorDB supports four scoring methods plus "auto", which is the default and picks between them based on search_type. Each has a different strength depending on your use case.

Note

Earlier releases exposed seven additional heuristic methods (worst, weighted_average, harmonic_mean, diminishing_returns, statistical, robust_mean, geometric_mean). They were removed in v0.1.0: measured on the NFCorpus retrieval benchmark none of them beat the methods below, they overlapped heavily, and several were non-monotonic (adding a low-scoring chunk could reorder documents). Passing a removed name now raises ValueError. percentile was pruned in the same pass but has since returned in a simplified single-knob form (see below) — its old two-percentile blend is gone for good.

Methods

"auto" (Default)

Picks the aggregator from search_type: "best" for search_type="vector", "frequency_boost" for "hybrid" and "keyword". Passing any explicit method pins it and disables this behaviour.

The two differ because of the scale the chunk scores arrive on, not the corpus. Hybrid and keyword min-max normalise each leg within the query’s own candidate pool, so the best chunk is 1.0 by construction and frequency_boost’s count multiplier acts on a bounded, query-relative scale. Vector search passes a raw bounded similarity through unchanged, where the same multiplier mostly rewards documents for owning more chunks. Measured on the vector leg, "best" beat "frequency_boost" by +0.0226 nDCG@10 on BEIR SciFact, +0.0150 on Natural Questions and +0.0084 on qasper.

A related result decided the section path: aggregation is a property of the unit being ranked, not of the corpus. Across 20 measured corpus/target/leg/pool cells a summing aggregator never lost on a document target and never won on a section target, so section-level roll-up keeps a plain maximum and exposes no knob.

"best"

Uses the highest-scoring chunk as the document score. Choose this when you want documents ranked by their single most relevant passage, regardless of overall document quality.

Parameters: None

"average"

Takes the arithmetic mean of all chunk scores. Good for documents where overall content quality matters more than peak relevance.

Parameters: None

"frequency_boost"

Boosts the best chunk score based on the number of quality chunks found, rewarding documents with multiple relevant passages. Ideal for comprehensive documents where breadth of coverage indicates relevance.

Parameters:

  • frequency_bias (0.0-1.0, default=0.3): Controls how much to boost scores based on chunk frequency. Higher values favor documents with more matching chunks.

"percentile"

A soft maximum: the document score is the p-th order statistic of its chunk scores. At percentile=1.0 it is exactly "best"; lower values drift toward the mean as a document owns more chunks, so a document whose relevance is spread across several strong chunks can outrank one carried by a single outlier.

This is a document-target aggregator. Measured against "best" when rolling chunks up to documents it was worth up to ~+0.02 nDCG@10 on high-fanout corpora (many chunks per document), and it consistently lost at section targets — which is why "auto" never selects it. Pin it explicitly via document_scoring_method="percentile" if your workload matches.

Parameters:

  • percentile (0.0-1.0, default=0.9): The order statistic to take, passed via document_scoring_options (0.9 means the 90th percentile; values outside [0, 1] raise ValueError). Interpolation matches numpy.percentile’s default linear rule, which is what the sweep behind this option measured.

Note

The percentile method that shipped before v0.1.0 was a two-percentile blend. That form lost to this clean order statistic in 19 of 20 measured cells, so its extra options do not come back with it: secondary_percentile and primary_weight in document_scoring_options are silently ignored.

Choosing a Method

  • Not sure: leave the default "auto"

  • Single best passage matters most: Use "best"

  • Overall document quality important: Use "average"

  • Want to reward multiple relevant sections: Use "frequency_boost"

  • Document-level ranking on a high-fanout corpus, want a soft max: Use "percentile"

How Raw Scores Are Computed

Before document-level aggregation is applied, each chunk receives a raw similarity score between 0.0 and 1.0. The normalization depends on the search type.

Using Scoring Methods via the Server API

All search endpoints accept document_scoring_method and document_scoring_options in the request body. These parameters are forwarded directly to the local database’s query() method.

# Unified query endpoint
curl -X POST http://localhost:8000/api/v1/databases/my_db/query \
  -H "Content-Type: application/json" \
  -d '{
    "query": "machine learning",
    "search_type": "hybrid",
    "return_type": "documents",
    "k": 10,
    "score_threshold": 0.3,
    "vector_weight": 0.5,
    "document_scoring_method": "frequency_boost",
    "document_scoring_options": {
      "frequency_bias": 0.3
    }
  }'

The convenience endpoints (/search/vector, /search/keyword, /search/hybrid) also accept these parameters with the same schema.

Note

document_scoring_method and document_scoring_options only take effect when return_type is "documents". For chunk-level return types ("chunks", "context", "enriched"), raw chunk scores are returned directly.