kumiho-construct 2026.5.11

Construct — memory-native AI agent runtime powered by Kumiho
/// Embedding providers — removed in Construct.
///
/// Persistent memory (and vector search) is exclusively handled via the
/// Kumiho MCP server. The `EmbeddingProvider` trait and `NoopEmbedding`
/// stub are preserved because they are referenced by other modules
/// (e.g., `skills::creator`). The OpenAI HTTP embedding implementation
/// and factory have been removed.
use async_trait::async_trait;

/// Trait for embedding providers — convert text to vectors.
#[async_trait]
pub trait EmbeddingProvider: Send + Sync {
    /// Provider name
    fn name(&self) -> &str;

    /// Embedding dimensions
    fn dimensions(&self) -> usize;

    /// Embed a batch of texts into vectors
    async fn embed(&self, texts: &[&str]) -> anyhow::Result<Vec<Vec<f32>>>;

    /// Embed a single text
    async fn embed_one(&self, text: &str) -> anyhow::Result<Vec<f32>> {
        let mut results = self.embed(&[text]).await?;
        results
            .pop()
            .ok_or_else(|| anyhow::anyhow!("Empty embedding result"))
    }
}

// ── Noop provider (keyword-only fallback) ────────────────────

pub struct NoopEmbedding;

#[async_trait]
impl EmbeddingProvider for NoopEmbedding {
    fn name(&self) -> &str {
        "none"
    }

    fn dimensions(&self) -> usize {
        0
    }

    async fn embed(&self, _texts: &[&str]) -> anyhow::Result<Vec<Vec<f32>>> {
        Ok(Vec::new())
    }
}

// ── Factory (stub) ───────────────────────────────────────────

/// Embedding provider factory — removed in Construct.
///
/// All embedding providers now return `NoopEmbedding`. Semantic search
/// is handled by the Kumiho MCP server.
pub fn create_embedding_provider(
    _provider: &str,
    _api_key: Option<&str>,
    _model: &str,
    _dims: usize,
) -> Box<dyn EmbeddingProvider> {
    Box::new(NoopEmbedding)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn noop_name() {
        let p = NoopEmbedding;
        assert_eq!(p.name(), "none");
        assert_eq!(p.dimensions(), 0);
    }

    #[tokio::test]
    async fn noop_embed_returns_empty() {
        let p = NoopEmbedding;
        let result = p.embed(&["hello"]).await.unwrap();
        assert!(result.is_empty());
    }

    #[test]
    fn factory_always_returns_noop() {
        let p = create_embedding_provider("openai", Some("key"), "text-embedding-3-small", 1536);
        assert_eq!(p.name(), "none");
        assert_eq!(p.dimensions(), 0);
    }

    #[tokio::test]
    async fn noop_embed_one_returns_error() {
        let p = NoopEmbedding;
        let result = p.embed_one("hello").await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn noop_embed_empty_batch() {
        let p = NoopEmbedding;
        let result = p.embed(&[]).await.unwrap();
        assert!(result.is_empty());
    }
}