use anyhow::Result; use serde::{Deserialize, Serialize}; /// Vector embedding record in pgvector. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VectorRecord { pub id: String, pub chunk_id: String, pub kind: String, // "text" | "symptom" pub embedding: Vec, // 768-dimensional for nomic pub tokens: u32, } /// pgvector client. pub struct VectorStore { // In production: PostgreSQL connection // For now: in-memory vec records: Vec, } impl VectorStore { /// Create a new vector store. pub fn new() -> Self { Self { records: Vec::new(), } } /// Insert a vector record. pub fn insert(&mut self, record: VectorRecord) -> Result<()> { self.records.push(record); Ok(()) } /// Search by cosine similarity. pub fn search(&self, query: &[f32], limit: usize, min_score: f32) -> Result> { let mut results = Vec::new(); for record in &self.records { if let Some(score) = cosine_similarity(query, &record.embedding) { if score >= min_score { results.push((record.id.clone(), score)); } } } results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); Ok(results.into_iter().take(limit).collect()) } /// Get all records. pub fn all(&self) -> Vec<&VectorRecord> { self.records.iter().collect() } } /// Compute cosine similarity between two vectors. fn cosine_similarity(a: &[f32], b: &[f32]) -> Option { if a.len() != b.len() { return None; } let mut dot_product = 0.0; let mut norm_a = 0.0; let mut norm_b = 0.0; for (x, y) in a.iter().zip(b.iter()) { dot_product += x * y; norm_a += x * x; norm_b += y * y; } let norm_a = norm_a.sqrt(); let norm_b = norm_b.sqrt(); if norm_a == 0.0 || norm_b == 0.0 { return None; } Some(dot_product / (norm_a * norm_b)) }