elph_core/memz/
builder.rs1use anyhow::{Result, bail};
2
3use super::embed::{DEFAULT_EMBED_MODEL, FastEmbedOptions, create_fastembed};
4#[cfg(feature = "fastembed")]
5use super::embed::{embedding_dims, resolve_embedding_model};
6use super::paths::MemzPaths;
7use super::store::{EmbedFn, MemoryStore, noop_embedder};
8use super::types::{MemzConfig, VectorType};
9use super::util::DEFAULT_EMBEDDING_DIMS;
10
11pub struct MemzBuilder {
13 config: MemzConfig,
14 custom_embed: Option<EmbedFn>,
15 fastembed_opts: Option<FastEmbedOptions>,
16}
17
18impl MemzBuilder {
19 pub fn new(db_path: impl Into<String>, session_id: impl Into<String>) -> Self {
20 Self {
21 config: MemzConfig::new(db_path, session_id),
22 custom_embed: None,
23 fastembed_opts: None,
24 }
25 }
26
27 pub fn from_paths(paths: &MemzPaths, session_id: impl Into<String>) -> Self {
28 Self::new(paths.db_path_string(), session_id)
29 }
30
31 pub fn vector_type(mut self, vector_type: VectorType) -> Self {
32 self.config = self.config.vector_type(vector_type);
33 self
34 }
35
36 pub fn dimensions(mut self, dimensions: u32) -> Self {
37 self.config = self.config.dimensions(dimensions);
38 self
39 }
40
41 pub fn top_k(mut self, top_k: u32) -> Self {
42 self.config = self.config.top_k(top_k);
43 self
44 }
45
46 pub fn learning_rate(mut self, learning_rate: f64) -> Self {
47 self.config = self.config.learning_rate(learning_rate);
48 self
49 }
50
51 pub fn decay_rate(mut self, decay_rate: f64) -> Self {
52 self.config = self.config.decay_rate(decay_rate);
53 self
54 }
55
56 pub fn apply_migrations(mut self, apply: bool) -> Self {
58 self.config = self.config.apply_migrations(apply);
59 self
60 }
61
62 pub fn embed(mut self, embed: EmbedFn) -> Self {
64 self.custom_embed = Some(embed);
65 self.fastembed_opts = None;
66 self
67 }
68
69 pub fn noop_embed(mut self) -> Self {
71 let dims = self.config.dimensions.unwrap_or(DEFAULT_EMBEDDING_DIMS);
72 self.custom_embed = Some(noop_embedder(dims));
73 self.fastembed_opts = None;
74 self
75 }
76
77 #[cfg(feature = "fastembed")]
79 pub fn fastembed(mut self, options: FastEmbedOptions) -> Result<Self> {
80 let model_name = options.model.as_deref().unwrap_or(DEFAULT_EMBED_MODEL);
81 let dims = resolve_embedding_model(model_name, options.quantized)
82 .map(|m| embedding_dims(&m))
83 .map_err(|e| anyhow::anyhow!("{e}"))?;
84 self.config = self.config.dimensions(dims);
85 self.fastembed_opts = Some(options);
86 self.custom_embed = None;
87 Ok(self)
88 }
89
90 #[cfg(not(feature = "fastembed"))]
91 pub fn fastembed(mut self, _options: FastEmbedOptions) -> Result<Self> {
92 bail!("fastembed backend requires the `fastembed` feature on this crate");
93 }
94
95 pub fn build(self) -> Result<MemoryStore> {
96 let embed = match (self.custom_embed, self.fastembed_opts) {
97 (Some(e), None) => e,
98 (None, Some(opts)) => create_fastembed(opts)?,
99 (None, None) => {
100 bail!("embedder required: call .embed(), .noop_embed(), or .fastembed()");
101 }
102 (Some(_), Some(_)) => bail!("cannot set both a custom embedder and fastembed options"),
103 };
104 Ok(MemoryStore::new(self.config, embed))
105 }
106}
107
108impl MemzPaths {
109 pub fn builder(&self, session_id: impl Into<String>) -> MemzBuilder {
111 MemzBuilder::from_paths(self, session_id)
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use std::sync::Arc;
119
120 fn mock_embed() -> EmbedFn {
121 Arc::new(|_| Box::pin(async { Ok(vec![1.0, 0.0, 0.0, 0.0]) }))
122 }
123
124 #[test]
125 fn builder_requires_embedder() {
126 let dir = tempfile::tempdir().expect("tempdir");
127 let db = dir.path().join("t.db").to_string_lossy().into_owned();
128 match MemzBuilder::new(db, "s").build() {
129 Err(e) => assert!(e.to_string().contains("embedder required")),
130 Ok(_) => panic!("expected embedder required error"),
131 }
132 }
133
134 #[test]
135 fn builder_with_custom_embed() {
136 let dir = tempfile::tempdir().expect("tempdir");
137 let db = dir.path().join("t.db").to_string_lossy().into_owned();
138 let store = MemzBuilder::new(db, "s")
139 .dimensions(4)
140 .embed(mock_embed())
141 .build()
142 .expect("build");
143 assert_eq!(store.dimensions(), 4);
144 }
145
146 #[test]
147 fn from_paths_sets_db_location() {
148 let paths = MemzPaths::project_local();
149 let store = paths.builder("sess").dimensions(4).noop_embed().build().expect("build");
150 assert_eq!(store.dimensions(), 4);
151 }
152}