docs/mintlify/cloud/search-api/ranking.mdx
import { Callout, Warning } from '/snippets/callout.mdx';
A ranking expression determines which documents are scored and how they're ordered:
No ranking (rank=None): Documents are returned in index order (typically insertion order)
With ranking expression:
Knn expressionKnn's top-k results to be consideredKnn results where default=NoneKnn with a default value get that default scoreKnn considers its top limit candidates (default: 16)Search.limit()rank = Knn(query="research papers", limit=100) + Knn(query="academic publications", limit=100, key="sparse_embedding")
rank = Knn(query="AI research", limit=100) * 0.5 + Knn(query="scientific papers", limit=50, default=1000.0, key="sparse_embedding") * 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
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,
};
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.
Knn(query="What is machine learning?")
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) )
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" });
| Parameter | Type | Default | Description |
|---|---|---|---|
query | str, List[float], SparseVector, or np.ndarray | Required | The query text or vector to search with |
key | str | "#embedding" | Field to search - "#embedding" for dense embeddings, or a metadata field name for sparse embeddings |
limit | int | 16 | Maximum number of candidates to consider |
default | float or None | None | Score for documents not in KNN results |
return_rank | bool | False | If True, return rank position (0, 1, 2...) instead of distance |
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?" });
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 });
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" });
Chroma currently supports:
"#embedding" or K.EMBEDDING)Knn(query="machine learning", key="sparse_embedding") # Search sparse embeddings in metadata
```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
Supported operators:
+ - Addition- - Subtraction* - Multiplication/ - Division- (unary) - NegationCombine 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.3normalized = Knn(query="quantum computing") / 100.0
with_baseline = Knn(query="artificial intelligence") + 0.5
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);
Supported functions:
exp() - Exponential (e^x)log() - Natural logarithmabs() - Absolute valuemin() - Minimum of two valuesmax() - Maximum of two valuescompressed = (Knn(query="deep learning") + 1).log()
diff = abs(Knn(query="neural networks") - Knn(query="machine learning"))
score = Knn(query="artificial intelligence") clamped = score.min(0.0).max(1.0) # Clamp to [0, 1]
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);
The Val class represents constant values in ranking expressions. Numbers are automatically converted to Val, but you can use it explicitly for clarity.
score1 = Knn(query="machine learning") * 0.5 score2 = Knn(query="machine learning") * Val(0.5)
baseline = Val(0.1) boost_factor = Val(2.0) final_score = (Knn(query="artificial intelligence") + baseline) * boost_factor
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);
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.2general_score = Knn(query="artificial intelligence overview") specific_score = Knn(query="neural network architectures") multi_query = general_score * 0.4 + specific_score * 0.6
base_score = Knn(query="quantum computing")
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
rank=None, results are returned in natural storage order1 - score (for normalized embeddings)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.
// 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
Documents must appear in at least one Knn's results to be candidates, AND must appear in ALL Knn results where default=None.
rank = ( Knn(query="machine learning", limit=100, default=10.0) * 0.7 + Knn(query="deep learning", limit=100, default=10.0) * 0.3 )
```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
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 ```// 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
Set return_rank=True when using Knn with RRF to get rank positions (0, 1, 2...) instead of distances.
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...
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.
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
Here's a practical example combining different ranking features:
<CodeGroup> ```python Python from chromadb import Search, K, Knn, Valsearch = (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)
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();
}