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}