use crate::error::{ConproxyError, Result};
use ndarray::{Array2, ArrayView3};
use ort::session::Session;
use ort::value::TensorRef;
use std::path::Path;
use std::sync::Mutex;
use tokenizers::Tokenizer;
use super::provider::EmbedderProvider;
use async_trait::async_trait;
pub struct Embedder {
session: Mutex<Session>,
tokenizer: Tokenizer,
dimensions: usize,
}
impl Embedder {
pub fn new(model_path: impl AsRef<Path>, tokenizer_path: impl AsRef<Path>) -> Result<Self> {
let session = Session::builder()
.and_then(|mut b| b.commit_from_file(model_path.as_ref()))
.map_err(|e| ConproxyError::Embedding(format!("Failed to load ONNX model: {}", e)))?;
let tokenizer = Tokenizer::from_file(tokenizer_path.as_ref())
.map_err(|e| ConproxyError::Embedding(format!("Failed to load tokenizer: {}", e)))?;
let mut embedder = Self {
session: Mutex::new(session),
tokenizer,
dimensions: 0,
};
let probe = embedder.embed("hello")?;
embedder.dimensions = probe.len();
Ok(embedder)
}
pub fn dimensions(&self) -> usize {
self.dimensions
}
pub fn embed(&self, text: &str) -> Result<Vec<f32>> {
let encoding = self
.tokenizer
.encode(text, true)
.map_err(|e| ConproxyError::Embedding(format!("Tokenization failed: {}", e)))?;
let ids = encoding.get_ids();
let mask = encoding.get_attention_mask();
let seq_len = ids.len();
let ids_i64: Vec<i64> = ids.iter().map(|&v| v as i64).collect();
let mask_i64: Vec<i64> = mask.iter().map(|&v| v as i64).collect();
let type_ids_i64: Vec<i64> = vec![0i64; seq_len];
let input_ids_tensor = TensorRef::from_array_view(([1usize, seq_len], ids_i64.as_slice()))
.map_err(|e| ConproxyError::Embedding(format!("Input tensor error: {}", e)))?;
let attention_mask_tensor =
TensorRef::from_array_view(([1usize, seq_len], mask_i64.as_slice()))
.map_err(|e| ConproxyError::Embedding(format!("Input tensor error: {}", e)))?;
let token_type_ids_tensor =
TensorRef::from_array_view(([1usize, seq_len], type_ids_i64.as_slice()))
.map_err(|e| ConproxyError::Embedding(format!("Input tensor error: {}", e)))?;
let (shape_dims, output_data) = {
let mut session = self
.session
.lock()
.map_err(|e| ConproxyError::Embedding(format!("Session lock failed: {}", e)))?;
let outputs = session
.run(ort::inputs![
input_ids_tensor,
attention_mask_tensor,
token_type_ids_tensor
])
.map_err(|e| ConproxyError::Embedding(format!("Inference failed: {}", e)))?;
let (shape, data) = outputs[0].try_extract_tensor::<f32>().map_err(|e| {
ConproxyError::Embedding(format!("Output extraction failed: {}", e))
})?;
let dims: Vec<usize> = shape.iter().map(|&d| d as usize).collect();
let owned_data: Vec<f32> = data.to_vec();
(dims, owned_data)
};
let view3 = ndarray::ArrayView3::from_shape(
(shape_dims[0], shape_dims[1], shape_dims[2]),
&output_data,
)
.map_err(|e| ConproxyError::Embedding(format!("Reshape error: {}", e)))?;
let attention_mask_nd = Array2::from_shape_vec((1, seq_len), mask_i64)
.map_err(|e| ConproxyError::Embedding(format!("Shape error: {}", e)))?;
let pooled = mean_pool(&view3, &attention_mask_nd);
let mut embedding = pooled.row(0).to_vec();
normalize(&mut embedding);
Ok(embedding)
}
pub fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
if texts.is_empty() {
return Ok(Vec::new());
}
if texts.len() == 1 {
return Ok(vec![self.embed(texts[0])?]);
}
let encodings = self
.tokenizer
.encode_batch(texts.to_vec(), true)
.map_err(|e| ConproxyError::Embedding(format!("Batch tokenization failed: {}", e)))?;
let max_len = encodings
.iter()
.map(|e| e.get_ids().len())
.max()
.unwrap_or(0);
let batch_size = encodings.len();
let mut ids_flat = Vec::with_capacity(batch_size * max_len);
let mut mask_flat = Vec::with_capacity(batch_size * max_len);
for enc in &encodings {
let ids = enc.get_ids();
let mask = enc.get_attention_mask();
ids_flat.extend(ids.iter().map(|&id| id as i64));
ids_flat.extend(std::iter::repeat_n(0i64, max_len - ids.len()));
mask_flat.extend(mask.iter().map(|&m| m as i64));
mask_flat.extend(std::iter::repeat_n(0i64, max_len - mask.len()));
}
let type_ids_flat: Vec<i64> = vec![0i64; batch_size * max_len];
let input_ids_tensor =
TensorRef::from_array_view(([batch_size, max_len], ids_flat.as_slice()))
.map_err(|e| ConproxyError::Embedding(format!("Input tensor error: {}", e)))?;
let attention_mask_tensor =
TensorRef::from_array_view(([batch_size, max_len], mask_flat.as_slice()))
.map_err(|e| ConproxyError::Embedding(format!("Input tensor error: {}", e)))?;
let token_type_ids_tensor =
TensorRef::from_array_view(([batch_size, max_len], type_ids_flat.as_slice()))
.map_err(|e| ConproxyError::Embedding(format!("Input tensor error: {}", e)))?;
let (shape_dims, output_data) = {
let mut session = self
.session
.lock()
.map_err(|e| ConproxyError::Embedding(format!("Session lock failed: {}", e)))?;
let outputs = session
.run(ort::inputs![
input_ids_tensor,
attention_mask_tensor,
token_type_ids_tensor
])
.map_err(|e| ConproxyError::Embedding(format!("Inference failed: {}", e)))?;
let (shape, data) = outputs[0].try_extract_tensor::<f32>().map_err(|e| {
ConproxyError::Embedding(format!("Output extraction failed: {}", e))
})?;
let dims: Vec<usize> = shape.iter().map(|&d| d as usize).collect();
let owned_data: Vec<f32> = data.to_vec();
(dims, owned_data)
};
let view3 = ndarray::ArrayView3::from_shape(
(shape_dims[0], shape_dims[1], shape_dims[2]),
&output_data,
)
.map_err(|e| ConproxyError::Embedding(format!("Reshape error: {}", e)))?;
let attention_mask_nd = Array2::from_shape_vec((batch_size, max_len), mask_flat)
.map_err(|e| ConproxyError::Embedding(format!("Shape error: {}", e)))?;
let pooled = mean_pool(&view3, &attention_mask_nd);
let mut results = Vec::with_capacity(batch_size);
for i in 0..batch_size {
let mut embedding = pooled.row(i).to_vec();
normalize(&mut embedding);
results.push(embedding);
}
Ok(results)
}
}
#[async_trait]
impl EmbedderProvider for Embedder {
async fn embed(&self, text: &str) -> Result<Vec<f32>> {
Embedder::embed(self, text)
}
async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
Embedder::embed_batch(self, texts)
}
fn dimensions(&self) -> usize {
self.dimensions
}
}
pub fn mean_pool(token_embeddings: &ArrayView3<f32>, attention_mask: &Array2<i64>) -> Array2<f32> {
let batch_size = token_embeddings.shape()[0];
let hidden_dim = token_embeddings.shape()[2];
let mut result = Array2::<f32>::zeros((batch_size, hidden_dim));
for b in 0..batch_size {
let mut sum = vec![0.0f32; hidden_dim];
let mut count = 0.0f32;
for s in 0..token_embeddings.shape()[1] {
let mask_val = attention_mask[[b, s]] as f32;
if mask_val > 0.0 {
for d in 0..hidden_dim {
sum[d] += token_embeddings[[b, s, d]] * mask_val;
}
count += mask_val;
}
}
if count > 0.0 {
for d in 0..hidden_dim {
result[[b, d]] = sum[d] / count;
}
}
}
result
}
pub fn normalize(embedding: &mut [f32]) {
let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 0.0 {
for x in embedding.iter_mut() {
*x /= norm;
}
}
}
#[cfg(test)]
#[path = "tests/embedder_tests.rs"]
mod tests;