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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! OneAI default retrieval backend stack.
//!
//! Implements the retrieval-backend traits declared in [`oneai-core::traits`]
//! (`VectorBackend`, `KeywordBackend`, `RetrievalBackend`, `RerankerProvider`)
//! using the P0-spike-verified library set (2026-07-26):
//!
//! | Backend | Crate | Feature | Role |
//! |---|---|---|---|
//! | [`InMemoryVectorBackend`] | (std only) | always | brute-force cosine, tests/tiny sets |
//! | [`SqliteVecBackend`] | `sqlite-vec` | `sqlite` | exact KNN via `vec0`, mobile/small |
//! | [`UsearchBackend`] | `usearch` | `usearch` | HNSW + mmap + pre-filter |
//! | [`TantivyBm25Backend`] | `tantivy` + `tantivy-jieba` | `tantivy` | CJK BM25 |
//! | [`BgeM3Embedder`] | `ort` | `ort` | local BGE-M3 dense embeddings |
//! | [`BgeRerankerOnnx`] | `ort` | `ort` | local `bge-reranker-v2-m3` cross-encoder |
//! | [`StandardRetrievalPipeline`] | (composes the above) | `default` | BM25+dense → RRF → rerank |
//!
//! ## The non-negotiable default pipeline
//!
//! [`StandardRetrievalPipeline`] runs BM25 + dense → RRF(k=60) → rerank
//! (top-150 → top-K), which Anthropic's "Contextual Retrieval" evaluation
//! showed cuts top-20 retrieval failure rate by 67%. An app that implements
//! its own [`RetrievalBackend`] (e.g. Qdrant, which does hybrid natively)
//! bypasses this pipeline — the trait docs in `oneai-core` say explicitly
//! that skipping the keyword/sparse leg degrades Chinese short queries and
//! unique-identifier lookups, so dropping BM25 is an informed decision.
//!
//! ## Feature flags
//!
//! - `default = ["sqlite", "usearch", "tantivy"]` — the storage backends +
//! BM25 + the pipeline all build without network access. The pipeline can
//! take any `EmbeddingService` (the ones in `oneai-rag`, or `BgeM3Embedder`
//! when `ort` is on).
//! - `ort` — local ONNX embedder + reranker. Off by default because
//! `ort-sys` downloads a prebuilt `libonnxruntime` blob and the embedder
//! needs ONNX model files at runtime. Enable with `--features ort`.
//!
//! ## Design principle
//!
//! These implementations must NOT leak storage-internal types — an app that
//! brings Qdrant/Milvus/pgvector/Elasticsearch implements against the public
//! `oneai-core` trait surface, not against these internals. Backends here are
//! the *reference* embedded stack.
//!
//! `unsafe` is confined to the `sqlite-vec` static-registration shim (a single
//! FFI transmute required to register `sqlite3_vec_init` as a SQLite auto
//! extension; every other backend is pure safe Rust over its crate's API).
use HashMap;
use Metadata;
pub use ;
pub use InMemoryVectorBackend;
pub use ;
pub use BgeM3Embedder;
pub use BgeRerankerOnnx;
pub use SqliteVecBackend;
pub use TantivyBm25Backend;
pub use UsearchBackend;
// ─── shared helpers ─────────────────────────────────────────────────────────
/// Cosine similarity between two equal-length vectors. Both must be non-empty
/// and the same length; returns 0.0 for a zero-norm query (degenerate, no
/// direction) rather than dividing by ~0.
/// Merge two metadata maps, `b` taking precedence over `a` (used when fusing
/// legs that each carry metadata for the same id).
pub
/// Convert a metadata map to a JSON string for storage in single-column backends.
pub
/// Parse a metadata map from a JSON string; empty/invalid → empty map.
pub