Skip to main content

construct/memory/
embeddings.rs

1/// Embedding providers — removed in Construct.
2///
3/// Persistent memory (and vector search) is exclusively handled via the
4/// Kumiho MCP server. The `EmbeddingProvider` trait and `NoopEmbedding`
5/// stub are preserved because they are referenced by other modules
6/// (e.g., `skills::creator`). The OpenAI HTTP embedding implementation
7/// and factory have been removed.
8use async_trait::async_trait;
9
10/// Trait for embedding providers — convert text to vectors.
11#[async_trait]
12pub trait EmbeddingProvider: Send + Sync {
13    /// Provider name
14    fn name(&self) -> &str;
15
16    /// Embedding dimensions
17    fn dimensions(&self) -> usize;
18
19    /// Embed a batch of texts into vectors
20    async fn embed(&self, texts: &[&str]) -> anyhow::Result<Vec<Vec<f32>>>;
21
22    /// Embed a single text
23    async fn embed_one(&self, text: &str) -> anyhow::Result<Vec<f32>> {
24        let mut results = self.embed(&[text]).await?;
25        results
26            .pop()
27            .ok_or_else(|| anyhow::anyhow!("Empty embedding result"))
28    }
29}
30
31// ── Noop provider (keyword-only fallback) ────────────────────
32
33pub struct NoopEmbedding;
34
35#[async_trait]
36impl EmbeddingProvider for NoopEmbedding {
37    fn name(&self) -> &str {
38        "none"
39    }
40
41    fn dimensions(&self) -> usize {
42        0
43    }
44
45    async fn embed(&self, _texts: &[&str]) -> anyhow::Result<Vec<Vec<f32>>> {
46        Ok(Vec::new())
47    }
48}
49
50// ── Factory (stub) ───────────────────────────────────────────
51
52/// Embedding provider factory — removed in Construct.
53///
54/// All embedding providers now return `NoopEmbedding`. Semantic search
55/// is handled by the Kumiho MCP server.
56pub fn create_embedding_provider(
57    _provider: &str,
58    _api_key: Option<&str>,
59    _model: &str,
60    _dims: usize,
61) -> Box<dyn EmbeddingProvider> {
62    Box::new(NoopEmbedding)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn noop_name() {
71        let p = NoopEmbedding;
72        assert_eq!(p.name(), "none");
73        assert_eq!(p.dimensions(), 0);
74    }
75
76    #[tokio::test]
77    async fn noop_embed_returns_empty() {
78        let p = NoopEmbedding;
79        let result = p.embed(&["hello"]).await.unwrap();
80        assert!(result.is_empty());
81    }
82
83    #[test]
84    fn factory_always_returns_noop() {
85        let p = create_embedding_provider("openai", Some("key"), "text-embedding-3-small", 1536);
86        assert_eq!(p.name(), "none");
87        assert_eq!(p.dimensions(), 0);
88    }
89
90    #[tokio::test]
91    async fn noop_embed_one_returns_error() {
92        let p = NoopEmbedding;
93        let result = p.embed_one("hello").await;
94        assert!(result.is_err());
95    }
96
97    #[tokio::test]
98    async fn noop_embed_empty_batch() {
99        let p = NoopEmbedding;
100        let result = p.embed(&[]).await.unwrap();
101        assert!(result.is_empty());
102    }
103}