1use crate::{Engine, EngineError, EngineResult, WriteMemoryInput, WriteMemoryOutput, WriteWarning};
7use hippmem_core::hash::stable_hash64;
8use hippmem_core::ids::{MemoryId, VectorId};
9use hippmem_core::model::enums::ContentType;
10use hippmem_core::model::links::SemanticSignature;
11use hippmem_core::model::understanding::MemoryUnderstanding;
12use hippmem_core::model::unit::{Language, MemoryContent, MemoryStage, MemoryUnit};
13use hippmem_core::score::UnitScore;
14use hippmem_core::time::{Clock, SystemClock};
15use hippmem_model::deterministic::extract::DeterministicExtractor;
16use hippmem_store::kv::InvertedIndex;
17use hippmem_store::semantic::vector_index::BinaryIndex;
18use hippmem_store::semantic::vector_index::VectorIndex;
19use hippmem_write::edges::EdgeBuildParams;
20use hippmem_write::keys::generate_keys;
21use hippmem_write::staged::{raw_to_indexed, StagedWriteInput};
22use hippmem_write::understanding::index_enriched_keys;
23
24impl Engine {
25 pub fn write(&self, input: WriteMemoryInput) -> EngineResult<WriteMemoryOutput> {
27 let memory_id = MemoryId::generate();
28 write_internal(self, memory_id, input, false, None)
29 }
30
31 pub fn write_batch(
36 &self,
37 inputs: Vec<WriteMemoryInput>,
38 ) -> EngineResult<Vec<WriteMemoryOutput>> {
39 if inputs.is_empty() {
40 return Ok(vec![]);
41 }
42
43 let n = inputs.len();
44 const CHUNK_SIZE: usize = 10; let mut embeddings: Vec<Option<Vec<f32>>> = Vec::with_capacity(n);
48 let texts: Vec<String> = inputs.iter().map(|inp| inp.content.clone()).collect();
49
50 for chunk in texts.chunks(CHUNK_SIZE) {
51 match self.embedder.embed_sync(chunk) {
52 Ok(vectors) => {
53 for v in vectors {
54 embeddings.push(Some(v));
55 }
56 }
57 Err(_e) => {
58 embeddings.resize(embeddings.len() + chunk.len(), None);
60 }
61 }
62 }
63
64 let mut outputs = Vec::with_capacity(inputs.len());
65 for (input, embedding) in inputs.into_iter().zip(embeddings) {
66 let memory_id = MemoryId::generate();
67 let output = write_internal(self, memory_id, input, false, embedding)?;
68 outputs.push(output);
69 }
70 Ok(outputs)
71 }
72}
73
74pub(crate) fn write_internal(
81 engine: &Engine,
82 memory_id: MemoryId,
83 input: WriteMemoryInput,
84 skip_memory_log: bool,
85 precomputed_embedding: Option<Vec<f32>>,
86) -> EngineResult<WriteMemoryOutput> {
87 let clock = SystemClock;
88 let _now = clock.now();
89
90 let content = MemoryContent {
92 raw: input.content.clone(),
93 summary: None,
94 normalized: None,
95 language: Language::Zh,
96 content_type: input.content_type.unwrap_or(ContentType::UserStatement),
97 };
98
99 let extractor = DeterministicExtractor;
101 let (understanding, mut warnings) = match extractor.extract_sync_immediate(&content) {
102 Ok(imm) => {
103 let u = MemoryUnderstanding {
104 entities: imm.entities,
105 topics: imm.topics,
106 causal_claims: imm.explicit_causals,
107 goals: vec![],
108 decisions: vec![],
109 preferences: vec![],
110 emotions: vec![],
111 events: vec![],
112 contradictions: vec![],
113 importance: input
114 .importance_hint
115 .map(UnitScore::new)
116 .unwrap_or(imm.importance),
117 confidence: UnitScore::new(0.5),
118 };
119 (u, vec![])
120 }
121 Err(_e) => (
122 MemoryUnderstanding {
123 entities: vec![],
124 topics: vec![],
125 causal_claims: vec![],
126 goals: vec![],
127 decisions: vec![],
128 preferences: vec![],
129 emotions: vec![],
130 events: vec![],
131 contradictions: vec![],
132 importance: UnitScore::new(0.0),
133 confidence: UnitScore::new(0.0),
134 },
135 vec![WriteWarning::ExtractorDegraded],
136 ),
137 };
138
139 let mut semantic = build_semantic_signature(&input.content);
141 {
143 if let Some(vector) = precomputed_embedding {
144 let vector_id = memory_id.0;
146 let mut idx = engine.dense_vector_index.lock();
147 let _ = idx.insert(vector_id, &vector);
148 semantic.dense_embedding_ref = Some(VectorId(vector_id));
149 } else {
150 let texts = vec![input.content.clone()];
152 if let Ok(vectors) = engine.embedder.embed_sync(&texts) {
153 if let Some(vector) = vectors.first() {
154 let vector_id = memory_id.0;
155 let mut idx = engine.dense_vector_index.lock();
156 let _ = idx.insert(vector_id, vector);
157 semantic.dense_embedding_ref = Some(VectorId(vector_id));
158 }
159 }
160 }
161 }
162 if semantic.dense_embedding_ref.is_none() {
164 warnings.push(WriteWarning::EmbeddingDeferred);
165 }
166
167 {
169 let bc_bytes = binary_code_to_bytes(&semantic.binary_code);
170 let mut idx = engine.binary_code_index.lock();
171 let _ = idx.insert(memory_id.0, &bc_bytes);
172 }
173
174 let keys = generate_keys(&content, &understanding, &input.context, &semantic)
176 .map_err(|e| EngineError::Internal(format!("generate_keys: {}", e)))?;
177
178 let inverted = InvertedIndex::new(engine.store.db_arc());
180 let candidate_ids = discover_candidates(&keys, &inverted);
181 let existing_units = load_memory_units(&engine.store.db_arc(), &candidate_ids);
182
183 let staged_input = StagedWriteInput {
185 id: memory_id,
186 content: content.clone(),
187 understanding: understanding.clone(),
188 context: input.context.clone(),
189 semantic,
190 };
191
192 let algo = engine.params.read();
194 let edge_params = EdgeBuildParams {
195 strong_threshold: algo.strong_edge_threshold,
196 strong_max: algo.strong_edge_max as usize,
197 weak_max: algo.weak_edge_max as usize,
198 min_score: algo.edge_build_min_score,
199 observation_max: algo.observation_enter_max,
200 max_candidates: 30, };
202 let staged_output = raw_to_indexed(staged_input, &existing_units, &edge_params, &algo)
203 .map_err(|e| EngineError::Internal(format!("raw_to_indexed: {}", e)))?;
204
205 let unit = staged_output.unit;
206
207 let bincode_unit = bincode::serde::encode_to_vec(&unit, bincode::config::standard())
209 .map_err(|e| EngineError::Internal(e.to_string()))?;
210 let bincode_links =
211 bincode::serde::encode_to_vec(&staged_output.created_links, bincode::config::standard())
212 .map_err(|e| EngineError::Internal(e.to_string()))?;
213
214 hippmem_store::kv::persist_memory_unit(
215 engine.store.db_arc(),
216 memory_id.0,
217 &bincode_unit,
218 &bincode_links,
219 &keys.entity_keys,
220 &keys.topic_keys,
221 &keys.temporal_keys,
222 &keys.goal_keys,
223 &keys.event_keys,
224 &keys.causal_keys,
225 skip_memory_log,
226 )
227 .map_err(|e| EngineError::Store(e.to_string()))?;
228
229 {
232 let tokens = hippmem_core::hash::tokenize(&input.content, "zh");
233 let mut ft = engine.fulltext_index.lock();
234 ft.add_document_tokenized(memory_id.0, &tokens)
235 .map_err(|e| EngineError::Store(format!("Tantivy add_document: {}", e)))?;
236 }
237
238 warnings.push(WriteWarning::StrongDimsDeferred);
242
243 let mut enriched_unit = unit.clone();
245 crate::runtime::run_enrich_sync(&mut enriched_unit);
246
247 index_enriched_keys(&enriched_unit, &inverted, memory_id.0)
249 .map_err(|e| EngineError::Internal(format!("index_enriched_keys: {}", e)))?;
250
251 let re_bincode = bincode::serde::encode_to_vec(&enriched_unit, bincode::config::standard())
252 .map_err(|e| EngineError::Internal(e.to_string()))?;
253 hippmem_store::kv::KvStore::new(engine.store.db_arc())
255 .put(memory_id.0, &re_bincode)
256 .map_err(|e| EngineError::Store(e.to_string()))?;
257
258 Ok(WriteMemoryOutput {
259 memory_id,
260 stage_reached: MemoryStage::Indexed,
261 created_links: staged_output.created_links,
262 understanding,
263 warnings,
264 })
265}
266
267fn binary_code_to_bytes(bc: &[u64; 2]) -> [u8; 16] {
271 let mut bytes = [0u8; 16];
272 bytes[..8].copy_from_slice(&bc[0].to_le_bytes());
273 bytes[8..].copy_from_slice(&bc[1].to_le_bytes());
274 bytes
275}
276
277fn build_semantic_signature(text: &str) -> SemanticSignature {
278 let sim0 = stable_hash64(text);
279 let sim1 = stable_hash64(&format!("{}_1", text));
280 let sim2 = stable_hash64(&format!("{}_2", text));
281 let sim3 = stable_hash64(&format!("{}_3", text));
282 let bc0 = stable_hash64(&format!("bc_0_{}", text));
283 let bc1 = stable_hash64(&format!("bc_1_{}", text));
284
285 let mut minhash = [0u32; 16];
286 for (i, v) in minhash.iter_mut().enumerate() {
287 *v = stable_hash64(&format!("mh_{}_{}", i, text)) as u32;
288 }
289
290 SemanticSignature {
291 lexical_simhash: [sim0, sim1, sim2, sim3],
292 dense_embedding_ref: None,
293 binary_code: [bc0, bc1],
294 topic_minhash: minhash,
295 }
296}
297
298fn discover_candidates(
299 keys: &hippmem_core::model::links::AssociationKeys,
300 inverted: &InvertedIndex,
301) -> Vec<MemoryId> {
302 let mut ids = std::collections::HashSet::new();
303 for ek in &keys.entity_keys {
304 if let Ok(hits) = inverted.get_entity(ek) {
305 for id in hits {
306 ids.insert(MemoryId(id));
307 }
308 }
309 }
310 for tk in &keys.topic_keys {
311 if let Ok(hits) = inverted.get_topic(tk) {
312 for id in hits {
313 ids.insert(MemoryId(id));
314 }
315 }
316 }
317 for tk in &keys.temporal_keys {
318 if let Ok(hits) = inverted.get_temporal(tk) {
319 for id in hits {
320 ids.insert(MemoryId(id));
321 }
322 }
323 }
324 ids.into_iter().collect()
325}
326
327fn load_memory_units(db: &std::sync::Arc<redb::Database>, ids: &[MemoryId]) -> Vec<MemoryUnit> {
328 let kv = hippmem_store::kv::KvStore::new(std::sync::Arc::clone(db));
329 ids.iter()
330 .filter_map(|mid| {
331 kv.get(&mid.0).ok().flatten().and_then(|data| {
332 bincode::serde::decode_from_slice::<MemoryUnit, _>(
333 &data,
334 bincode::config::standard(),
335 )
336 .ok()
337 .map(|(unit, _)| unit)
338 })
339 })
340 .collect()
341}