1use crate::typed_id::{MemoryId, MemoryStoreId, OrgId};
10use async_trait::async_trait;
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13
14#[cfg(feature = "openapi")]
15use utoipa::ToSchema;
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24#[cfg_attr(feature = "openapi", derive(ToSchema))]
25#[serde(tag = "type", rename_all = "snake_case")]
26pub enum MemoryContentPart {
27 Text(MemoryTextPart),
29 Image(MemoryImagePart),
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
35#[cfg_attr(feature = "openapi", derive(ToSchema))]
36pub struct MemoryTextPart {
37 pub text: String,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42#[cfg_attr(feature = "openapi", derive(ToSchema))]
43pub struct MemoryImagePart {
44 pub base64: String,
46 pub media_type: String,
48}
49
50impl MemoryContentPart {
51 pub fn text(text: impl Into<String>) -> Self {
52 Self::Text(MemoryTextPart { text: text.into() })
53 }
54
55 pub fn image(base64: impl Into<String>, media_type: impl Into<String>) -> Self {
56 Self::Image(MemoryImagePart {
57 base64: base64.into(),
58 media_type: media_type.into(),
59 })
60 }
61
62 pub fn estimated_size(&self) -> usize {
64 match self {
65 Self::Text(t) => t.text.len(),
66 Self::Image(i) => i.base64.len(),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
77#[cfg_attr(feature = "openapi", derive(ToSchema))]
78#[serde(rename_all = "snake_case")]
79pub enum MemoryKind {
80 #[default]
81 Fact,
82 Preference,
83 Correction,
84 Procedure,
85 Context,
86}
87
88impl std::fmt::Display for MemoryKind {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 match self {
91 Self::Fact => write!(f, "fact"),
92 Self::Preference => write!(f, "preference"),
93 Self::Correction => write!(f, "correction"),
94 Self::Procedure => write!(f, "procedure"),
95 Self::Context => write!(f, "context"),
96 }
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
106#[cfg_attr(feature = "openapi", derive(ToSchema))]
107pub struct Memory {
108 pub id: MemoryId,
109 pub store_id: MemoryStoreId,
110 pub content: String,
111 pub content_parts: Vec<MemoryContentPart>,
112 pub kind: MemoryKind,
113 pub importance: u8,
115 pub tags: Vec<String>,
116 pub active: bool,
117 pub created_at: DateTime<Utc>,
118 pub updated_at: DateTime<Utc>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
127#[cfg_attr(feature = "openapi", derive(ToSchema))]
128pub struct MemoryStoreEntity {
129 pub id: MemoryStoreId,
130 pub org_id: OrgId,
131 pub name: String,
132 pub is_default: bool,
133 pub created_at: DateTime<Utc>,
134}
135
136pub struct MemoryLimits;
142
143impl MemoryLimits {
144 pub const MAX_CONTENT_LENGTH: usize = 2_000;
146 pub const MAX_TAGS: usize = 10;
148 pub const MAX_IMAGE_PARTS: usize = 4;
150 pub const MAX_IMAGE_SIZE: usize = 5 * 1024 * 1024;
152 pub const MAX_TOTAL_IMAGE_SIZE: usize = 10 * 1024 * 1024;
154 pub const MAX_MEMORIES_PER_STORE: usize = 10_000;
156 pub const MAX_STORES_PER_ORG: usize = 50;
158
159 pub fn validate(
161 content: &str,
162 tags: &[String],
163 parts: &[MemoryContentPart],
164 ) -> Result<(), String> {
165 if content.len() > Self::MAX_CONTENT_LENGTH {
166 return Err(format!(
167 "Memory content exceeds {} character limit (got {})",
168 Self::MAX_CONTENT_LENGTH,
169 content.len()
170 ));
171 }
172 if tags.len() > Self::MAX_TAGS {
173 return Err(format!(
174 "Too many tags: max {}, got {}",
175 Self::MAX_TAGS,
176 tags.len()
177 ));
178 }
179
180 let mut image_count = 0;
181 let mut total_image_size = 0;
182 for part in parts {
183 if let MemoryContentPart::Image(img) = part {
184 image_count += 1;
185 let size = img.base64.len();
186 if size > Self::MAX_IMAGE_SIZE {
187 return Err(format!(
188 "Image exceeds {} byte limit (got {})",
189 Self::MAX_IMAGE_SIZE,
190 size
191 ));
192 }
193 total_image_size += size;
194 }
195 }
196 if image_count > Self::MAX_IMAGE_PARTS {
197 return Err(format!(
198 "Too many images: max {}, got {}",
199 Self::MAX_IMAGE_PARTS,
200 image_count
201 ));
202 }
203 if total_image_size > Self::MAX_TOTAL_IMAGE_SIZE {
204 return Err(format!(
205 "Total image data exceeds {} byte limit (got {})",
206 Self::MAX_TOTAL_IMAGE_SIZE,
207 total_image_size
208 ));
209 }
210
211 Ok(())
212 }
213}
214
215#[derive(Debug, Default)]
221pub struct MemoryQuery {
222 pub store_id: Option<MemoryStoreId>,
223 pub query: Option<String>,
224 pub tags: Option<Vec<String>>,
225 pub kind: Option<MemoryKind>,
226 pub limit: usize,
227}
228
229#[async_trait]
231pub trait MemoryStoreBackend: Send + Sync {
232 async fn get_or_create_default_store(
234 &self,
235 org_id: OrgId,
236 ) -> crate::error::Result<MemoryStoreEntity>;
237
238 async fn get_store(
240 &self,
241 store_id: MemoryStoreId,
242 ) -> crate::error::Result<Option<MemoryStoreEntity>>;
243
244 async fn create_memory(
246 &self,
247 store_id: MemoryStoreId,
248 content: String,
249 content_parts: Vec<MemoryContentPart>,
250 kind: MemoryKind,
251 importance: u8,
252 tags: Vec<String>,
253 ) -> crate::error::Result<Memory>;
254
255 async fn recall(&self, query: MemoryQuery) -> crate::error::Result<(Vec<Memory>, usize)>;
257
258 async fn forget(
263 &self,
264 store_id: MemoryStoreId,
265 memory_id: MemoryId,
266 ) -> crate::error::Result<bool>;
267
268 async fn count_active(&self, store_id: MemoryStoreId) -> crate::error::Result<usize>;
270}