Back to Chroma

Ranking and Scoring

docs/mintlify/cloud/search-api/ranking.mdx

1.5.923.4 KB
Original Source

import { Callout, Warning } from '/snippets/callout.mdx';

How Ranking Works

A ranking expression determines which documents are scored and how they're ordered:

Expression Evaluation Process

  1. No ranking (rank=None): Documents are returned in index order (typically insertion order)

  2. With ranking expression:

    • Must contain at least one Knn expression
    • Documents must appear in at least one Knn's top-k results to be considered
    • Documents must also appear in ALL Knn results where default=None
    • Documents missing from a Knn with a default value get that default score
    • Each Knn considers its top limit candidates (default: 16)
    • Documents are sorted by score (ascending - lower scores first)
    • Final results based on Search.limit()

Document Selection and Scoring

<CodeGroup> ```python Python # Example 1: Single Knn - scores top 16 documents rank = Knn(query="machine learning research") # Only the 16 nearest documents get scored (default limit)

Example 2: Multiple Knn with default=None

rank = Knn(query="research papers", limit=100) + Knn(query="academic publications", limit=100, key="sparse_embedding")

Both Knn have default=None (the default)

Documents must appear in BOTH top-100 lists to be scored

Documents in only one list are excluded

Example 3: Mixed default values

rank = Knn(query="AI research", limit=100) * 0.5 + Knn(query="scientific papers", limit=50, default=1000.0, key="sparse_embedding") * 0.5

First Knn has default=None, second has default=1000.0

Documents in first top-100 but not in second top-50:

- Get first distance * 0.5 + 1000.0 * 0.5 (second's default)

Documents in second top-50 but not in first top-100:

- Excluded (must appear in all Knn where default=None)

Documents in both lists:

- Get first distance * 0.5 + second distance * 0.5


```typescript TypeScript
// Example 1: Single Knn - scores top 16 documents
const rank1 = Knn({ query: "machine learning research" });
// Only the 16 nearest documents get scored (default limit)

// Example 2: Multiple Knn with default undefined
const rank2 = Knn({ query: "research papers", limit: 100 })
  .add(Knn({ query: "academic publications", limit: 100, key: "sparse_embedding" }));
// Both Knn have default undefined (the default)
// Documents must appear in BOTH top-100 lists to be scored
// Documents in only one list are excluded

// Example 3: Mixed default values
const rank3 = Knn({ query: "AI research", limit: 100 }).multiply(0.5)
  .add(Knn({ query: "scientific papers", limit: 50, default: 1000.0, key: "sparse_embedding" }).multiply(0.5));
// First Knn has default undefined, second has default 1000.0
// Documents in first top-100 but not in second top-50:
//   - Get first distance * 0.5 + 1000.0 * 0.5 (second's default)
// Documents in second top-50 but not in first top-100:
//   - Excluded (must appear in all Knn where default is undefined)
// Documents in both lists:
//   - Get first distance * 0.5 + second distance * 0.5
rust
use chroma::types::{Key, QueryVector, RankExpr};

let rank1 = RankExpr::Knn {
    query: QueryVector::Dense(vec![0.1, 0.2, 0.3]),
    key: Key::Embedding,
    limit: 16,
    default: None,
    return_rank: false,
};

let rank2 = RankExpr::Knn {
    query: QueryVector::Dense(vec![0.1, 0.2, 0.3]),
    key: Key::Embedding,
    limit: 100,
    default: None,
    return_rank: false,
};
</CodeGroup> <Warning> When combining multiple `Knn` expressions, documents must appear in at least one `Knn`'s results AND must appear in every `Knn` where `default=None`. To avoid excluding documents, set `default` values on your `Knn` expressions. </Warning>

The Knn Class

The Knn class performs K-nearest neighbor search to find similar vectors. It's the primary way to add vector similarity scoring to your searches.

<Callout> **Sparse embeddings:** To search custom sparse embedding fields, you must first configure a sparse vector index in your collection schema. See [Sparse Vector Search Setup](../schema/sparse-vector-search) for configuration instructions. </Callout> <CodeGroup> ```python Python from chromadb import Knn

Basic search on default embedding field

Knn(query="What is machine learning?")

Search with custom parameters

Knn( query="What is machine learning?", key="#embedding", # Field to search (default: "#embedding") limit=100, # Max candidates to consider (default: 16) return_rank=False # Return rank position vs distance (default: False) )

Search custom sparse embedding field in metadata

Knn(query="machine learning", key="sparse_embedding")


```typescript TypeScript
import { Knn } from 'chromadb';

