feat(memory): lucid memory integration with optional backends (#285)
This commit is contained in:
parent
04bf94443f
commit
53844f7207
11 changed files with 1089 additions and 137 deletions
|
|
@ -1,12 +1,22 @@
|
|||
pub mod backend;
|
||||
pub mod chunker;
|
||||
pub mod embeddings;
|
||||
pub mod hygiene;
|
||||
pub mod lucid;
|
||||
pub mod markdown;
|
||||
pub mod none;
|
||||
pub mod sqlite;
|
||||
pub mod traits;
|
||||
pub mod vector;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use backend::{
|
||||
classify_memory_backend, default_memory_backend_key, memory_backend_profile,
|
||||
selectable_memory_backends, MemoryBackendKind, MemoryBackendProfile,
|
||||
};
|
||||
pub use lucid::LucidMemory;
|
||||
pub use markdown::MarkdownMemory;
|
||||
pub use none::NoneMemory;
|
||||
pub use sqlite::SqliteMemory;
|
||||
pub use traits::Memory;
|
||||
#[allow(unused_imports)]
|
||||
|
|
@ -16,6 +26,32 @@ use crate::config::MemoryConfig;
|
|||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn create_memory_with_sqlite_builder<F>(
|
||||
backend_name: &str,
|
||||
workspace_dir: &Path,
|
||||
mut sqlite_builder: F,
|
||||
unknown_context: &str,
|
||||
) -> anyhow::Result<Box<dyn Memory>>
|
||||
where
|
||||
F: FnMut() -> anyhow::Result<SqliteMemory>,
|
||||
{
|
||||
match classify_memory_backend(backend_name) {
|
||||
MemoryBackendKind::Sqlite => Ok(Box::new(sqlite_builder()?)),
|
||||
MemoryBackendKind::Lucid => {
|
||||
let local = sqlite_builder()?;
|
||||
Ok(Box::new(LucidMemory::new(workspace_dir, local)))
|
||||
}
|
||||
MemoryBackendKind::Markdown => Ok(Box::new(MarkdownMemory::new(workspace_dir))),
|
||||
MemoryBackendKind::None => Ok(Box::new(NoneMemory::new())),
|
||||
MemoryBackendKind::Unknown => {
|
||||
tracing::warn!(
|
||||
"Unknown memory backend '{backend_name}'{unknown_context}, falling back to markdown"
|
||||
);
|
||||
Ok(Box::new(MarkdownMemory::new(workspace_dir)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Factory: create the right memory backend from config
|
||||
pub fn create_memory(
|
||||
config: &MemoryConfig,
|
||||
|
|
@ -27,32 +63,54 @@ pub fn create_memory(
|
|||
tracing::warn!("memory hygiene skipped: {e}");
|
||||
}
|
||||
|
||||
match config.backend.as_str() {
|
||||
"sqlite" => {
|
||||
let embedder: Arc<dyn embeddings::EmbeddingProvider> =
|
||||
Arc::from(embeddings::create_embedding_provider(
|
||||
&config.embedding_provider,
|
||||
api_key,
|
||||
&config.embedding_model,
|
||||
config.embedding_dimensions,
|
||||
));
|
||||
fn build_sqlite_memory(
|
||||
config: &MemoryConfig,
|
||||
workspace_dir: &Path,
|
||||
api_key: Option<&str>,
|
||||
) -> anyhow::Result<SqliteMemory> {
|
||||
let embedder: Arc<dyn embeddings::EmbeddingProvider> =
|
||||
Arc::from(embeddings::create_embedding_provider(
|
||||
&config.embedding_provider,
|
||||
api_key,
|
||||
&config.embedding_model,
|
||||
config.embedding_dimensions,
|
||||
));
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let mem = SqliteMemory::with_embedder(
|
||||
workspace_dir,
|
||||
embedder,
|
||||
config.vector_weight as f32,
|
||||
config.keyword_weight as f32,
|
||||
config.embedding_cache_size,
|
||||
)?;
|
||||
Ok(Box::new(mem))
|
||||
}
|
||||
"markdown" | "none" => Ok(Box::new(MarkdownMemory::new(workspace_dir))),
|
||||
other => {
|
||||
tracing::warn!("Unknown memory backend '{other}', falling back to markdown");
|
||||
Ok(Box::new(MarkdownMemory::new(workspace_dir)))
|
||||
}
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let mem = SqliteMemory::with_embedder(
|
||||
workspace_dir,
|
||||
embedder,
|
||||
config.vector_weight as f32,
|
||||
config.keyword_weight as f32,
|
||||
config.embedding_cache_size,
|
||||
)?;
|
||||
Ok(mem)
|
||||
}
|
||||
|
||||
create_memory_with_sqlite_builder(
|
||||
&config.backend,
|
||||
workspace_dir,
|
||||
|| build_sqlite_memory(config, workspace_dir, api_key),
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_memory_for_migration(
|
||||
backend: &str,
|
||||
workspace_dir: &Path,
|
||||
) -> anyhow::Result<Box<dyn Memory>> {
|
||||
if matches!(classify_memory_backend(backend), MemoryBackendKind::None) {
|
||||
anyhow::bail!(
|
||||
"memory backend 'none' disables persistence; choose sqlite, lucid, or markdown before migration"
|
||||
);
|
||||
}
|
||||
|
||||
create_memory_with_sqlite_builder(
|
||||
backend,
|
||||
workspace_dir,
|
||||
|| SqliteMemory::new(workspace_dir),
|
||||
" during migration",
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -83,14 +141,25 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn factory_none_falls_back_to_markdown() {
|
||||
fn factory_lucid() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let cfg = MemoryConfig {
|
||||
backend: "lucid".into(),
|
||||
..MemoryConfig::default()
|
||||
};
|
||||
let mem = create_memory(&cfg, tmp.path(), None).unwrap();
|
||||
assert_eq!(mem.name(), "lucid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_none_uses_noop_memory() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let cfg = MemoryConfig {
|
||||
backend: "none".into(),
|
||||
..MemoryConfig::default()
|
||||
};
|
||||
let mem = create_memory(&cfg, tmp.path(), None).unwrap();
|
||||
assert_eq!(mem.name(), "markdown");
|
||||
assert_eq!(mem.name(), "none");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -103,4 +172,20 @@ mod tests {
|
|||
let mem = create_memory(&cfg, tmp.path(), None).unwrap();
|
||||
assert_eq!(mem.name(), "markdown");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_factory_lucid() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mem = create_memory_for_migration("lucid", tmp.path()).unwrap();
|
||||
assert_eq!(mem.name(), "lucid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_factory_none_is_rejected() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let error = create_memory_for_migration("none", tmp.path())
|
||||
.err()
|
||||
.expect("backend=none should be rejected for migration");
|
||||
assert!(error.to_string().contains("disables persistence"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue