Skip to main content

klieo_memory_qdrant/
factory.rs

1//! `MemoryQdrant` — convenience factory wrapping the `LongTermMemory`
2//! handle in an `Arc<dyn …>` ready for `AgentContext`.
3
4use crate::client::{QdrantConfig, QdrantHandle};
5use crate::embedder::{DummyEmbedder, Embedder};
6use crate::long_term::QdrantLongTerm;
7use klieo_core::error::MemoryError;
8use klieo_core::memory::LongTermMemory;
9use std::sync::Arc;
10
11/// Factory bundle returned by [`MemoryQdrant::new`].
12#[non_exhaustive]
13pub struct MemoryQdrant {
14    /// `LongTermMemory` implementation backed by Qdrant.
15    pub long_term: Arc<dyn LongTermMemory>,
16    /// Concrete [`QdrantLongTerm`] handle. The M2 GraphRAG composer
17    /// needs the concrete type to call `recall_filtered_checked` —
18    /// `FilterableLongTermMemory` lives in `klieo-memory-graph` (0.x)
19    /// so it cannot be smuggled into the `LongTermMemory` trait
20    /// object. Both fields point to the same `QdrantLongTerm`.
21    pub qdrant_long_term: Arc<QdrantLongTerm>,
22}
23
24impl MemoryQdrant {
25    /// Capability-shaped default — connect to a Qdrant endpoint URL
26    /// with [`DummyEmbedder`] (zero-vector, FIFO recall) and default
27    /// [`QdrantConfig`] (collection prefix `"klieo_facts"`, plaintext
28    /// remote refused, embedder id `"default"`).
29    ///
30    /// Use [`Self::new`] when you need a real embedder or non-default
31    /// `QdrantConfig` (API key, custom prefix, opt-in plaintext for
32    /// sealed mesh deployments, custom embedder id).
33    pub async fn connect(url: impl Into<String>) -> Result<Self, MemoryError> {
34        Self::new(QdrantConfig::new(url), Arc::new(DummyEmbedder)).await
35    }
36
37    /// Connect to Qdrant and return a ready-to-use handle.
38    ///
39    /// The collection is *not* created upfront; it is bootstrapped
40    /// lazily on the first `remember()` call. This avoids races when
41    /// multiple processes concurrently start before any fact is
42    /// written.
43    pub async fn new(cfg: QdrantConfig, embedder: Arc<dyn Embedder>) -> Result<Self, MemoryError> {
44        let handle = QdrantHandle::connect(&cfg)?;
45        let embedder_id = cfg.embedder_id().to_string();
46        let qdrant_long_term = Arc::new(QdrantLongTerm::new(handle, embedder, embedder_id));
47        let long_term: Arc<dyn LongTermMemory> = qdrant_long_term.clone();
48        Ok(Self {
49            long_term,
50            qdrant_long_term,
51        })
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use klieo_core::error::MemoryError;
59
60    #[tokio::test]
61    async fn connect_refuses_plaintext_remote_by_default() {
62        let result = MemoryQdrant::connect("http://qdrant.example.com:6334").await;
63        match result {
64            Err(MemoryError::Store(_)) => {}
65            Err(other) => {
66                panic!("expected MemoryError::Store from plaintext-remote refusal, got {other:?}")
67            }
68            Ok(_) => panic!("non-loopback http:// must be rejected by default"),
69        }
70    }
71}