mentedb_context/
serializer.rs1use crate::layout::{AttentionZone, ContextBlock};
4
5pub trait ContextSerializer {
7 fn serialize(&self, blocks: &[ContextBlock]) -> String;
8}
9
10#[derive(Debug, Clone, Copy)]
13pub struct CompactFormat;
14
15impl ContextSerializer for CompactFormat {
16 fn serialize(&self, blocks: &[ContextBlock]) -> String {
17 let mut lines = Vec::new();
18
19 for block in blocks {
20 if block.memories.is_empty() {
21 continue;
22 }
23 lines.push(format!("# {}", zone_label(block.zone)));
24 for sm in &block.memories {
25 let m = &sm.memory;
26 let tags = if m.tags.is_empty() {
27 String::new()
28 } else {
29 format!("|tags:{}", m.tags.join(","))
30 };
31 lines.push(format!(
32 "M|{:?}|{:.2}|{}{}",
33 m.memory_type, m.salience, m.content, tags
34 ));
35 }
36 }
37
38 lines.join("\n")
39 }
40}
41
42#[derive(Debug, Clone, Copy)]
44pub struct StructuredFormat;
45
46impl ContextSerializer for StructuredFormat {
47 fn serialize(&self, blocks: &[ContextBlock]) -> String {
48 let mut parts = Vec::new();
49
50 for block in blocks {
51 if block.memories.is_empty() {
52 continue;
53 }
54 parts.push(format!("## {}", zone_label(block.zone)));
55 for sm in &block.memories {
56 let m = &sm.memory;
57 let mut line = format!(
58 "- **[{:?}]** (salience: {:.2}) {}",
59 m.memory_type, m.salience, m.content
60 );
61 if !m.tags.is_empty() {
62 line.push_str(&format!(" [{}]", m.tags.join(", ")));
63 }
64 parts.push(line);
65 }
66 parts.push(String::new());
67 }
68
69 parts.join("\n")
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct DeltaFormat {
76 pub delta_header: String,
77}
78
79impl DeltaFormat {
80 pub fn new(delta_header: String) -> Self {
81 Self { delta_header }
82 }
83}
84
85impl ContextSerializer for DeltaFormat {
86 fn serialize(&self, blocks: &[ContextBlock]) -> String {
87 let mut parts = vec![self.delta_header.clone()];
88 parts.push(String::new());
89
90 for block in blocks {
92 if block.memories.is_empty() {
93 continue;
94 }
95 parts.push(format!("## {}", zone_label(block.zone)));
96 for sm in &block.memories {
97 parts.push(format!(
98 "- [NEW] {:?} | {}",
99 sm.memory.memory_type, sm.memory.content
100 ));
101 }
102 }
103
104 parts.join("\n")
105 }
106}
107
108fn zone_label(zone: AttentionZone) -> &'static str {
109 match zone {
110 AttentionZone::Opening => "⚠️ Warnings & Corrections",
111 AttentionZone::Critical => "🎯 Critical Context",
112 AttentionZone::Primary => "📋 Primary Context",
113 AttentionZone::Supporting => "📎 Supporting Context",
114 AttentionZone::Closing => "🔁 Summary & Reinforcement",
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::layout::{AttentionZone, ContextBlock, ScoredMemory};
122 use mentedb_core::MemoryNode;
123 use mentedb_core::memory::MemoryType;
124
125 fn make_block(zone: AttentionZone, content: &str, mem_type: MemoryType) -> ContextBlock {
126 let mut m = MemoryNode::new(uuid::Uuid::new_v4(), mem_type, content.to_string(), vec![]);
127 m.salience = 0.9;
128 m.tags = vec!["test".to_string()];
129 ContextBlock {
130 zone,
131 memories: vec![ScoredMemory {
132 memory: m,
133 score: 0.9,
134 }],
135 estimated_tokens: 10,
136 }
137 }
138
139 #[test]
140 fn test_compact_format() {
141 let blocks = vec![make_block(
142 AttentionZone::Critical,
143 "user likes Rust",
144 MemoryType::Semantic,
145 )];
146 let output = CompactFormat.serialize(&blocks);
147 assert!(output.contains("M|Semantic|0.90|user likes Rust|tags:test"));
148 assert!(output.contains("🎯 Critical Context"));
149 }
150
151 #[test]
152 fn test_structured_format() {
153 let blocks = vec![make_block(
154 AttentionZone::Opening,
155 "avoid eval",
156 MemoryType::AntiPattern,
157 )];
158 let output = StructuredFormat.serialize(&blocks);
159 assert!(output.contains("## ⚠️ Warnings & Corrections"));
160 assert!(output.contains("**[AntiPattern]**"));
161 assert!(output.contains("avoid eval"));
162 }
163
164 #[test]
165 fn test_delta_format() {
166 let blocks = vec![make_block(
167 AttentionZone::Critical,
168 "new info",
169 MemoryType::Episodic,
170 )];
171 let fmt = DeltaFormat::new("[UNCHANGED] 5 memories from previous turn".to_string());
172 let output = fmt.serialize(&blocks);
173 assert!(output.contains("[UNCHANGED] 5 memories"));
174 assert!(output.contains("[NEW] Episodic | new info"));
175 }
176
177 #[test]
178 fn test_empty_blocks_skipped() {
179 let blocks = vec![ContextBlock {
180 zone: AttentionZone::Supporting,
181 memories: vec![],
182 estimated_tokens: 0,
183 }];
184 let output = CompactFormat.serialize(&blocks);
185 assert!(output.is_empty());
186 }
187}