Common questions about this section
  • How do I combine full-text and semantic search in AntflyDB?
  • What merge strategies are available for hybrid search?
  • How do I use reranking to improve search relevance?
  • How do I prune low-quality results from search results?
  • When should I use hybrid search vs full-text or semantic search alone?

Overview#

Hybrid search combines two complementary retrieval methods:

  • Full-text search (BM25) excels at exact keyword matching. It finds documents containing specific terms, phrases, and field-scoped queries. But it misses synonyms and paraphrases entirely -- searching for "automobile" won't find documents about "cars."

  • Semantic search (vector similarity) understands meaning. It finds conceptually related documents even when they use different words. But it can miss exact matches that a keyword search would catch instantly.

AntflyDB runs both searches in parallel and fuses the results into a single ranked list. You get the precision of keyword matching and the recall of semantic understanding in one query.

Getting Started#

This guide assumes you have AntflyDB running with the Wikipedia dataset from the quickstart guide. If you haven't set that up yet, start there first.

Your First Hybrid Query#

A hybrid query uses both --full-text-search and --semantic-search together:

antfly query --table wikipedia \
  --full-text-search 'body:Einstein' \
  --semantic-search "theory of relativity and physics" \
  --indexes "title_body" \
  --fields "title,url" \
  --limit 10

The same query via the REST API:

curl -X POST http://localhost:8080/v1/query \
  -H "Content-Type: application/json" \
  -d '{
    "table": "wikipedia",
    "full_text_search": {"query": "body:Einstein"},
    "semantic_search": "theory of relativity and physics",
    "indexes": ["title_body"],
    "fields": ["title", "url"],
    "limit": 10
  }'

AntflyDB runs both searches simultaneously, then merges the results using Reciprocal Rank Fusion (the default strategy).

Merge Strategies#

When both full-text and semantic results come back, AntflyDB needs to combine them into a single ranked list. The merge_strategy field controls how this happens.

RRF (Reciprocal Rank Fusion) -- Default#

RRF scores each result based on its rank position across both result sets:

score = sum(1 / (k + rank))  for each source where the result appears

The constant k is set to 60. A result ranked #1 in both lists gets 1/61 + 1/61 = 0.0328. A result ranked #1 in one list and #10 in the other gets 1/61 + 1/70 = 0.0307.

RRF ignores raw scores entirely -- only rank positions matter. This makes it robust across different scoring scales without any tuning. It is the right choice for most use cases.

The tradeoff: because RRF compresses all scores into a narrow range, it can make it harder to distinguish high-relevance results from marginal ones when pruning.

RSF (Relative Score Fusion)#

RSF normalizes raw scores from each source using min-max normalization, then combines them:

normalized = (score - min) / (max - min)  within a sliding window

Because RSF preserves score magnitude information, it produces a wider spread of fused scores. This makes it a better fit when you plan to use pruning to filter out low-quality results.

curl -X POST http://localhost:8080/v1/query \
  -H "Content-Type: application/json" \
  -d '{
    "table": "wikipedia",
    "full_text_search": {"query": "body:Einstein"},
    "semantic_search": "theory of relativity",
    "indexes": ["title_body"],
    "merge_strategy": "rsf",
    "limit": 10
  }'

Failover#

Failover is a reliability strategy: it uses semantic search results when available, and falls back to full-text search if embedding generation fails.

curl -X POST http://localhost:8080/v1/query \
  -H "Content-Type: application/json" \
  -d '{
    "table": "wikipedia",
    "full_text_search": {"query": "body:Einstein"},
    "semantic_search": "theory of relativity",
    "indexes": ["title_body"],
    "merge_strategy": "failover",
    "limit": 10
  }'

This is useful in production when your embedding service (Termite, Ollama, or an external provider) might be temporarily unavailable. Your queries will always return results.

Reranking#

After fusion, results are ranked but not perfectly ordered. A cross-encoder reranker can re-score each result by looking at the full query-document pair, producing significantly more accurate relevance scores.

antfly query --table wikipedia \
  --full-text-search 'body:Einstein' \
  --semantic-search "theory of relativity and physics" \
  --indexes "title_body" \
  --fields "title,url" \
  --limit 50 \
  --reranker '{
    "provider": "antfly",
    "field": "body"
  }'

