use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum MemoryBackendType {
#[default]
InMemory,
Sqlite,
Redis,
Qdrant,
#[serde(alias = "chroma")]
ChromaDB,
Pinecone,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MemoryConfig {
#[serde(default)]
pub backend: MemoryBackendType,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub url: Option<String>,
#[serde(default)]
pub grpc_url: Option<String>,
#[serde(default)]
pub collection: Option<String>,
#[serde(default)]
pub dimensions: Option<usize>,
#[serde(default)]
pub api_key: Option<String>,
#[serde(default)]
pub embedding_model: Option<String>,
#[serde(default)]
pub use_server_embeddings: Option<bool>,
#[serde(default)]
pub prefix: Option<String>,
#[serde(default)]
pub max_entries: Option<usize>,
#[serde(default)]
pub ttl_seconds: Option<i64>,
}
impl MemoryConfig {
pub fn new() -> Self {
Self::default()
}
pub fn in_memory() -> Self {
Self {
backend: MemoryBackendType::InMemory,
..Default::default()
}
}
pub fn sqlite(path: impl Into<String>) -> Self {
Self {
backend: MemoryBackendType::Sqlite,
path: Some(path.into()),
..Default::default()
}
}
pub fn redis(url: impl Into<String>) -> Self {
Self {
backend: MemoryBackendType::Redis,
url: Some(url.into()),
..Default::default()
}
}
pub fn qdrant(
grpc_url: impl Into<String>,
collection: impl Into<String>,
dimensions: usize,
) -> Self {
Self {
backend: MemoryBackendType::Qdrant,
grpc_url: Some(grpc_url.into()),
collection: Some(collection.into()),
dimensions: Some(dimensions),
..Default::default()
}
}
pub fn chromadb(url: impl Into<String>, collection: impl Into<String>) -> Self {
Self {
backend: MemoryBackendType::ChromaDB,
url: Some(url.into()),
collection: Some(collection.into()),
..Default::default()
}
}
pub fn pinecone(
api_key: impl Into<String>,
collection: impl Into<String>,
dimensions: usize,
) -> Self {
Self {
backend: MemoryBackendType::Pinecone,
api_key: Some(api_key.into()),
collection: Some(collection.into()),
dimensions: Some(dimensions),
..Default::default()
}
}
pub fn with_max_entries(mut self, max: usize) -> Self {
self.max_entries = Some(max);
self
}
pub fn with_ttl_seconds(mut self, seconds: i64) -> Self {
self.ttl_seconds = Some(seconds);
self
}
pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = Some(prefix.into());
self
}
pub fn with_collection(mut self, collection: impl Into<String>) -> Self {
self.collection = Some(collection.into());
self
}
pub fn with_dimensions(mut self, dimensions: usize) -> Self {
self.dimensions = Some(dimensions);
self
}
pub fn with_embedding_model(mut self, model: impl Into<String>) -> Self {
self.embedding_model = Some(model.into());
self
}
pub fn with_server_embeddings(mut self, enabled: bool) -> Self {
self.use_server_embeddings = Some(enabled);
self
}
pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
self.api_key = Some(api_key.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_inmemory_config() {
let toml = r#"
backend = "inmemory"
max_entries = 500
ttl_seconds = 3600
"#;
let config: MemoryConfig = toml::from_str(toml).unwrap();
assert_eq!(config.backend, MemoryBackendType::InMemory);
assert_eq!(config.max_entries, Some(500));
assert_eq!(config.ttl_seconds, Some(3600));
}
#[test]
fn test_parse_sqlite_config() {
let toml = r#"
backend = "sqlite"
path = "./data/knowledge.db"
"#;
let config: MemoryConfig = toml::from_str(toml).unwrap();
assert_eq!(config.backend, MemoryBackendType::Sqlite);
assert_eq!(config.path, Some("./data/knowledge.db".to_string()));
}
#[test]
fn test_parse_redis_config() {
let toml = r#"
backend = "redis"
url = "redis://localhost:6379"
prefix = "myapp"
ttl_seconds = 7200
"#;
let config: MemoryConfig = toml::from_str(toml).unwrap();
assert_eq!(config.backend, MemoryBackendType::Redis);
assert_eq!(config.url, Some("redis://localhost:6379".to_string()));
assert_eq!(config.prefix, Some("myapp".to_string()));
assert_eq!(config.ttl_seconds, Some(7200));
}
#[test]
fn test_default_is_inmemory() {
let toml = "";
let config: MemoryConfig = toml::from_str(toml).unwrap();
assert_eq!(config.backend, MemoryBackendType::InMemory);
}
#[test]
fn test_builder_methods() {
let config = MemoryConfig::in_memory()
.with_max_entries(1000)
.with_ttl_seconds(3600);
assert_eq!(config.backend, MemoryBackendType::InMemory);
assert_eq!(config.max_entries, Some(1000));
assert_eq!(config.ttl_seconds, Some(3600));
let config = MemoryConfig::redis("redis://localhost:6379")
.with_prefix("test")
.with_ttl_seconds(7200);
assert_eq!(config.backend, MemoryBackendType::Redis);
assert_eq!(config.url, Some("redis://localhost:6379".to_string()));
assert_eq!(config.prefix, Some("test".to_string()));
}
}