// Basic search on default embedding field
Knn({ query: "What is machine learning?" });

// Search with custom parameters
Knn({
  query: "What is machine learning?",
  key: "#embedding",      // Field to search (default: "#embedding")
  limit: 100,            // Max candidates to consider (default: 16)
  returnRank: false      // Return rank position vs distance (default: false)
});

// Search custom sparse embedding field in metadata
Knn({ query: "machine learning", key: "sparse_embedding" });
</CodeGroup>

Knn Parameters

ParameterTypeDefaultDescription
querystr, List[float], SparseVector, or np.ndarrayRequiredThe query text or vector to search with
keystr"#embedding"Field to search - "#embedding" for dense embeddings, or a metadata field name for sparse embeddings
limitint16Maximum number of candidates to consider
defaultfloat or NoneNoneScore for documents not in KNN results
return_rankboolFalseIf True, return rank position (0, 1, 2...) instead of distance
<Callout> `"#embedding"` (or `K.EMBEDDING`) refers to the default embedding field where Chroma stores dense embeddings. Sparse embeddings must be stored in metadata under a consistent key. </Callout>

Query Formats

Text Queries

<CodeGroup> ```python Python # Text query (most common - auto-embedded using collection schema) Knn(query="machine learning applications")

Text is automatically converted to embeddings using the collection's

configured embedding function

Knn(query="What are the latest advances in quantum computing?")


```typescript TypeScript
// Text query (most common - auto-embedded using collection schema)
Knn({ query: "machine learning applications" });

// Text is automatically converted to embeddings using the collection's
// configured embedding function
Knn({ query: "What are the latest advances in quantum computing?" });
</CodeGroup>

Dense Vectors

<CodeGroup> ```python Python # Python list Knn(query=[0.1, 0.2, 0.3, 0.4])

NumPy array

import numpy as np embedding = np.array([0.1, 0.2, 0.3, 0.4]) Knn(query=embedding)


```typescript TypeScript
// Array
Knn({ query: [0.1, 0.2, 0.3, 0.4] });

// Float32Array or other typed arrays
const embedding = new Float32Array([0.1, 0.2, 0.3, 0.4]);
Knn({ query: embedding });
</CodeGroup>

Sparse Vectors

<CodeGroup> ```python Python # Sparse vector format: dictionary with indices and values sparse_vector = { "indices": [1, 5, 10, 50], # Non-zero indices "values": [0.5, 0.3, 0.8, 0.2] # Corresponding values }

Search using sparse vector (must specify the metadata field)

Knn(query=sparse_vector, key="sparse_embedding")


```typescript TypeScript
// Sparse vector format: object with indices and values
const sparseVector = {
  indices: [1, 5, 10, 50],         // Non-zero indices
  values: [0.5, 0.3, 0.8, 0.2]     // Corresponding values
};

// Search using sparse vector (must specify the metadata field)
Knn({ query: sparseVector, key: "sparse_embedding" });
</CodeGroup>

Embedding Fields

Chroma currently supports:

  1. Dense embeddings - Stored in the default embedding field ("#embedding" or K.EMBEDDING)
  2. Sparse embeddings - Can be stored in metadata under a consistent key
<CodeGroup> ```python Python # Text or dense embeddings - use the default embedding field Knn(query="machine learning") # Implicitly uses key="#embedding" Knn(query="machine learning", key="#embedding") # Explicit Knn(query="machine learning", key=K.EMBEDDING) # Using constant (same as "#embedding")

Sparse embeddings - store in metadata under a consistent key

The sparse vector should be stored under the same metadata key across all documents

Knn(query="machine learning", key="sparse_embedding") # Search sparse embeddings in metadata

NOT SUPPORTED: Dense embeddings in metadata

Knn(query=[0.1, 0.2], key="some_metadata_field") # Not supported


```typescript TypeScript
// Text or dense embeddings - use the default embedding field
Knn({ query: "machine learning" });              // Implicitly uses key "#embedding"
Knn({ query: "machine learning", key: "#embedding" });  // Explicit
Knn({ query: "machine learning", key: K.EMBEDDING });   // Using constant (same as "#embedding")

// Sparse embeddings - store in metadata under a consistent key
// The sparse vector should be stored under the same metadata key across all documents
Knn({ query: "machine learning", key: "sparse_embedding" });  // Search sparse embeddings in metadata