The field parameter tells the reranker which document field to score against the query. The built-in antfly reranker uses a cross-encoder model bundled in the binary. For larger or custom models, use "provider": "termite" with a model like cross-encoder/ms-marco-MiniLM-L-6-v2.

Pruning#

Pruning filters out low-quality results after fusion (and after reranking, if configured). Instead of returning a fixed number of results, pruning returns only the results that meet a quality threshold.

This is especially valuable for RAG pipelines, where sending marginally relevant documents to an LLM adds noise and degrades answer quality.

Pruning Strategies#

You can combine multiple strategies in a single pruner configuration:

min_score_ratio -- Keep results scoring at least X% of the top result.

{"min_score_ratio": 0.5}

A value of 0.5 keeps results with at least half the score of the best result.

max_score_gap_percent -- Stop when the score drops too sharply from one result to the next.

{"max_score_gap_percent": 30}

This detects "elbows" in the score distribution. A value of 30 stops returning results when a score drops more than 30% from the previous result.

min_absolute_score -- Hard floor on score values.

{"min_absolute_score": 0.01}

std_dev_threshold -- Statistical outlier removal. Keeps results within N standard deviations below the mean.

{"std_dev_threshold": 1.5}

require_multi_index -- Only keep results that appear in both full-text and vector results.

{"require_multi_index": true}

Example with Pruning#

antfly query --table wikipedia \
  --full-text-search 'body:Einstein' \
  --semantic-search "theory of relativity and physics" \
  --indexes "title_body" \
  --fields "title,url" \
  --limit 50 \
  --pruner '{"min_score_ratio": 0.5, "max_score_gap_percent": 30}'

Putting It All Together#

Here is a complete hybrid search pipeline that combines all the pieces: full-text search, semantic search, merge strategy, reranking, and pruning.

curl -X POST http://localhost:8080/v1/query \
  -H "Content-Type: application/json" \
  -d '{
    "table": "wikipedia",
    "full_text_search": {"query": "body:Einstein"},
    "semantic_search": "theory of relativity and physics",
    "indexes": ["title_body"],
    "fields": ["title", "body", "url"],
    "merge_strategy": "rsf",
    "limit": 50,
    "reranker": {
      "provider": "antfly",
      "field": "body"
    },
    "pruner": {
      "min_score_ratio": 0.5,
      "max_score_gap_percent": 40
    }
  }'

This query:

  1. Runs a BM25 keyword search for "Einstein" in the body field
  2. Runs a semantic similarity search for "theory of relativity and physics"
  3. Fuses results using RSF (preserving score magnitude)
  4. Reranks the top 50 candidates with a cross-encoder model
  5. Prunes results below 50% of the top score or where the score drops sharply

Feeding Into RAG#

The same pipeline works with antfly agents retrieval to generate AI-powered answers from the search results:

antfly agents retrieval --table wikipedia \
  --full-text-search 'body:Einstein' \
  --semantic-search "What is the theory of relativity?" \
  --indexes "title_body" \
  --fields "title,body" \
  --limit 50 \
  --reranker '{
    "provider": "antfly",
    "field": "body"
  }' \
  --pruner '{"min_score_ratio": 0.6, "max_score_gap_percent": 40}' \
  --generator '{
    "provider": "ollama",
    "ollama": {
      "model": "gemma3:4b-it-qat",
      "url": "http://localhost:11434"
    }
  }' \
  --system-prompt "Answer the question based on the provided context."

See the quickstart guide for more on setting up RAG with Ollama.

When to Use What#

ScenarioApproach
Users search for exact terms, IDs, error messages, or codesFull-text search only
Users describe what they want in natural languageSemantic search only
General-purpose search with no tuning neededHybrid with RRF (default)
Need score-aware fusion, especially with pruningHybrid with RSF
Embedding service might be unavailableHybrid with failover
High-precision use cases (RAG, question answering)Hybrid + reranking
Want to automatically filter irrelevant resultsHybrid + pruning
Production RAG pipelineHybrid + reranking + pruning

Additional Resources#