Learn ChromaDB - Full-Text & Hybrid Search
Episode 8 of 23

Learn ChromaDB - Full-Text & Hybrid Search

This episode covers full-text search for precise keyword matching and relevance scoring, then hybrid search strategies that combine vector, full-text, and metadata, including fusion and rerank approaches for accurate RAG retrieval.

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

Introduction

Episode 7 introduced where_document as a simple text filter. Now it's time to take it further: full-text search and hybrid search. This is where ChromaDB proves its claim as "search infrastructure for AI" — not just a vector database, but an engine that combines four types of search at once.

We will discuss when full-text outperforms semantic search, how to combine both, relevance scoring, and the fusion and rerank strategies that make RAG accurate. Many teams assume semantic search is always the best — this episode will correct that assumption.

When Full-Text Outperforms Semantic

Semantic search excels at capturing meaning but fails in cases that require character-level precision. Real examples: error codes, product names, software versions, or UUIDs. A query like "error code E404" could semantically pull in documents about other errors with similar meaning — when what you need is exactly E404.

PythonKasus di mana full-text menang
hasil = collection.query(
    query_texts=["error E404"],
    n_results=5,
    where_document={"$contains": "E404"},
)

where_document={"$contains": "E404"} ensures only documents that truly contain the string E404 are returned. Character-level precision like this cannot be guaranteed by semantic search.

Two Complementary Worlds

Let's put both side by side:

CharacteristicSemantic SearchFull-Text Search
BasisMeaning (embedding)Exact words
Strong atParaphrase, synonymsCodes, names, versions
Weak atCharacter precisionMeaning, synonyms
SpeedDepends on indexVery fast

Full-text in ChromaDB searches for words in documents, and results are filtered by the presence of those words. For RAG serving diverse questions, both must run together — and that is exactly hybrid search.

Hybrid Search: Combining Two Worlds

Basic Concepts

Hybrid search runs vector and full-text searches against the same query, then combines the results. ChromaDB makes this easy: you can run query with where_document, or run two separate searches and combine them in your application.

The most common approach in ChromaDB:

PythonPencarian terpisah lalu digabung
hasil_vector = collection.query(
    query_texts=["cara deploy aplikasi"],
    n_results=10,
)
 
hasil_fulltext = collection.query(
    query_texts=["cara deploy aplikasi"],
    n_results=10,
    where_document={"$contains": "deploy"},
)

The two collection.query(...) calls above produce two candidate sets of a different character: the first is based on vector similarity, the second is constrained to the word "deploy". Your job is to combine them into one final list.

Metadata Filtering as the Third Dimension

Hybrid actually has three dimensions in ChromaDB: vector, full-text, and metadata. The metadata filter from episode 7 can be added to either search above:

PythonHybrid dengan metadata
hasil = collection.query(
    query_texts=["deploy"],
    n_results=10,
    where={"env": "production"},
    where_document={"$contains": "Docker"},
)

collection.query(query_texts=["deploy"], where={"env": "production"}, where_document={"$contains": "Docker"}) runs a semantic search constrained to the production environment and documents containing the word Docker. Three filters working in a single call.

Info

It is important to understand: where_document in ChromaDB is a strict filter — results that do not contain the word are discarded. It is not a continuous scoring search like BM25. For full relevance scoring, combine results manually or use a fusion tool.

Fusion and Rerank Strategies

Combining Two Rankings

The main hybrid problem: two result lists with two different scales cannot be mixed directly. The classic solution is Reciprocal Rank Fusion (RRF): each document is scored based on its position in each list.

PythonReciprocal Rank Fusion sederhana
def rrf(daftar_dok, k=60):
    skor = {}
    for daftar in daftar_dok:
        for posisi, dok in enumerate(daftar):
            skor[dok] = skor.get(dok, 0) + 1 / (k + posisi + 1)
    return sorted(skor, key=skor.get, reverse=True)
 
final = rrf([hasil_vector["ids"][0], hasil_fulltext["ids"][0]])

The rrf([...], k=60) function gives high scores to documents that appear near the top of both lists. This is a simple fusion pattern, parameter-free, and strong enough for many cases.

Reranking with a Model

For the highest accuracy, the final step is reranking with a specialized model — for example a cross-encoder that computes query-document relevance directly:

PythonRerank dengan cross-encoder
from sentence_transformers import CrossEncoder
 
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
 
kandidat = hasil_vector["documents"][0] + hasil_fulltext["documents"][0]
skor = reranker.predict([(query, d) for d in kandidat])

reranker.predict([(query, d) for d in kandidat]) returns a relevance score for each query-document pair. Sort descending, take the top-N, and you have results far more accurate than relying on embeddings alone.

Summarizing this episode into one production pattern:

  1. Run a semantic query (vector-only) retrieving 10-20 results.
  2. Run a full-text query with where_document retrieving parallel results.
  3. Combine with RRF or a simple combination.
  4. Rerank the top results with a cross-encoder.
  5. Provide the final documents as context to the LLM.

This pattern balances recall (semantic captures meaning, full-text captures precision) and precision (rerank filters out what is truly irrelevant). You will see the full implementation in episode 18 when we cover LLM framework integration.

Closing

Episode 8 raised your retrieval level: understanding when full-text beats semantic, running hybrid search with three dimensions (vector, full-text, metadata), combining rankings with RRF, and refining results with cross-encoder reranking. ChromaDB is not just a vector store — it is a search engine.

Key takeaways:

  • Full-text wins for character precision: codes, names, versions.
  • Semantic wins for meaning and paraphrase; the two complement each other.
  • Hybrid search in ChromaDB = vector + where_document + where.
  • RRF combines result lists without normalizing score scales.
  • Cross-encoder reranking measurably improves RAG precision.
  • Production pattern: query → filter → fusion → rerank → LLM context.

In the next episode, episode 9, we will discuss distance metrics and the HNSW index — the differences between L2, cosine, and inner product, choosing a metric to match your embedding model, and tuning the ef_construction, M, and search ef parameters with the recall-versus-latency-and-memory trade-off. This is where retrieval performance starts to be tuned.

Learn ChromaDB - Full-Text & Hybrid Search | Learn ChromaDB