// NOT SUPPORTED: Dense embeddings in metadata
// Knn({ query: [0.1, 0.2], key: "some_metadata_field" })  // Not supported
</CodeGroup> <Warning> Currently, dense embeddings can only be stored in the default embedding field (`#embedding`). Only sparse vector embeddings can be stored in metadata, and they must be stored consistently under the same key across all documents. Additionally, only one sparse vector index is allowed per collection in metadata. </Warning> <Callout> Support for multiple dense embedding fields and multiple sparse vector indices is coming in a future release. This will allow you to store and query multiple embeddings per document, with optimized indexing for each field. </Callout>

Arithmetic Operations

Supported operators:

  • + - Addition
  • - - Subtraction
  • * - Multiplication
  • / - Division
  • - (unary) - Negation

Combine ranking expressions using arithmetic operators. Operator precedence follows Python's standard rules.

<CodeGroup> ```python Python # Weighted combination of two searches text_score = Knn(query="machine learning research") sparse_q = {"indices": [1, 5, 10], "values": [0.5, 0.3, 0.8]} sparse_score = Knn(query=sparse_q, key="sparse_embedding") combined = text_score * 0.7 + sparse_score * 0.3

Scaling scores

normalized = Knn(query="quantum computing") / 100.0

Adding baseline score

with_baseline = Knn(query="artificial intelligence") + 0.5

Complex expressions (use parentheses for clarity)

final_score = (Knn(query="deep learning") * 0.5 + Knn(query="neural networks") * 0.3) / 1.8


```typescript TypeScript
// Weighted combination of two searches
const textScore = Knn({ query: "machine learning research" });
const sparseQ = { indices: [1, 5, 10], values: [0.5, 0.3, 0.8] };
const sparseScore = Knn({ query: sparseQ, key: "sparse_embedding" });
const combined = textScore.multiply(0.7).add(sparseScore.multiply(0.3));

// Scaling scores
const normalized = Knn({ query: "quantum computing" }).divide(100.0);

// Adding baseline score
const withBaseline = Knn({ query: "artificial intelligence" }).add(0.5);

// Complex expressions (use chaining for clarity)
const finalScore = Knn({ query: "deep learning" }).multiply(0.5)
  .add(Knn({ query: "neural networks" }).multiply(0.3))
  .divide(1.8);
</CodeGroup> <Callout> Numbers in expressions are automatically converted to `Val` constants. For example, `Knn(query=v) * 0.5` is equivalent to `Knn(query=v) * Val(0.5)`. </Callout>

Mathematical Functions

Supported functions:

  • exp() - Exponential (e^x)
  • log() - Natural logarithm
  • abs() - Absolute value
  • min() - Minimum of two values
  • max() - Maximum of two values
<CodeGroup> ```python Python # Exponential - amplifies differences between scores score = Knn(query="machine learning").exp()

Logarithm - compresses score range

Add constant to avoid log(0)

compressed = (Knn(query="deep learning") + 1).log()

Absolute value - useful for difference calculations

diff = abs(Knn(query="neural networks") - Knn(query="machine learning"))

Clamping scores to a range

score = Knn(query="artificial intelligence") clamped = score.min(0.0).max(1.0) # Clamp to [0, 1]

Ensuring non-negative scores

positive_only = Knn(query="quantum computing").min(0.0)


```typescript TypeScript
// Exponential - amplifies differences between scores
const score = Knn({ query: "machine learning" }).exp();

// Logarithm - compresses score range
// Add constant to avoid log(0)
const compressed = Knn({ query: "deep learning" }).add(1).log();

// Absolute value - useful for difference calculations
const diff = Knn({ query: "neural networks" }).subtract(Knn({ query: "machine learning" })).abs();

// Clamping scores to a range
const score2 = Knn({ query: "artificial intelligence" });
const clamped = score2.min(0.0).max(1.0);  // Clamp to [0, 1]

// Ensuring non-negative scores
const positiveOnly = Knn({ query: "quantum computing" }).min(0.0);
</CodeGroup>

Val for Constant Values

The Val class represents constant values in ranking expressions. Numbers are automatically converted to Val, but you can use it explicitly for clarity.

<CodeGroup> ```python Python from chromadb import Val

Automatic conversion (these are equivalent)

score1 = Knn(query="machine learning") * 0.5 score2 = Knn(query="machine learning") * Val(0.5)

Explicit Val for named constants

baseline = Val(0.1) boost_factor = Val(2.0) final_score = (Knn(query="artificial intelligence") + baseline) * boost_factor

Using Val in complex expressions

threshold = Val(0.8) penalty = Val(0.5) adjusted = Knn(query="deep learning").max(threshold) - penalty


```typescript TypeScript
import { Val, Knn } from 'chromadb';

