searchHybrid method
Hybrid search combining vector and keyword (BM25) search.
This method uses Reciprocal Rank Fusion (RRF) to combine:
- Vector search: semantic similarity using embeddings
- BM25 search: keyword matching for exact terms
Use this for better results when searching for:
- Proper nouns (names, product models)
- Technical terms or code snippets
- Exact keyword matches
Parameters:
query: The search query texttopK: Number of results to return (default: 10)vectorWeight: Weight for vector search (0.0-1.0, default: 0.2)bm25Weight: Weight for BM25 search (0.0-1.0, default: 0.8)
Implementation
Future<List<hybrid.HybridSearchResult>> searchHybrid(
String query, {
int topK = 10,
double vectorWeight = 0.2,
double bm25Weight = 0.8,
List<int>? sourceIds,
}) async {
// 1. Generate query embedding
final queryEmbedding = await EmbeddingService.embed(query);
// 2. Ensure hybrid indexes are switched to this collection.
await rust_rag.activateCollectionForHybridSearch(
collectionId: collectionId,
basePath: _indexPath,
);
// 3. Perform hybrid search with RRF fusion
late List<hybrid.HybridSearchResult> results;
try {
// Improve post-filtering recall:
// If filtering by source, fetch more candidates internally to avoid
// the dominant source occupying all top-k slots before filtering.
final effectiveTopK = sourceIds != null
? (topK * 10).clamp(50, 200)
: topK;
results = await hybrid.searchHybrid(
queryText: query,
queryEmbedding: queryEmbedding,
topK: effectiveTopK,
config: hybrid.RrfConfig(
k: 60,
vectorWeight: vectorWeight,
bm25Weight: bm25Weight,
),
filter: hybrid.SearchFilter(
sourceIds: sourceIds != null ? _toInt64List(sourceIds) : null,
collectionId: collectionId,
),
);
} on RagError catch (e) {
e.when(
databaseError: (msg) =>
debugPrint('[SmartError] Hybrid search DB error: $msg'),
ioError: (_) {},
modelLoadError: (_) {},
invalidInput: (_) {},
internalError: (msg) =>
log('[SmartError] Hybrid search engine error: $msg'),
unknown: (msg) => log('[SmartError] Hybrid search unknown error: $msg'),
);
rethrow;
}
return results.take(topK).toList();
}