How Hybrid Search Works
Hybrid search combines two complementary retrieval methods:- Vector Search (Semantic): Finds documents with similar meaning using embeddings
- BM25 (Keyword): Finds documents with matching terms using statistical ranking
Implementation
Fromquery.py:68-105, the core search implementation:
Key Components
- Query Embedding: The query text is converted to a vector using the same embedding model used for documents
- Hybrid Builder: LanceDB’s
search(query_type="hybrid")enables hybrid mode - Dual Input: Both
.text(query)(for BM25) and.vector(query_vec)(for semantic) are provided - Filtering: Version and library filters are applied as SQL WHERE clauses
- Result Limit:
top_kcontrols the number of results returned
Why Hybrid Search?
Vector Search Alone
Strengths:- Understands semantic similarity
- Handles synonyms and paraphrases
- Good for conceptual queries
- Can miss exact keyword matches
- May retrieve semantically similar but contextually wrong results
- Sensitive to embedding model quality
BM25 Alone
Strengths:- Excellent for exact term matching
- Fast and deterministic
- Good for technical terms and code
- No semantic understanding
- Misses synonyms and paraphrases
- Sensitive to term frequency
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):
- Vector distance: Lower is better (cosine distance)
- BM25 score: Higher is better (statistical relevance)
Tuning Parameters
Top-K Results
Control the number of results returned:- 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:- Small models: Faster embedding, lower memory, slightly lower accuracy
- Large models: Better semantic understanding, higher memory/compute cost
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):
query.py:46-65):
Caching
Query module uses multiple caches to improve performance (query.py:12-43):
Performance Considerations
Query Latency
Typical query latency breakdown:- Embedding generation: 10-100ms (depends on model and backend)
- Hybrid search: 10-50ms (depends on index size)
- Result formatting: Less than 5ms
Optimizing Query Speed
- Use fastembed backend: Faster embedding generation
- Enable GPU: 10x faster embeddings (see GPU Acceleration)
- Reduce top_k: Fewer results = faster search
- 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):
Example Queries
Basic Search
Advanced Filtering
Programmatic Access
Troubleshooting
No Results Found
Causes:- Version mismatch in filter
- Library name mismatch
- No documents indexed for that version
- Check available versions:
openground list - Verify indexing completed successfully
- Try broader query terms
Irrelevant Results
Causes:- Query too vague
- Embedding model mismatch
- BM25 overwhelming semantic signal
- Make query more specific
- Increase
top_kto see more results - Re-index with better embedding model
Slow Queries
Causes:- Large index size
- Slow embedding generation
- Cold cache
- Enable GPU acceleration
- Use fastembed backend
- Reduce
top_k - Run a warmup query
Next Steps
- Learn about Embedding Backends to optimize semantic search
- Explore GPU Acceleration to speed up query embedding generation