// Automatic conversion (these are equivalent)
const score1 = Knn({ query: "machine learning" }).multiply(0.5);
const score2 = Knn({ query: "machine learning" }).multiply(Val(0.5));

// Explicit Val for named constants
const baseline = Val(0.1);
const boostFactor = Val(2.0);
const finalScore = Knn({ query: "artificial intelligence" }).add(baseline).multiply(boostFactor);

// Using Val in complex expressions
const threshold = Val(0.8);
const penalty = Val(0.5);
const adjusted = Knn({ query: "deep learning" }).max(threshold).subtract(penalty);
</CodeGroup>

Combining Ranking Expressions

You can combine multiple Knn searches using arithmetic operations for custom scoring strategies.

<CodeGroup> ```python Python # Linear combination - weighted average of different searches dense_score = Knn(query="machine learning applications") sparse_score = Knn(query="machine learning applications", key="sparse_embedding") combined = dense_score * 0.8 + sparse_score * 0.2

Multi-query search - combining different perspectives

general_score = Knn(query="artificial intelligence overview") specific_score = Knn(query="neural network architectures") multi_query = general_score * 0.4 + specific_score * 0.6

Boosting with constant

base_score = Knn(query="quantum computing")

Note: K("boost") would need to be part of select() to use in ranking

final_score = base_score * (1 + Val(0.1)) # Fixed 10% boost


```typescript TypeScript
// Linear combination - weighted average of different searches
const denseScore = Knn({ query: "machine learning applications" });
const sparseScore = Knn({ query: "machine learning applications", key: "sparse_embedding" });
const combined = denseScore.multiply(0.8).add(sparseScore.multiply(0.2));

// Multi-query search - combining different perspectives
const generalScore = Knn({ query: "artificial intelligence overview" });
const specificScore = Knn({ query: "neural network architectures" });
const multiQuery = generalScore.multiply(0.4).add(specificScore.multiply(0.6));

// Boosting with constant
const baseScore = Knn({ query: "quantum computing" });
// Note: K("boost") would need to be part of select() to use in ranking
const finalScore = baseScore.multiply(Val(1).add(Val(0.1)));  // Fixed 10% boost
</CodeGroup> <Callout> For advanced hybrid search combining multiple ranking strategies, consider using [RRF (Reciprocal Rank Fusion)](./hybrid-search) which is specifically designed for this purpose. </Callout>

Understanding Scores

  • Lower scores = better matches - Chroma uses distance-based scoring
  • Score range - Depends on your embedding model and distance metric
  • No ranking - When rank=None, results are returned in natural storage order
  • Distance vs similarity - Scores represent distance; for similarity, use 1 - score (for normalized embeddings)

Edge Cases and Important Behavior

Default Ranking

When no ranking is specified (rank=None), results are returned in index order (typically insertion order). This is useful when you only need filtering without scoring.

<CodeGroup> ```python Python # No ranking - results in index order search = Search().where(K("status") == "active").limit(10) # Score for each document is simply its index position ```
typescript
// No ranking - results in index order
const search = new Search().where(K("status").eq("active")).limit(10);
// Score for each document is simply its index position
</CodeGroup>

Combining Knn Expressions with default=None

Documents must appear in at least one Knn's results to be candidates, AND must appear in ALL Knn results where default=None.

<CodeGroup> ```python Python # Problem: Restrictive filtering with default=None rank = Knn(query="machine learning", limit=100) * 0.7 + Knn(query="deep learning", limit=100) * 0.3 # Both have default=None # Only documents in BOTH top-100 lists get scored

Solution: Set default values for more inclusive results

rank = ( Knn(query="machine learning", limit=100, default=10.0) * 0.7 + Knn(query="deep learning", limit=100, default=10.0) * 0.3 )

Now documents in either top-100 list can be scored

Documents get default score (10.0) for Knn where they don't appear


```typescript TypeScript
// Problem: Restrictive filtering with default undefined
const rank1 = Knn({ query: "machine learning", limit: 100 }).multiply(0.7)
  .add(Knn({ query: "deep learning", limit: 100 }).multiply(0.3));
// Both have default undefined
// Only documents in BOTH top-100 lists get scored

// Solution: Set default values for more inclusive results
const rank2 = Knn({ query: "machine learning", limit: 100, default: 10.0 }).multiply(0.7)
  .add(Knn({ query: "deep learning", limit: 100, default: 10.0 }).multiply(0.3));
// Now documents in either top-100 list can be scored
// Documents get default score (10.0) for Knn where they don't appear
</CodeGroup>

