use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorSearchResult<T> {
pub entity: T,
pub distance: f32,
pub score: f32,
}
impl<T> VectorSearchResult<T> {
pub fn new(entity: T, distance: f32) -> Self {
let score = 1.0 - (distance / 2.0).min(1.0);
Self { entity, distance, score }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum EntityType {
Product,
Customer,
Order,
InventoryItem,
}
impl EntityType {
#[must_use]
pub const fn embedding_table(&self) -> &'static str {
match self {
Self::Product => "product_embeddings",
Self::Customer => "customer_embeddings",
Self::Order => "order_embeddings",
Self::InventoryItem => "inventory_embeddings",
}
}
#[must_use]
pub const fn id_column(&self) -> &'static str {
match self {
Self::Product => "product_id",
Self::Customer => "customer_id",
Self::Order => "order_id",
Self::InventoryItem => "item_id",
}
}
#[must_use]
pub const fn source_table(&self) -> &'static str {
match self {
Self::Product => "products",
Self::Customer => "customers",
Self::Order => "orders",
Self::InventoryItem => "inventory_items",
}
}
}
impl fmt::Display for EntityType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Product => write!(f, "product"),
Self::Customer => write!(f, "customer"),
Self::Order => write!(f, "order"),
Self::InventoryItem => write!(f, "inventory_item"),
}
}
}
impl std::str::FromStr for EntityType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"product" | "products" => Ok(Self::Product),
"customer" | "customers" => Ok(Self::Customer),
"order" | "orders" => Ok(Self::Order),
"inventory_item" | "inventory" | "inventory_items" => Ok(Self::InventoryItem),
_ => Err(format!("Unknown entity type: {s}")),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddingMetadata {
pub entity_type: EntityType,
pub entity_id: String,
pub model: String,
pub text_hash: String,
pub created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddingRequest {
pub entity_type: EntityType,
pub entity_id: String,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddingConfig {
pub api_key: String,
pub model: String,
pub dimensions: usize,
}
impl Default for EmbeddingConfig {
fn default() -> Self {
Self {
api_key: String::new(),
model: "text-embedding-3-small".to_string(),
dimensions: 1536,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorSearchQuery {
pub query: String,
pub entity_type: EntityType,
pub limit: usize,
pub min_score: Option<f32>,
}
impl Default for VectorSearchQuery {
fn default() -> Self {
Self { query: String::new(), entity_type: EntityType::Product, limit: 10, min_score: None }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddingStats {
pub counts: std::collections::HashMap<EntityType, u64>,
pub model: String,
pub dimensions: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entity_type_parsing() {
assert_eq!("product".parse::<EntityType>().unwrap(), EntityType::Product);
assert_eq!("products".parse::<EntityType>().unwrap(), EntityType::Product);
assert_eq!("customer".parse::<EntityType>().unwrap(), EntityType::Customer);
assert_eq!("inventory".parse::<EntityType>().unwrap(), EntityType::InventoryItem);
}
#[test]
fn test_vector_search_result_score() {
let result = VectorSearchResult::new("test", 0.0);
assert_eq!(result.score, 1.0);
let result = VectorSearchResult::new("test", 2.0);
assert_eq!(result.score, 0.0);
let result = VectorSearchResult::new("test", 1.0);
assert_eq!(result.score, 0.5);
}
}