use crate::error::{RragError, RragResult};
use crate::storage::{Memory, MemoryValue};
use rexis_llm::{ChatMessage, MessageRole}; use uuid::Uuid;
pub struct ConversationMemoryStore {
storage: std::sync::Arc<dyn Memory>,
namespace: String,
max_length: usize,
persist: bool,
}
impl ConversationMemoryStore {
pub fn new(
storage: std::sync::Arc<dyn Memory>,
session_id: String,
max_length: usize,
persist: bool,
) -> Self {
let namespace = format!("session::{}::conversation", session_id);
Self {
storage,
namespace,
max_length,
persist,
}
}
pub async fn add_message(&self, message: ChatMessage) -> RragResult<()> {
if !self.persist {
return Ok(());
}
let count = self.count().await?;
let key = self.message_key(count);
let value = self.message_to_value(&message)?;
self.storage.set(&key, value).await?;
if count + 1 > self.max_length {
self.prune_old_messages().await?;
}
Ok(())
}
pub async fn get_messages(&self) -> RragResult<Vec<ChatMessage>> {
if !self.persist {
return Ok(Vec::new());
}
let count = self.count().await?;
let mut messages = Vec::with_capacity(count);
for idx in 0..count {
let key = self.message_key(idx);
if let Some(value) = self.storage.get(&key).await? {
let message = self.value_to_message(&value)?;
messages.push(message);
}
}
Ok(messages)
}
pub async fn count(&self) -> RragResult<usize> {
if !self.persist {
return Ok(0);
}
let count_key = format!("{}::count", self.namespace);
if let Some(value) = self.storage.get(&count_key).await? {
if let Some(count) = value.as_integer() {
return Ok(count as usize);
}
}
Ok(0)
}
pub async fn clear(&self) -> RragResult<()> {
if !self.persist {
return Ok(());
}
let system_msg = if self.count().await? > 0 {
let key = self.message_key(0);
if let Some(value) = self.storage.get(&key).await? {
let msg = self.value_to_message(&value)?;
if matches!(msg.role, MessageRole::System) {
Some(msg)
} else {
None
}
} else {
None
}
} else {
None
};
self.storage.clear(Some(&self.namespace)).await?;
if let Some(msg) = system_msg {
self.add_message(msg).await?;
}
Ok(())
}
fn message_key(&self, index: usize) -> String {
format!("{}::msg_{}", self.namespace, index)
}
fn message_to_value(&self, message: &ChatMessage) -> RragResult<MemoryValue> {
let json = serde_json::to_value(message).map_err(|e| {
RragError::storage(
"serialize_message",
std::io::Error::new(std::io::ErrorKind::Other, e),
)
})?;
Ok(MemoryValue::Json(json))
}
fn value_to_message(&self, value: &MemoryValue) -> RragResult<ChatMessage> {
if let Some(json) = value.as_json() {
let message = serde_json::from_value(json.clone()).map_err(|e| {
RragError::storage(
"deserialize_message",
std::io::Error::new(std::io::ErrorKind::Other, e),
)
})?;
Ok(message)
} else {
Err(RragError::storage(
"invalid_message_type",
std::io::Error::new(std::io::ErrorKind::InvalidData, "Expected JSON value"),
))
}
}
async fn prune_old_messages(&self) -> RragResult<()> {
let count = self.count().await?;
if count <= self.max_length {
return Ok(());
}
let has_system = if let Some(value) = self.storage.get(&self.message_key(0)).await? {
let msg = self.value_to_message(&value)?;
matches!(msg.role, MessageRole::System)
} else {
false
};
let start_idx = if has_system { 1 } else { 0 };
let to_remove = count - self.max_length;
let mut keys_to_delete = Vec::new();
for idx in start_idx..(start_idx + to_remove) {
keys_to_delete.push(self.message_key(idx));
}
self.storage.mdelete(&keys_to_delete).await?;
for idx in (start_idx + to_remove)..count {
let old_key = self.message_key(idx);
let new_key = self.message_key(idx - to_remove);
if let Some(value) = self.storage.get(&old_key).await? {
self.storage.set(&new_key, value).await?;
self.storage.delete(&old_key).await?;
}
}
let count_key = format!("{}::count", self.namespace);
self.storage
.set(&count_key, MemoryValue::Integer((count - to_remove) as i64))
.await?;
Ok(())
}
pub async fn is_empty(&self) -> RragResult<bool> {
Ok(self.count().await? == 0)
}
}
pub fn generate_session_id() -> String {
Uuid::new_v4().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::InMemoryStorage;
use std::sync::Arc;
#[tokio::test]
async fn test_conversation_memory_store() {
let storage = Arc::new(InMemoryStorage::new());
let session_id = generate_session_id();
let store = ConversationMemoryStore::new(storage, session_id, 10, true);
store
.add_message(ChatMessage::system("You are a helpful assistant"))
.await
.unwrap();
store.add_message(ChatMessage::user("Hello")).await.unwrap();
store
.add_message(ChatMessage::assistant("Hi there!"))
.await
.unwrap();
let messages = store.get_messages().await.unwrap();
assert_eq!(messages.len(), 3);
assert_eq!(store.count().await.unwrap(), 3);
store.clear().await.unwrap();
let messages = store.get_messages().await.unwrap();
assert_eq!(messages.len(), 1);
assert!(matches!(messages[0].role, rexis_llm::MessageRole::System));
}
}