Vector Dimension Mismatch

Query vectors must match the dimension of the indexed embeddings. Mismatched dimensions will result in an error.

<CodeGroup> ```python Python # If your embeddings are 384-dimensional Knn(query=[0.1, 0.2, 0.3]) # Error - only 3 dimensions Knn(query=[0.1] * 384) # Correct - 384 dimensions ```
typescript
// If your embeddings are 384-dimensional
Knn({ query: [0.1, 0.2, 0.3] });         // Error - only 3 dimensions
Knn({ query: Array(384).fill(0.1) });   // Correct - 384 dimensions
</CodeGroup>

The return_rank Parameter

Set return_rank=True when using Knn with RRF to get rank positions (0, 1, 2...) instead of distances.

<CodeGroup> ```python Python # For regular scoring - use distances Knn(query="machine learning") # Returns: 0.23, 0.45, 0.67...

For RRF - use rank positions

Knn(query="machine learning", return_rank=True) # Returns: 0, 1, 2...


```typescript TypeScript
// For regular scoring - use distances
Knn({ query: "machine learning" });  // Returns: 0.23, 0.45, 0.67...

// For RRF - use rank positions
Knn({ query: "machine learning", returnRank: true });  // Returns: 0, 1, 2...
</CodeGroup>

The limit Parameter

The limit parameter in Knn controls how many candidates are considered, not the final result count. Use Search.limit() to control the number of results returned.

<CodeGroup> ```python Python # Knn.limit - candidates to consider for scoring rank = Knn(query="artificial intelligence", limit=1000) # Score top 1000 candidates

Search.limit - results to return

search = Search().rank(rank).limit(10) # Return top 10 results


```typescript TypeScript
// Knn.limit - candidates to consider for scoring
const rank = Knn({ query: "artificial intelligence", limit: 1000 });  // Score top 1000 candidates

// Search.limit - results to return
const search = new Search().rank(rank).limit(10);  // Return top 10 results
</CodeGroup>

Complete Example

Here's a practical example combining different ranking features:

<CodeGroup> ```python Python from chromadb import Search, K, Knn, Val

Complex ranking with filtering and mathematical functions

search = (Search() .where( (K("status") == "published") & (K("category").is_in(["tech", "science"])) ) .rank( # Combine two queries with weights ( Knn(query="latest AI research developments") * 0.7 + Knn(query="artificial intelligence breakthroughs") * 0.3 ).exp() # Amplify score differences .min(0.0) # Ensure non-negative ) .limit(20) .select(K.DOCUMENT, K.SCORE, "title", "category") )

results = collection.search(search)

Process results using rows() for cleaner access

rows = results.rows()[0] # Get first (and only) search results for i, row in enumerate(rows): print(f"{i+1}. {row['metadata']['title']}") print(f" Score: {row['score']:.3f}") print(f" Category: {row['metadata']['category']}") print(f" Preview: {row['document'][:100]}...") print()


```typescript TypeScript
import { Search, K, Knn, Val } from 'chromadb';

// Complex ranking with filtering and mathematical functions
const search = new Search()
  .where(
    K("status").eq("published")
      .and(K("category").isIn(["tech", "science"]))
  )
  .rank(
    // Combine two queries with weights
    Knn({ query: "latest AI research developments" }).multiply(0.7)
      .add(Knn({ query: "artificial intelligence breakthroughs" }).multiply(0.3))
      .exp()  // Amplify score differences
      .min(0.0)  // Ensure non-negative
  )
  .limit(20)
  .select(K.DOCUMENT, K.SCORE, "title", "category");

const results = await collection.search(search);

// Process results using rows() for cleaner access
const rows = results.rows()[0];  // Get first (and only) search results
for (const [i, row] of rows.entries()) {
  console.log(`${i+1}. ${row.metadata?.title}`);
  console.log(`   Score: ${row.score?.toFixed(3)}`);
  console.log(`   Category: ${row.metadata?.category}`);
  console.log(`   Preview: ${row.document?.substring(0, 100)}...`);
  console.log();
}
</CodeGroup>

Tips and Best Practices

  • Normalize your vectors - Ensure consistent scoring by normalizing query vectors
  • Use appropriate limit values - Higher limits in Knn mean more accurate but slower results
  • Set return_rank=True for RRF - Essential when using Reciprocal Rank Fusion
  • Test score ranges - Understand your model's typical score ranges for better thresholding
  • Combine strategies wisely - Linear combinations work well for similar score ranges

Next Steps