Chroma provides powerful querying capabilities to find relevant documents using semantic similarity and metadata filtering.
Query vs Get
Chroma has two main retrieval methods:
Query: Similarity Search
query() finds the most similar records using vector similarity:
results = collection.query(
query_texts = [ "artificial intelligence" ],
n_results = 10
)
Use query when you want to find semantically similar documents.
Get: Direct Retrieval
get() retrieves specific records by ID or metadata filters:
results = collection.get(
ids = [ "doc1" , "doc2" ],
where = { "category" : "news" }
)
Use get when you know which documents you want or need to filter without similarity.
Basic Query
Find similar documents using text:
results = collection.query(
query_texts = [ "machine learning algorithms" ],
n_results = 5
)
print (results[ 'documents' ]) # The matching documents
print (results[ 'distances' ]) # Similarity distances
print (results[ 'metadatas' ]) # Metadata for each result
Query Parameters
You can query with different input types:
# Query with text (automatically embedded)
results = collection.query(
query_texts = [ "search query" ],
n_results = 10
)
# Query with pre-computed embeddings
results = collection.query(
query_embeddings = [[ 0.1 , 0.2 , 0.3 , ... ]],
n_results = 10
)
# Query with images
import numpy as np
from PIL import Image
image = np.array(Image.open( "photo.jpg" ))
results = collection.query(
query_images = [image],
n_results = 10
)
# Query with URIs
results = collection.query(
query_uris = [ "https://example.com/image.jpg" ],
n_results = 10
)
Provide exactly one query input type per call. Don’t mix query_texts, query_embeddings, query_images, or query_uris.
Number of Results
Control how many results to return:
results = collection.query(
query_texts = [ "search query" ],
n_results = 20 # Return top 20 most similar
)
Including Fields
Specify which fields to include in results:
results = collection.query(
query_texts = [ "search query" ],
n_results = 10 ,
include = [ "documents" , "metadatas" , "distances" , "embeddings" ]
)
Available fields:
documents: The document text
metadatas: Metadata for each record
distances: Similarity distances
embeddings: The embedding vectors
uris: URIs if stored
Default: ["documents", "metadatas", "distances"]
Batch Queries
Query multiple queries at once:
results = collection.query(
query_texts = [
"machine learning" ,
"deep learning" ,
"neural networks"
],
n_results = 5
)
# Results are batched - one result set per query
for i, (ids, docs, distances) in enumerate (
zip (results[ 'ids' ], results[ 'documents' ], results[ 'distances' ])
):
print ( f "Query { i } : { docs } " )
Filtering Queries
Combine similarity search with metadata filtering:
results = collection.query(
query_texts = [ "artificial intelligence" ],
n_results = 10 ,
where = { "category" : "research" , "year" : { "$gte" : 2020 }}
)
This finds similar documents that also match the metadata criteria.
Document Filters
results = collection.query(
query_texts = [ "neural networks" ],
n_results = 10 ,
where_document = { "$contains" : "tensorflow" }
)
Combined Filters
results = collection.query(
query_texts = [ "machine learning" ],
n_results = 10 ,
where = {
"$and" : [
{ "category" : "tutorial" },
{ "difficulty" : { "$in" : [ "beginner" , "intermediate" ]}}
]
},
where_document = { "$contains" : "python" }
)
Filter by IDs
Restrict search to specific IDs:
results = collection.query(
query_texts = [ "search query" ],
ids = [ "doc1" , "doc2" , "doc5" ], # Only search these documents
n_results = 2
)
Query results are returned in a batched, columnar format:
results = collection.query(
query_texts = [ "query1" , "query2" ],
n_results = 3
)
# Results structure
results = {
'ids' : [ # List of result lists (one per query)
[ 'id1' , 'id2' , 'id3' ], # Results for query1
[ 'id4' , 'id5' , 'id6' ] # Results for query2
],
'documents' : [
[ 'doc1' , 'doc2' , 'doc3' ],
[ 'doc4' , 'doc5' , 'doc6' ]
],
'distances' : [
[ 0.1 , 0.2 , 0.3 ],
[ 0.15 , 0.25 , 0.35 ]
],
'metadatas' : [
[{ ... }, { ... }, { ... }],
[{ ... }, { ... }, { ... }]
],
'included' : [ 'documents' , 'metadatas' , 'distances' ]
}
Iterate over results:
# Process each query's results
for batch_ids, batch_docs, batch_dists in zip (
results[ 'ids' ], results[ 'documents' ], results[ 'distances' ]
):
# Process each result in the batch
for id , doc, dist in zip (batch_ids, batch_docs, batch_dists):
print ( f "ID: { id } , Distance: { dist } " )
print ( f "Document: { doc } " )
Get Operations
Retrieve documents directly without similarity search:
Get by IDs
results = collection.get(
ids = [ "doc1" , "doc2" , "doc3" ]
)
Get with Filters
results = collection.get(
where = { "category" : "news" },
limit = 100 ,
offset = 0
)
# Get first 50 documents
results = collection.get(
limit = 50 ,
offset = 0
)
# Get next 50 documents
results = collection.get(
limit = 50 ,
offset = 50
)
Get results are in columnar format (no batching):
results = {
'ids' : [ 'id1' , 'id2' , 'id3' ],
'documents' : [ 'doc1' , 'doc2' , 'doc3' ],
'metadatas' : [{ ... }, { ... }, { ... }],
'embeddings' : None , # Unless included
'included' : [ 'documents' , 'metadatas' ]
}
The Search API (Advanced)
The Search API provides a more expressive query language for hybrid search, combining vector similarity with complex filtering and ranking.
The Search API is experimental and only available in distributed and hosted Chroma.
Basic Search
from chromadb.execution.expression import Search, K, Knn
search = Search().where(
K( "category" ) == "science"
).rank(
Knn( query = [ 0.1 , 0.2 , 0.3 ])
).limit( 10 )
results = collection.search(search)
Key Components
The Search API uses:
K or Key : Reference to fields (metadata or special fields)
where() : Filter conditions
rank() : Ranking expression (e.g., KNN, RRF)
limit() : Pagination
select() : Choose which fields to return
Where Expressions
Build complex filters:
from chromadb.execution.expression import K
# Simple equality
search = Search().where(K( "category" ) == "science" )
# Comparison operators
search = Search().where(K( "score" ) > 0.5 )
# Logical combinations
search = Search().where(
(K( "category" ) == "science" ) & (K( "score" ) > 0.5 )
)
# Complex logic
search = Search().where(
(K( "category" ) == "science" ) &
((K( "score" ) > 0.8 ) | (K( "priority" ) == "high" ))
)
Ranking Expressions
Combine multiple ranking strategies:
from chromadb.execution.expression import Knn, Val
# Simple KNN
search = Search().rank(Knn( query = [ 0.1 , 0.2 , 0.3 ]))
# Weighted hybrid ranking
search = Search().rank(
Knn( query = [ 0.1 , 0.2 , 0.3 ]) * 0.7 + Val( 0.5 ) * 0.3
)
# Reciprocal Rank Fusion
from chromadb.execution.expression import Rrf
search = Search().rank(
Rrf([
Knn( query = [ 0.1 , 0.2 ], return_rank = True ),
Knn( query = [ 0.3 , 0.4 ], key = "sparse_embedding" , return_rank = True )
])
)
Select Fields
search = Search().select(
K. DOCUMENT , K. SCORE , "title" , "author"
)
Predefined keys:
K.DOCUMENT: Document text
K.EMBEDDING: Embedding vector
K.METADATA: All metadata
K.SCORE: Similarity score
K.ID: Record ID
Group By
Group results by metadata fields:
from chromadb.execution.expression import GroupBy, MinK
# Top 3 per category
search = Search().group_by(
GroupBy(
keys = K( "category" ),
aggregate = MinK( keys = K. SCORE , k = 3 )
)
)
Read Levels
Control whether to read from the write-ahead log:
from chromadb.api.types import ReadLevel
# Default: read from index + WAL (all writes visible)
results = collection.search(search)
# Faster: skip WAL (recent writes may not be visible)
results = collection.search(
search,
read_level = ReadLevel. INDEX_ONLY
)
Search returns column-major results:
results = collection.search([search1, search2])
# Access results
results[ 'ids' ] # List[List[str]]
results[ 'documents' ] # List[Optional[List[Optional[str]]]]
results[ 'scores' ] # List[Optional[List[Optional[float]]]]
# Convert to row format
for payload_rows in results.rows():
for row in payload_rows:
print (row[ 'id' ])
print (row[ 'document' ])
print (row[ 'score' ])
Best Practices
Choose appropriate n_results
Balance between performance and recall: # Good - reasonable number of results
results = collection.query(
query_texts = [ "query" ],
n_results = 10 # Fast and usually sufficient
)
# Be careful - very large n_results
results = collection.query(
query_texts = [ "query" ],
n_results = 1000 # Slower, use only if needed
)
Use filters to narrow search space
Combine similarity with metadata filters: # More efficient - filter first
results = collection.query(
query_texts = [ "query" ],
n_results = 10 ,
where = { "year" : { "$gte" : 2020 }} # Narrows search space
)
Batch queries when possible
Process multiple queries in one call: # Efficient - single batched call
results = collection.query(
query_texts = [ "query1" , "query2" , "query3" ],
n_results = 10
)
# Avoid - multiple separate calls
# results1 = collection.query(query_texts=["query1"], ...)
# results2 = collection.query(query_texts=["query2"], ...)
Only include needed fields
Reduce data transfer by including only required fields: # Efficient - only needed fields
results = collection.query(
query_texts = [ "query" ],
n_results = 10 ,
include = [ "documents" , "distances" ] # Skip embeddings
)
Next Steps
Metadata Learn about metadata filtering
Embeddings Understand how embeddings work
Performance Optimize query performance
Filtering Guide Advanced filtering techniques