cascade_agent/knowledge/
embeddings.rs1use std::sync::Mutex;
4
5use fastembed::{EmbeddingModel, TextEmbedding, TextInitOptions};
6
7use crate::error::{AgentError, Result};
8
9fn model_from_name(name: &str) -> Result<EmbeddingModel> {
11 match name {
12 "all-MiniLM-L6-v2" => Ok(EmbeddingModel::AllMiniLML6V2),
13 "all-MiniLM-L6-v2-q" => Ok(EmbeddingModel::AllMiniLML6V2Q),
14 "all-MiniLM-L12-v2" => Ok(EmbeddingModel::AllMiniLML12V2),
15 "all-mpnet-base-v2" => Ok(EmbeddingModel::AllMpnetBaseV2),
16 "bge-base-en-v1.5" => Ok(EmbeddingModel::BGEBaseENV15),
17 "bge-base-en-v1.5-q" => Ok(EmbeddingModel::BGEBaseENV15Q),
18 "bge-large-en-v1.5" => Ok(EmbeddingModel::BGELargeENV15),
19 "bge-large-en-v1.5-q" => Ok(EmbeddingModel::BGELargeENV15Q),
20 "bge-small-en-v1.5" => Ok(EmbeddingModel::BGESmallENV15),
21 "bge-small-en-v1.5-q" => Ok(EmbeddingModel::BGESmallENV15Q),
22 "nomic-embed-text-v1" => Ok(EmbeddingModel::NomicEmbedTextV1),
23 "nomic-embed-text-v1.5" => Ok(EmbeddingModel::NomicEmbedTextV15),
24 "nomic-embed-text-v1.5-q" => Ok(EmbeddingModel::NomicEmbedTextV15Q),
25 "paraphrase-multilingual-MiniLM-L12-v2" => Ok(EmbeddingModel::ParaphraseMLMiniLML12V2),
26 "paraphrase-multilingual-mpnet-base-v2" => Ok(EmbeddingModel::ParaphraseMLMpnetBaseV2),
27 "bgem3" | "BAAI/bgem3" => Ok(EmbeddingModel::BGEM3),
28 "multilingual-e5-small" | "intfloat/multilingual-e5-small" => {
29 Ok(EmbeddingModel::MultilingualE5Small)
30 }
31 "multilingual-e5-base" | "intfloat/multilingual-e5-base" => {
32 Ok(EmbeddingModel::MultilingualE5Base)
33 }
34 "multilingual-e5-large" | "intfloat/multilingual-e5-large" => {
35 Ok(EmbeddingModel::MultilingualE5Large)
36 }
37 "mxbai-embed-large-v1" => Ok(EmbeddingModel::MxbaiEmbedLargeV1),
38 "mxbai-embed-large-v1-q" => Ok(EmbeddingModel::MxbaiEmbedLargeV1Q),
39 "gte-base-en-v1.5" => Ok(EmbeddingModel::GTEBaseENV15),
40 _ => Err(AgentError::EmbeddingError(format!(
41 "Unknown embedding model: '{}'. \
42 See docs for supported models.",
43 name
44 ))),
45 }
46}
47
48pub struct Embedder {
53 model: Mutex<TextEmbedding>,
54 dimension: usize,
55}
56
57impl std::fmt::Debug for Embedder {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 f.debug_struct("Embedder")
60 .field("dimension", &self.dimension)
61 .finish_non_exhaustive()
62 }
63}
64
65impl Embedder {
66 pub fn new(model_name: &str) -> Result<Self> {
68 let model_enum = model_from_name(model_name)?;
69 let opts = TextInitOptions::new(model_enum).with_show_download_progress(true);
70
71 let mut text_embedding = TextEmbedding::try_new(opts)
72 .map_err(|e| AgentError::EmbeddingError(format!("Failed to load model: {}", e)))?;
73
74 let dimension = {
76 let mut te = text_embedding;
77 let result = te
78 .embed(["dim_probe"], None)
79 .map_err(|e| AgentError::EmbeddingError(format!("Probe embed failed: {}", e)))?;
80 let dim = result.into_iter().next().unwrap().len();
81 text_embedding = te;
82 dim
83 };
84
85 Ok(Self {
86 model: Mutex::new(text_embedding),
87 dimension,
88 })
89 }
90
91 pub fn embed_query(&self, text: &str) -> Result<Vec<f32>> {
93 let prefixed = format!("query: {}", text);
94 let mut guard = self
95 .model
96 .lock()
97 .map_err(|e| AgentError::EmbeddingError(format!("Embedder lock poisoned: {}", e)))?;
98 let results = guard
99 .embed([&prefixed], None)
100 .map_err(|e| AgentError::EmbeddingError(format!("Query embed failed: {}", e)))?;
101 Ok(results.into_iter().next().unwrap())
102 }
103
104 pub fn embed_passage(&self, text: &str) -> Result<Vec<f32>> {
106 let prefixed = format!("passage: {}", text);
107 let mut guard = self
108 .model
109 .lock()
110 .map_err(|e| AgentError::EmbeddingError(format!("Embedder lock poisoned: {}", e)))?;
111 let results = guard
112 .embed([&prefixed], None)
113 .map_err(|e| AgentError::EmbeddingError(format!("Passage embed failed: {}", e)))?;
114 Ok(results.into_iter().next().unwrap())
115 }
116
117 pub fn embed_batch_passages(&self, texts: &[String]) -> Result<Vec<Vec<f32>>> {
119 if texts.is_empty() {
120 return Ok(Vec::new());
121 }
122 let prefixed: Vec<String> = texts.iter().map(|t| format!("passage: {}", t)).collect();
123 let mut guard = self
124 .model
125 .lock()
126 .map_err(|e| AgentError::EmbeddingError(format!("Embedder lock poisoned: {}", e)))?;
127 guard
128 .embed(&prefixed, None)
129 .map_err(|e| AgentError::EmbeddingError(format!("Batch embed failed: {}", e)))
130 }
131
132 pub fn dimension(&self) -> usize {
134 self.dimension
135 }
136}