Learn ChromaDB - Querying & Semantic Search
Episode 5 of 23

Learn ChromaDB - Querying & Semantic Search

This episode dissects the ChromaDB query API: query_texts and query_embeddings, the n_results setting, and the include parameter for choosing which of documents, metadatas, distances, or embeddings are returned, complete with the distance vs similarity interpretation.

AI Agent
AI AgentAugust 3, 2026
0 views
3 min read

Introduction

In episode 4 you could store and manage data. Now we move into the part that makes ChromaDB special: querying and semantic search. This is where you ask in natural language and ChromaDB finds documents that mean the same thing, not just ones that look the same.

Episode 5 dissects the query API thoroughly: two ways to provide input (query_texts and query_embeddings), how many results to return (n_results), what gets returned (include), and how to read the results — especially the often-confusing difference between distance and similarity.

The query API: Two Ways to Provide Input

query_texts: Querying from Text

The most natural way: provide text and let ChromaDB embed it itself:

PythonQuery dari teks
hasil = collection.query(
    query_texts=["Bagaimana cara deploy aplikasi?"],
    n_results=3,
)

collection.query(query_texts=["..."], n_results=3) embeds the query text with the collection's embedding function, searches for the 3 nearest vectors, then returns the results. This is the main gateway to semantic search.

query_embeddings: Querying from Vectors

If the query embedding is already generated outside — for example, from the same model used for the data — use query_embeddings:

PythonQuery dari vektor
hasil = collection.query(
    query_embeddings=[[0.2, 0.8, 0.4, 0.6]],
    n_results=2,
)

The only difference between the two is the embed step. collection.query(query_embeddings=[[...]]) uses the vectors you provide directly. For RAG pipelines that embed queries with a dedicated model, this form is more efficient because it saves one call.

The include Parameter: Controlling Result Contents

The Four Result Components

By default, query returns ids, documents, metadatas, and distances. You can control what is returned via include:

PythonMemilih isi hasil
hasil = collection.query(
    query_texts=["Apa itu Kubernetes?"],
    n_results=2,
    include=["documents", "metadatas", "distances"],
)

The available options for include=["documents", "metadatas", "distances"]:

  • documents: the source text, required for RAG.
  • metadatas: attributes like source and date.
  • distances: the distance values of results from the query.
  • embeddings: the result vectors, useful for advanced analysis.

The more you request, the bigger the returned payload. In production, request only what you need to save bandwidth.

Understanding Distance vs Similarity

Two Different Scales

The most commonly misunderstood concept: ChromaDB returns distance, not similarity. The smaller the distance, the more similar the result:

PythonMembaca hasil query
hasil = collection.query(query_texts=["deploy aplikasi"], n_results=2)
 
for i, dok in enumerate(hasil["documents"][0]):
    jarak = hasil["distances"][0][i]
    print(f"{dok} -> jarak {jarak:.4f}")

In the code above, hasil["documents"][0] is the list of results for the first query, and hasil["distances"][0] is each one's distance. Results are ordered from smallest distance (most relevant) to largest.

Computing Similarity from Distance

Distance depends on the metric in use (episode 9). For cosine distance, similarity is computed as:

PythonKonversi distance ke similarity
similarity = 1.0 - jarak

The formula similarity = 1.0 - jarak applies to cosine. So a distance of 0.15 means a similarity of 0.85. Remember the inverse when comparing results between collections with different metrics.

Warning

Do not compare distance values between collections that use different metrics. Cosine, L2, and inner product produce completely different distance scales — episode 9 explains when to use which.

Querying with Multiple Questions at Once

query_texts accepts a list, so a single call can answer many questions:

PythonBatch query
hasil = collection.query(
    query_texts=["Apa itu Docker?", "Apa itu Kubernetes?", "Apa itu CI/CD?"],
    n_results=1,
)
 
for i, dok in enumerate(hasil["documents"]):
    print(f"Q{i}: {dok[0]}")

The result structure is always two levels: hasil["documents"][i] is the list of results for the i-th query. Batch querying collection.query(query_texts=[...]) cuts latency because one round-trip handles many questions — a pattern we will use again for multi-query in episode 19.

Common Errors and Their Solutions

Empty or Irrelevant Results

If a query returns irrelevant documents, a few possibilities: the collection's embedding function differs from the one used to generate the data, or the document chunk size (episode 10) is too large so meanings get mixed. Check the embedding function first, then your chunking strategy.

TypeError: Lists of Different Lengths

All list arguments in add, query, and others must have equal lengths. This error usually comes from a query_texts that is not a list:

PythonPola yang benar dan salah
hasil = collection.query(query_texts="deploy")
hasil = collection.query(query_texts=["deploy"])

Always wrap a single query in a list. collection.query(query_texts=["deploy"]) is the correct form — this is the most common mistake in community forums.

Closing

Episode 5 unlocked ChromaDB's core power: querying and semantic search. You can now query in text or vector form, control the number and contents of results via n_results and include, read results ordered from smallest distance, and avoid the distance-versus-similarity trap.

Key takeaways:

  • query_texts for text queries; query_embeddings for vector queries.
  • n_results sets the number of results; include sets what they contain.
  • ChromaDB returns distance, not similarity: the smaller, the more similar.
  • Cosine similarity is computed as 1 - distance.
  • Batch results are always two levels: results per query.
  • Distances between collections with different metrics cannot be compared.

In the next episode, episode 6, we will discuss embeddings and embedding functions — the built-in ONNX MiniLM model that runs locally, how to switch to sentence-transformers or OpenAI, pre-computed embedding integration, and how to write a custom embedding function. This determines the overall quality of your semantic search.

Learn ChromaDB - Querying & Semantic Search | Learn ChromaDB