dory-memory 0.1.8

Backend memory store for Hermes Agent — pgvector-powered semantic memory engine with server-side embeddings
use crate::cache::DoryCacheManager;
use crate::error::{DoryError, DoryResult};

pub struct EmbeddingClient {
    client: reqwest::Client,
    api_url: String,
    api_key: String,
    model: String,
}

impl EmbeddingClient {
    pub fn new(api_url: String, api_key: String, model: String) -> Self {
        Self {
            client: reqwest::Client::new(),
            api_url,
            api_key,
            model,
        }
    }

    pub async fn generate_embedding(
        &self,
        text: &str,
        cache: &DoryCacheManager,
        dimensions: usize,
    ) -> DoryResult<Vec<f32>> {
        if let Some(cached) = cache.get_cached_embedding(text) {
            return Ok(cached);
        }

        let response = self
            .client
            .post(&self.api_url)
            .header("Authorization", format!("Bearer {}", self.api_key))
            .json(&serde_json::json!({
                "model": self.model,
                "input": text,
                "dimensions": dimensions,
            }))
            .send()
            .await
            .map_err(|e| DoryError::Embedding(format!("Request failed: {e}")))?;

        if !response.status().is_success() {
            let status = response.status();
            let body = response.text().await.unwrap_or_default();
            return Err(DoryError::Embedding(format!(
                "API returned {status}: {body}"
            )));
        }

        let body: serde_json::Value = response
            .json()
            .await
            .map_err(|e| DoryError::Embedding(format!("Failed to parse response: {e}")))?;

        let embedding: Vec<f32> = body["data"][0]["embedding"]
            .as_array()
            .ok_or_else(|| DoryError::Embedding("No embedding in response".to_string()))?
            .iter()
            .map(|v| v.as_f64().unwrap_or(0.0) as f32)
            .collect();

        cache.insert_embedding(text, embedding.clone());
        Ok(embedding)
    }
}