Skip to main content
OpenGround uses hybrid search that combines semantic vector search with traditional keyword-based BM25 ranking. This approach leverages the strengths of both methods for superior retrieval accuracy.

How Hybrid Search Works

Hybrid search combines two complementary retrieval methods:
  1. Vector Search (Semantic): Finds documents with similar meaning using embeddings
  2. BM25 (Keyword): Finds documents with matching terms using statistical ranking
The results are merged using a ranking algorithm that balances both signals.

Implementation

From query.py:68-105, the core search implementation:

Key Components

  1. Query Embedding: The query text is converted to a vector using the same embedding model used for documents
  2. Hybrid Builder: LanceDB’s search(query_type="hybrid") enables hybrid mode
  3. Dual Input: Both .text(query) (for BM25) and .vector(query_vec) (for semantic) are provided
  4. Filtering: Version and library filters are applied as SQL WHERE clauses
  5. Result Limit: top_k controls the number of results returned

Vector Search Alone

Strengths:
  • Understands semantic similarity
  • Handles synonyms and paraphrases
  • Good for conceptual queries
Weaknesses:
  • Can miss exact keyword matches
  • May retrieve semantically similar but contextually wrong results
  • Sensitive to embedding model quality
Example: Query “GPU acceleration” might miss documentation that uses “CUDA” instead

BM25 Alone

Strengths:
  • Excellent for exact term matching
  • Fast and deterministic
  • Good for technical terms and code
Weaknesses:
  • No semantic understanding
  • Misses synonyms and paraphrases
  • Sensitive to term frequency
Example: Query “how to speed up indexing” won’t match “performance optimization” documentation

Hybrid Search (Best of Both)

By combining both methods, hybrid search:
  • Finds exact keyword matches (via BM25)
  • Captures semantic relevance (via vectors)
  • Provides more robust ranking
  • Reduces false negatives

Query Flow

The complete query flow:

Result Scoring

Each result includes a relevance score (query.py:117-123):
The score combines:
  • Vector distance: Lower is better (cosine distance)
  • BM25 score: Higher is better (statistical relevance)
LanceDB automatically normalizes and combines these scores.

Tuning Parameters

Top-K Results

Control the number of results returned:
Recommendations:
  • User-facing queries: 5-10 results
  • LLM context retrieval: 10-20 results
  • Comprehensive analysis: 20-50 results
Larger top_k values increase latency and token usage when passing to LLMs.

Embedding Model Selection

The embedding model affects semantic search quality:
Trade-offs:
  • Small models: Faster embedding, lower memory, slightly lower accuracy
  • Large models: Better semantic understanding, higher memory/compute cost
For most use cases, the default bge-small-en-v1.5 provides excellent quality-to-speed ratio.

Query Optimization

SQL Filtering

OpenGround applies filters using SQL WHERE clauses (query.py:98-103):
All user input is escaped using _escape_sql_string() to prevent SQL injection.
The escaping function (query.py:46-65):

Caching

Query module uses multiple caches to improve performance (query.py:12-43):
This avoids reopening database connections and table handles on each query.

Performance Considerations

Query Latency

Typical query latency breakdown:
  1. Embedding generation: 10-100ms (depends on model and backend)
  2. Hybrid search: 10-50ms (depends on index size)
  3. Result formatting: Less than 5ms
Total: 20-155ms for most queries

Optimizing Query Speed

  1. Use fastembed backend: Faster embedding generation
  2. Enable GPU: 10x faster embeddings (see GPU Acceleration)
  3. Reduce top_k: Fewer results = faster search
  4. Keep index warm: First query may be slower due to cache loading

Scaling Considerations

  • Index size: Hybrid search scales sub-linearly with document count
  • Concurrent queries: LanceDB supports concurrent reads efficiently
  • Memory usage: Keeps index in memory for fast access

Full Content Retrieval

Search returns snippets, but you can fetch complete documents (query.py:211-251):
This reconstructs full documents from chunks stored in the index.

Example Queries

Advanced Filtering

Programmatic Access

Troubleshooting

No Results Found

Causes:
  • Version mismatch in filter
  • Library name mismatch
  • No documents indexed for that version
Solutions:
  1. Check available versions: openground list
  2. Verify indexing completed successfully
  3. Try broader query terms

Irrelevant Results

Causes:
  • Query too vague
  • Embedding model mismatch
  • BM25 overwhelming semantic signal
Solutions:
  1. Make query more specific
  2. Increase top_k to see more results
  3. Re-index with better embedding model

Slow Queries

Causes:
  • Large index size
  • Slow embedding generation
  • Cold cache
Solutions:
  1. Enable GPU acceleration
  2. Use fastembed backend
  3. Reduce top_k
  4. Run a warmup query

Next Steps