1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//! Unified hybrid search interface.
//!
//! Combines HNSW vector search, BM25 keyword search, and graph traversal
//! into a single query interface with configurable fusion strategies.
//!
//! # Architecture (ADR-002)
//!
//! ```text
//! Query ──┬── [Vector Search] ── HNSW ── Vec<(Id, Distance)>
//! │ │
//! │ distance → similarity
//! │ │
//! │ Vec<(Id, DeterministicScore)>
//! │ │
//! └── [Keyword Search] ── BM25 ── Vec<(Id, BM25Score)>
//! │
//! normalize → DeterministicScore
//! │
//! Vec<(Id, DeterministicScore)>
//! │
//! ┌─────────────┴─────────────┐
//! │ reciprocal_rank_fusion │
//! │ k=60 (standard) │
//! └─────────────┬─────────────┘
//! │
//! Vec<(Id, DeterministicScore)>
//! ```
//!
//! # Trait Hierarchy
//!
//! ```text
//! VectorSearch ──┐
//! ├── HybridSearcher
//! KeywordSearch ─┘
//!
//! Reranker (standalone, generic over Id)
//! ```
//!
//! Each trait can be implemented independently:
//! - [`VectorSearch`]: Embedding-based nearest-neighbor search (e.g., HNSW)
//! - [`KeywordSearch`]: Text-based retrieval (e.g., BM25)
//! - [`HybridSearcher`]: Combined search requiring both vector + keyword
//! - [`Reranker`]: Post-retrieval reranking (e.g., cross-encoder)
//!
//! # Fusion Strategies
//!
//! - **RRF (Reciprocal Rank Fusion)**: Default and recommended. Uses only ranks,
//! making it robust to score distribution differences.
//! - **Weighted**: Linear combination of scores with configurable weights.
//! - **Union**: Takes the maximum score per ID across sources.
//!
//! # Example
//!
//! ```rust,ignore
//! use khive_retrieval::hybrid::{
//! HybridConfig, HybridSearcher, VectorSearch, KeywordSearch, Query, fuse_search_results,
//! };
//! use khive_score::DeterministicScore;
//!
//! // Create your own searcher implementing VectorSearch + KeywordSearch + HybridSearcher
//! // Then use fuse_search_results to combine vector and keyword results
//!
//! let vector_results = vec![("doc1".to_string(), DeterministicScore::from_f64(0.9))];
//! let keyword_results = vec![("doc1".to_string(), DeterministicScore::from_f64(0.85))];
//!
//! let config = HybridConfig::new(10);
//! let fused = fuse_search_results(vec![vector_results, keyword_results], &config);
//! ```
//!
//! See [ADR-002](../docs/ADR-002-hybrid-search.md) for algorithm specification.
// Re-export public types
pub use ;
pub use ;
pub use ;
pub use ;