mentedb_context/
serializer.rs1use crate::layout::{AttentionZone, ContextBlock};
4pub trait ContextSerializer {
6 fn serialize(&self, blocks: &[ContextBlock]) -> String;
7}
8
9#[derive(Debug, Clone, Copy)]
12pub struct CompactFormat;
13
14impl ContextSerializer for CompactFormat {
15 fn serialize(&self, blocks: &[ContextBlock]) -> String {
16 let mut lines = Vec::new();
17
18 for block in blocks {
19 if block.memories.is_empty() {
20 continue;
21 }
22 lines.push(format!("# {}", zone_label(block.zone)));
23 for sm in &block.memories {
24 let m = &sm.memory;
25 let tags = if m.tags.is_empty() {
26 String::new()
27 } else {
28 format!("|tags:{}", m.tags.join(","))
29 };
30 lines.push(format!(
31 "M|{:?}|{:.2}|{}{}",
32 m.memory_type, m.salience, m.content, tags
33 ));
34 }
35 }
36
37 lines.join("\n")
38 }
39}
40
41#[derive(Debug, Clone, Copy)]
43pub struct StructuredFormat;
44
45impl ContextSerializer for StructuredFormat {
46 fn serialize(&self, blocks: &[ContextBlock]) -> String {
47 let mut parts = Vec::new();
48
49 for block in blocks {
50 if block.memories.is_empty() {
51 continue;
52 }
53 parts.push(format!("## {}", zone_label(block.zone)));
54 for sm in &block.memories {
55 let m = &sm.memory;
56 let mut line = format!(
57 "- **[{:?}]** (salience: {:.2}) {}",
58 m.memory_type, m.salience, m.content
59 );
60 if !m.tags.is_empty() {
61 line.push_str(&format!(" [{}]", m.tags.join(", ")));
62 }
63 parts.push(line);
64 }
65 parts.push(String::new());
66 }
67
68 parts.join("\n")
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct DeltaFormat {
75 pub delta_header: String,
76}
77
78impl DeltaFormat {
79 pub fn new(delta_header: String) -> Self {
80 Self { delta_header }
81 }
82}
83
84impl ContextSerializer for DeltaFormat {
85 fn serialize(&self, blocks: &[ContextBlock]) -> String {
86 let mut parts = vec![self.delta_header.clone()];
87 parts.push(String::new());
88
89 for block in blocks {
91 if block.memories.is_empty() {
92 continue;
93 }
94 parts.push(format!("## {}", zone_label(block.zone)));
95 for sm in &block.memories {
96 parts.push(format!(
97 "- [NEW] {:?} | {}",
98 sm.memory.memory_type, sm.memory.content
99 ));
100 }
101 }
102
103 parts.join("\n")
104 }
105}
106
107fn zone_label(zone: AttentionZone) -> &'static str {
108 match zone {
109 AttentionZone::Opening => "⚠️ Warnings & Corrections",
110 AttentionZone::Critical => "🎯 Critical Context",
111 AttentionZone::Primary => "📋 Primary Context",
112 AttentionZone::Supporting => "📎 Supporting Context",
113 AttentionZone::Closing => "🔁 Summary & Reinforcement",
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::layout::{AttentionZone, ContextBlock, ScoredMemory};
121 use mentedb_core::MemoryNode;
122 use mentedb_core::memory::MemoryType;
123 use mentedb_core::types::AgentId;
124
125 fn make_block(zone: AttentionZone, content: &str, mem_type: MemoryType) -> ContextBlock {
126 let mut m = MemoryNode::new(AgentId::new(), 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}