1use std::collections::{HashMap, HashSet};
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::MemoryError;
6use crate::types::{MemoryPoint, MemoryPointKind, HEADLESS_POINT_ID};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9struct ChildPosition {
10 parent_id: String,
11 index: usize,
12}
13
14#[derive(Debug, Clone)]
15pub struct MemoryTree {
16 nodes: HashMap<String, MemoryPoint>,
17 children: HashMap<String, Vec<String>>,
18 child_positions: HashMap<String, ChildPosition>,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct MemoryTypeSearchOptions {
23 pub relation_depth: usize,
24 pub max_parent_types: usize,
25 pub max_matches_per_type: usize,
26 pub max_neighborhood_nodes: usize,
27}
28
29impl Default for MemoryTypeSearchOptions {
30 fn default() -> Self {
31 Self {
32 relation_depth: 1,
33 max_parent_types: 5,
34 max_matches_per_type: 5,
35 max_neighborhood_nodes: 64,
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct MemoryRecallOptions {
42 pub max_types: usize,
43 pub max_matches_per_type: usize,
44 pub max_returned_memories: usize,
45}
46
47impl Default for MemoryRecallOptions {
48 fn default() -> Self {
49 Self {
50 max_types: 5,
51 max_matches_per_type: 3,
52 max_returned_memories: 64,
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub struct MemoryTypeNeighborhood {
59 pub query_type: String,
60 pub matched_point_id: String,
61 pub points: Vec<MemoryNeighborhoodPoint>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct MemoryRecallBranch {
66 pub query_type: String,
67 pub matched_point_id: String,
68 pub memories: Vec<String>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72pub struct MemoryNeighborhoodPoint {
73 pub point_id: String,
74 pub parent_id: Option<String>,
75 pub kind: MemoryPointKind,
76 pub relation_depth: i32,
77 pub storage: Option<String>,
78 pub types: Option<String>,
79}
80
81impl MemoryTree {
82 pub fn new() -> Self {
83 let headless = MemoryPoint::headless();
84 let mut nodes = HashMap::new();
85 nodes.insert(headless.point_id.clone(), headless);
86
87 Self {
88 nodes,
89 children: HashMap::new(),
90 child_positions: HashMap::new(),
91 }
92 }
93
94 pub fn from_points(points: Vec<MemoryPoint>) -> Result<Self, MemoryError> {
95 let mut tree = Self::new();
96
97 for point in points {
98 if point.is_headless() {
99 point.validate()?;
100 tree.nodes.insert(HEADLESS_POINT_ID.to_string(), point);
101 continue;
102 }
103
104 if tree.nodes.contains_key(&point.point_id) {
105 return Err(MemoryError::DuplicatePoint {
106 point_id: point.point_id,
107 });
108 }
109
110 point.validate()?;
111 tree.nodes.insert(point.point_id.clone(), point);
112 }
113
114 tree.rebuild_indexes()?;
115 tree.validate()?;
116 Ok(tree)
117 }
118
119 pub fn insert_root(
120 &mut self,
121 storage: impl Into<String>,
122 types: impl Into<String>,
123 ) -> Result<String, MemoryError> {
124 let point = MemoryPoint::new_root(storage, types)?;
125 self.insert_point(point)
126 }
127
128 pub fn insert_child(
129 &mut self,
130 parent_id: &str,
131 storage: impl Into<String>,
132 types: impl Into<String>,
133 ) -> Result<String, MemoryError> {
134 self.ensure_parent_exists(parent_id)?;
135 let point = if parent_id == HEADLESS_POINT_ID {
136 MemoryPoint::new_root(storage, types)?
137 } else {
138 MemoryPoint::new_child(parent_id, storage, types)?
139 };
140 self.insert_point(point)
141 }
142
143 pub fn insert_point(&mut self, mut point: MemoryPoint) -> Result<String, MemoryError> {
144 if point.is_headless() {
145 return Err(MemoryError::CannotInsertHeadless);
146 }
147
148 point.validate()?;
149
150 if self.nodes.contains_key(&point.point_id) {
151 return Err(MemoryError::DuplicatePoint {
152 point_id: point.point_id,
153 });
154 }
155
156 let parent_id = point.parent_id.clone().ok_or(MemoryError::MissingParent)?;
157 self.ensure_parent_exists(&parent_id)?;
158
159 point.kind = if parent_id == HEADLESS_POINT_ID {
160 MemoryPointKind::Root
161 } else {
162 MemoryPointKind::Point
163 };
164
165 let point_id = point.point_id.clone();
166 self.nodes.insert(point_id.clone(), point);
167 self.attach_child(&parent_id, &point_id);
168 Ok(point_id)
169 }
170
171 pub fn contains(&self, point_id: &str) -> bool {
172 self.nodes.contains_key(point_id)
173 }
174
175 pub fn len(&self) -> usize {
176 self.nodes.len()
177 }
178
179 pub fn memory_len(&self) -> usize {
180 self.nodes.len().saturating_sub(1)
181 }
182
183 pub fn is_empty(&self) -> bool {
184 self.memory_len() == 0
185 }
186
187 pub fn get(&self, point_id: &str) -> Option<&MemoryPoint> {
188 self.nodes.get(point_id)
189 }
190
191 pub fn iter_points(&self) -> impl Iterator<Item = &MemoryPoint> {
192 self.nodes.values()
193 }
194
195 pub fn to_points(&self) -> Vec<MemoryPoint> {
196 self.nodes.values().cloned().collect()
197 }
198
199 pub fn root_ids(&self) -> Vec<String> {
200 self.children
201 .get(HEADLESS_POINT_ID)
202 .cloned()
203 .unwrap_or_default()
204 }
205
206 pub fn child_ids(&self, parent_id: &str) -> Result<Vec<String>, MemoryError> {
207 self.ensure_point_exists(parent_id)?;
208 Ok(self.children.get(parent_id).cloned().unwrap_or_default())
209 }
210
211 pub fn children(&self, parent_id: &str) -> Result<Vec<&MemoryPoint>, MemoryError> {
212 self.ensure_point_exists(parent_id)?;
213 self.children
214 .get(parent_id)
215 .into_iter()
216 .flatten()
217 .map(|child_id| self.require_point(child_id))
218 .collect()
219 }
220
221 pub fn subtree_ids(&self, start_id: &str) -> Result<Vec<String>, MemoryError> {
222 self.ensure_point_exists(start_id)?;
223
224 let mut result = Vec::new();
225 let mut stack = vec![start_id.to_string()];
226
227 while let Some(point_id) = stack.pop() {
228 result.push(point_id.clone());
229 if let Some(child_ids) = self.children.get(&point_id) {
230 for child_id in child_ids.iter().rev() {
231 stack.push(child_id.clone());
232 }
233 }
234 }
235
236 Ok(result)
237 }
238
239 pub fn subtree(&self, start_id: &str) -> Result<Vec<&MemoryPoint>, MemoryError> {
240 self.subtree_ids(start_id)?
241 .iter()
242 .map(|point_id| self.require_point(point_id))
243 .collect()
244 }
245
246 pub fn path_ids(&self, point_id: &str) -> Result<Vec<String>, MemoryError> {
247 self.ensure_point_exists(point_id)?;
248
249 let mut path = Vec::new();
250 let mut current_id = Some(point_id.to_string());
251 let mut visited = HashSet::new();
252
253 while let Some(id) = current_id {
254 if !visited.insert(id.clone()) {
255 return Err(MemoryError::CycleDetected {
256 point_id: point_id.to_string(),
257 parent_id: id,
258 });
259 }
260
261 let point = self.require_point(&id)?;
262 path.push(id);
263 current_id = point.parent_id.clone();
264 }
265
266 path.reverse();
267 Ok(path)
268 }
269
270 pub fn find_type_neighborhoods(
271 &self,
272 possible_parent_types: &[String],
273 options: &MemoryTypeSearchOptions,
274 ) -> Result<Vec<MemoryTypeNeighborhood>, MemoryError> {
275 let query_types = normalize_query_types(possible_parent_types, options.max_parent_types);
276 let mut neighborhoods = Vec::new();
277
278 for query_type in query_types {
279 let mut matches = self.matching_points_by_type(&query_type);
280 matches.sort_by(|left, right| {
281 right
282 .0
283 .cmp(&left.0)
284 .then_with(|| left.1.point_id.cmp(&right.1.point_id))
285 });
286
287 for (_, point) in matches.into_iter().take(options.max_matches_per_type) {
288 let points = self.collect_neighborhood_points(
289 &point.point_id,
290 options.relation_depth,
291 options.max_neighborhood_nodes,
292 )?;
293
294 neighborhoods.push(MemoryTypeNeighborhood {
295 query_type: query_type.clone(),
296 matched_point_id: point.point_id.clone(),
297 points,
298 });
299 }
300 }
301
302 if neighborhoods.is_empty() && !self.is_empty() {
303 let fallback_query_type = possible_parent_types
304 .iter()
305 .find_map(|query_type| {
306 let query_type = query_type.trim();
307 (!query_type.is_empty()).then(|| query_type.to_string())
308 })
309 .unwrap_or_else(|| "root fallback".to_string());
310
311 for root_id in self
312 .root_ids()
313 .into_iter()
314 .take(options.max_matches_per_type)
315 {
316 let points = self.collect_neighborhood_points(
317 &root_id,
318 options.relation_depth,
319 options.max_neighborhood_nodes,
320 )?;
321
322 neighborhoods.push(MemoryTypeNeighborhood {
323 query_type: fallback_query_type.clone(),
324 matched_point_id: root_id,
325 points,
326 });
327 }
328 }
329
330 Ok(neighborhoods)
331 }
332
333 pub fn recall_subtree_memories(
334 &self,
335 memory_types: &[String],
336 options: &MemoryRecallOptions,
337 ) -> Result<Vec<MemoryRecallBranch>, MemoryError> {
338 let query_types = normalize_query_types(memory_types, options.max_types);
339 let mut branches = Vec::new();
340 let mut seen_branch_ids = HashSet::new();
341 let mut seen_memory_point_ids = HashSet::new();
342 let mut returned_memories = 0usize;
343
344 for query_type in query_types {
345 if returned_memories >= options.max_returned_memories {
346 break;
347 }
348
349 let mut matches = self.matching_points_by_type(&query_type);
350 matches.sort_by(|left, right| {
351 right
352 .0
353 .cmp(&left.0)
354 .then_with(|| left.1.point_id.cmp(&right.1.point_id))
355 });
356
357 for (_, point) in matches.into_iter().take(options.max_matches_per_type) {
358 if returned_memories >= options.max_returned_memories {
359 break;
360 }
361 if !seen_branch_ids.insert(point.point_id.clone()) {
362 continue;
363 }
364
365 let mut memories = Vec::new();
366 for point_id in self.subtree_ids(&point.point_id)? {
367 if returned_memories >= options.max_returned_memories {
368 break;
369 }
370 if !seen_memory_point_ids.insert(point_id.clone()) {
371 continue;
372 }
373
374 let subtree_point = self.require_point(&point_id)?;
375 if !subtree_point.is_active() {
376 continue;
377 }
378 if let Some(storage) = subtree_point
379 .storage
380 .as_deref()
381 .map(str::trim)
382 .filter(|storage| !storage.is_empty())
383 {
384 memories.push(storage.to_string());
385 returned_memories += 1;
386 }
387 }
388
389 if !memories.is_empty() {
390 branches.push(MemoryRecallBranch {
391 query_type: query_type.clone(),
392 matched_point_id: point.point_id.clone(),
393 memories,
394 });
395 }
396 }
397 }
398
399 Ok(branches)
400 }
401
402 pub fn format_type_neighborhoods_for_model(neighborhoods: &[MemoryTypeNeighborhood]) -> String {
403 if neighborhoods.is_empty() {
404 return "<existing-memory-candidates>\n</existing-memory-candidates>".to_string();
405 }
406
407 let mut lines = vec!["<existing-memory-candidates>".to_string()];
408
409 for neighborhood in neighborhoods {
410 lines.push(format!(
411 " <candidate query_type=\"{}\" matched_point_id=\"{}\">",
412 escape_model_text(&neighborhood.query_type),
413 escape_model_text(&neighborhood.matched_point_id)
414 ));
415
416 for point in &neighborhood.points {
417 lines.push(format!(
418 " - relation_depth={} kind={:?} point_id={} parent_id={} types={} storage={}",
419 point.relation_depth,
420 point.kind,
421 escape_model_text(&point.point_id),
422 point
423 .parent_id
424 .as_deref()
425 .map(escape_model_text)
426 .unwrap_or_else(|| "none".to_string()),
427 point
428 .types
429 .as_deref()
430 .map(escape_model_text)
431 .unwrap_or_else(|| "none".to_string()),
432 point
433 .storage
434 .as_deref()
435 .map(escape_model_text)
436 .unwrap_or_else(|| "none".to_string())
437 ));
438 }
439
440 lines.push(" </candidate>".to_string());
441 }
442
443 lines.push("</existing-memory-candidates>".to_string());
444 lines.join("\n")
445 }
446
447 pub fn update_types(
448 &mut self,
449 point_id: &str,
450 new_types: impl Into<String>,
451 ) -> Result<(), MemoryError> {
452 self.require_point_mut(point_id)?.update_types(new_types)
453 }
454
455 pub fn update_storage(
456 &mut self,
457 point_id: &str,
458 new_storage: impl Into<String>,
459 ) -> Result<(), MemoryError> {
460 self.require_point_mut(point_id)?
461 .update_storage(new_storage)
462 }
463
464 pub fn deactivate(&mut self, point_id: &str) -> Result<(), MemoryError> {
465 self.require_point_mut(point_id)?.deactivate()
466 }
467
468 pub fn reactivate(&mut self, point_id: &str) -> Result<(), MemoryError> {
469 self.require_point_mut(point_id)?.reactivate()
470 }
471
472 pub fn reparent(&mut self, point_id: &str, new_parent_id: &str) -> Result<(), MemoryError> {
473 if point_id == HEADLESS_POINT_ID {
474 return Err(MemoryError::HeadlessModification {
475 operation: "reparent",
476 });
477 }
478
479 self.ensure_point_exists(point_id)?;
480 self.ensure_parent_exists(new_parent_id)?;
481
482 if point_id == new_parent_id || self.is_ancestor(point_id, new_parent_id)? {
483 return Err(MemoryError::CycleDetected {
484 point_id: point_id.to_string(),
485 parent_id: new_parent_id.to_string(),
486 });
487 }
488
489 let old_parent_id = self
490 .require_point(point_id)?
491 .parent_id
492 .clone()
493 .ok_or(MemoryError::MissingParent)?;
494
495 if old_parent_id == new_parent_id {
496 return Ok(());
497 }
498
499 self.detach_child(point_id)?;
500 self.require_point_mut(point_id)?
501 .set_parent(new_parent_id.to_string())?;
502 self.attach_child(new_parent_id, point_id);
503 Ok(())
504 }
505
506 pub fn validate(&self) -> Result<(), MemoryError> {
507 let headless = self.require_point(HEADLESS_POINT_ID)?;
508 headless.validate()?;
509
510 for point in self.nodes.values() {
511 point.validate()?;
512
513 if point.is_headless() {
514 continue;
515 }
516
517 let parent_id = point
518 .parent_id
519 .as_deref()
520 .ok_or(MemoryError::MissingParent)?;
521 self.ensure_parent_exists(parent_id)?;
522
523 if parent_id == HEADLESS_POINT_ID && point.kind != MemoryPointKind::Root {
524 return Err(MemoryError::InvalidPointKind {
525 point_id: point.point_id.clone(),
526 expected: "root",
527 });
528 }
529 if parent_id != HEADLESS_POINT_ID && point.kind != MemoryPointKind::Point {
530 return Err(MemoryError::InvalidPointKind {
531 point_id: point.point_id.clone(),
532 expected: "point",
533 });
534 }
535
536 self.assert_reaches_headless(&point.point_id)?;
537 }
538
539 for (parent_id, child_ids) in &self.children {
540 self.ensure_point_exists(parent_id)?;
541 for (index, child_id) in child_ids.iter().enumerate() {
542 let child = self.require_point(child_id)?;
543 if child.parent_id.as_deref() != Some(parent_id.as_str()) {
544 return Err(MemoryError::TreeInvariant {
545 message: format!("child '{}' has mismatched parent", child_id),
546 });
547 }
548
549 let position = self.child_positions.get(child_id).ok_or_else(|| {
550 MemoryError::TreeInvariant {
551 message: format!("child '{}' is missing position index", child_id),
552 }
553 })?;
554
555 if position.parent_id != *parent_id || position.index != index {
556 return Err(MemoryError::TreeInvariant {
557 message: format!("child '{}' has stale position index", child_id),
558 });
559 }
560 }
561 }
562
563 for point in self.nodes.values() {
564 if point.is_headless() {
565 continue;
566 }
567 if !self.child_positions.contains_key(&point.point_id) {
568 return Err(MemoryError::TreeInvariant {
569 message: format!("point '{}' is missing from child index", point.point_id),
570 });
571 }
572 }
573
574 Ok(())
575 }
576
577 fn require_point(&self, point_id: &str) -> Result<&MemoryPoint, MemoryError> {
578 self.nodes
579 .get(point_id)
580 .ok_or_else(|| MemoryError::PointNotFound {
581 point_id: point_id.to_string(),
582 })
583 }
584
585 fn require_point_mut(&mut self, point_id: &str) -> Result<&mut MemoryPoint, MemoryError> {
586 self.nodes
587 .get_mut(point_id)
588 .ok_or_else(|| MemoryError::PointNotFound {
589 point_id: point_id.to_string(),
590 })
591 }
592
593 fn ensure_point_exists(&self, point_id: &str) -> Result<(), MemoryError> {
594 if self.nodes.contains_key(point_id) {
595 Ok(())
596 } else {
597 Err(MemoryError::PointNotFound {
598 point_id: point_id.to_string(),
599 })
600 }
601 }
602
603 fn ensure_parent_exists(&self, parent_id: &str) -> Result<(), MemoryError> {
604 if self.nodes.contains_key(parent_id) {
605 Ok(())
606 } else {
607 Err(MemoryError::ParentNotFound {
608 parent_id: parent_id.to_string(),
609 })
610 }
611 }
612
613 fn attach_child(&mut self, parent_id: &str, child_id: &str) {
614 let child_ids = self.children.entry(parent_id.to_string()).or_default();
615 let index = child_ids.len();
616 child_ids.push(child_id.to_string());
617 self.child_positions.insert(
618 child_id.to_string(),
619 ChildPosition {
620 parent_id: parent_id.to_string(),
621 index,
622 },
623 );
624 }
625
626 fn detach_child(&mut self, child_id: &str) -> Result<(), MemoryError> {
627 let position =
628 self.child_positions
629 .remove(child_id)
630 .ok_or_else(|| MemoryError::TreeInvariant {
631 message: format!("child '{}' is missing from position index", child_id),
632 })?;
633
634 let remove_parent_entry;
635
636 {
637 let child_ids = self.children.get_mut(&position.parent_id).ok_or_else(|| {
638 MemoryError::TreeInvariant {
639 message: format!(
640 "parent '{}' is missing from children index",
641 position.parent_id
642 ),
643 }
644 })?;
645
646 if child_ids.get(position.index).map(String::as_str) != Some(child_id) {
647 return Err(MemoryError::TreeInvariant {
648 message: format!("child '{}' has inconsistent position index", child_id),
649 });
650 }
651
652 child_ids.swap_remove(position.index);
653
654 if position.index < child_ids.len() {
655 let moved_child_id = child_ids[position.index].clone();
656 if let Some(moved_position) = self.child_positions.get_mut(&moved_child_id) {
657 moved_position.index = position.index;
658 }
659 }
660
661 remove_parent_entry = child_ids.is_empty();
662 }
663
664 if remove_parent_entry {
665 self.children.remove(&position.parent_id);
666 }
667
668 Ok(())
669 }
670
671 fn is_ancestor(&self, ancestor_id: &str, point_id: &str) -> Result<bool, MemoryError> {
672 let mut current_id = Some(point_id.to_string());
673
674 while let Some(id) = current_id {
675 if id == ancestor_id {
676 return Ok(true);
677 }
678 current_id = self.require_point(&id)?.parent_id.clone();
679 }
680
681 Ok(false)
682 }
683
684 fn assert_reaches_headless(&self, point_id: &str) -> Result<(), MemoryError> {
685 let mut current_id = Some(point_id.to_string());
686 let mut steps = 0usize;
687 let max_steps = self.nodes.len();
688
689 while let Some(id) = current_id {
690 if steps > max_steps {
691 let parent_id = self
692 .nodes
693 .get(&id)
694 .and_then(|point| point.parent_id.clone())
695 .unwrap_or_default();
696 return Err(MemoryError::CycleDetected {
697 point_id: point_id.to_string(),
698 parent_id,
699 });
700 }
701
702 if id == HEADLESS_POINT_ID {
703 return Ok(());
704 }
705
706 current_id = self.require_point(&id)?.parent_id.clone();
707 steps += 1;
708 }
709
710 Err(MemoryError::TreeInvariant {
711 message: format!("point '{}' does not reach headless", point_id),
712 })
713 }
714
715 fn rebuild_indexes(&mut self) -> Result<(), MemoryError> {
716 self.children.clear();
717 self.child_positions.clear();
718
719 let links: Vec<(String, String)> = self
720 .nodes
721 .values()
722 .filter(|point| !point.is_headless())
723 .map(|point| {
724 let parent_id = point.parent_id.clone().ok_or(MemoryError::MissingParent)?;
725 Ok((parent_id, point.point_id.clone()))
726 })
727 .collect::<Result<_, MemoryError>>()?;
728
729 for (parent_id, child_id) in links {
730 self.ensure_parent_exists(&parent_id)?;
731 self.attach_child(&parent_id, &child_id);
732 }
733
734 Ok(())
735 }
736
737 fn matching_points_by_type(&self, query_type: &str) -> Vec<(u32, &MemoryPoint)> {
738 self.nodes
739 .values()
740 .filter(|point| !point.is_headless() && point.is_active())
741 .filter_map(|point| {
742 let score = type_match_score(point.types.as_deref()?, query_type)?;
743 Some((score, point))
744 })
745 .collect()
746 }
747
748 fn collect_neighborhood_points(
749 &self,
750 matched_point_id: &str,
751 relation_depth: usize,
752 max_nodes: usize,
753 ) -> Result<Vec<MemoryNeighborhoodPoint>, MemoryError> {
754 let mut result = Vec::new();
755 let mut seen = HashSet::new();
756
757 self.collect_ancestors(
758 matched_point_id,
759 relation_depth,
760 &mut seen,
761 &mut result,
762 max_nodes,
763 )?;
764 self.push_neighborhood_point(matched_point_id, 0, &mut seen, &mut result, max_nodes)?;
765 self.collect_descendants(
766 matched_point_id,
767 relation_depth,
768 &mut seen,
769 &mut result,
770 max_nodes,
771 )?;
772
773 Ok(result)
774 }
775
776 fn collect_ancestors(
777 &self,
778 matched_point_id: &str,
779 relation_depth: usize,
780 seen: &mut HashSet<String>,
781 result: &mut Vec<MemoryNeighborhoodPoint>,
782 max_nodes: usize,
783 ) -> Result<(), MemoryError> {
784 let mut ancestors = Vec::new();
785 let mut current_parent = self.require_point(matched_point_id)?.parent_id.clone();
786
787 for depth in 1..=relation_depth {
788 let Some(parent_id) = current_parent else {
789 break;
790 };
791
792 if parent_id == HEADLESS_POINT_ID {
793 break;
794 }
795
796 let parent = self.require_point(&parent_id)?;
797 ancestors.push((parent_id.clone(), -(depth as i32)));
798 current_parent = parent.parent_id.clone();
799 }
800
801 for (point_id, relation_depth) in ancestors.into_iter().rev() {
802 self.push_neighborhood_point(&point_id, relation_depth, seen, result, max_nodes)?;
803 }
804
805 Ok(())
806 }
807
808 fn collect_descendants(
809 &self,
810 matched_point_id: &str,
811 relation_depth: usize,
812 seen: &mut HashSet<String>,
813 result: &mut Vec<MemoryNeighborhoodPoint>,
814 max_nodes: usize,
815 ) -> Result<(), MemoryError> {
816 let mut queue = vec![(matched_point_id.to_string(), 0usize)];
817
818 while let Some((point_id, depth)) = queue.pop() {
819 if depth >= relation_depth || result.len() >= max_nodes {
820 continue;
821 }
822
823 if let Some(child_ids) = self.children.get(&point_id) {
824 for child_id in child_ids.iter().rev() {
825 let next_depth = depth + 1;
826 self.push_neighborhood_point(
827 child_id,
828 next_depth as i32,
829 seen,
830 result,
831 max_nodes,
832 )?;
833 queue.push((child_id.clone(), next_depth));
834
835 if result.len() >= max_nodes {
836 break;
837 }
838 }
839 }
840 }
841
842 Ok(())
843 }
844
845 fn push_neighborhood_point(
846 &self,
847 point_id: &str,
848 relation_depth: i32,
849 seen: &mut HashSet<String>,
850 result: &mut Vec<MemoryNeighborhoodPoint>,
851 max_nodes: usize,
852 ) -> Result<(), MemoryError> {
853 if result.len() >= max_nodes || !seen.insert(point_id.to_string()) {
854 return Ok(());
855 }
856
857 let point = self.require_point(point_id)?;
858 result.push(MemoryNeighborhoodPoint {
859 point_id: point.point_id.clone(),
860 parent_id: point.parent_id.clone(),
861 kind: point.kind.clone(),
862 relation_depth,
863 storage: point.storage.clone(),
864 types: point.types.clone(),
865 });
866
867 Ok(())
868 }
869}
870
871impl Default for MemoryTree {
872 fn default() -> Self {
873 Self::new()
874 }
875}
876
877fn normalize_query_types(possible_parent_types: &[String], max_parent_types: usize) -> Vec<String> {
878 let mut seen = HashSet::new();
879 let mut normalized = Vec::new();
880
881 for query_type in possible_parent_types {
882 let query_type = query_type.trim();
883 if query_type.is_empty() {
884 continue;
885 }
886
887 let key = normalize_search_term(query_type);
888 if seen.insert(key) {
889 normalized.push(query_type.to_string());
890 }
891
892 if normalized.len() >= max_parent_types {
893 break;
894 }
895 }
896
897 normalized
898}
899
900fn type_match_score(candidate_type: &str, query_type: &str) -> Option<u32> {
901 let candidate = normalize_search_term(candidate_type);
902 let query = normalize_search_term(query_type);
903
904 if candidate.is_empty() || query.is_empty() {
905 return None;
906 }
907
908 if candidate == query {
909 return Some(1000);
910 }
911
912 if candidate.contains(&query) || query.contains(&candidate) {
913 let shorter = candidate.chars().count().min(query.chars().count()) as u32;
914 return Some(800 + shorter.min(100));
915 }
916
917 let simplified_candidate = simplify_search_term(&candidate);
918 let simplified_query = simplify_search_term(&query);
919 if !simplified_candidate.is_empty() && !simplified_query.is_empty() {
920 if simplified_candidate == simplified_query {
921 return Some(760);
922 }
923 if simplified_candidate.contains(&simplified_query)
924 || simplified_query.contains(&simplified_candidate)
925 {
926 let shorter = simplified_candidate
927 .chars()
928 .count()
929 .min(simplified_query.chars().count()) as u32;
930 return Some(700 + shorter.min(50));
931 }
932 }
933
934 if let Some(score) = semantic_type_match_score(&candidate, &query) {
935 return Some(score);
936 }
937
938 let query_chars = query.chars().count();
939 if query_chars <= 4 {
940 let candidate_chars: HashSet<char> = candidate.chars().collect();
941 let query_chars_set: HashSet<char> = query.chars().collect();
942 let overlap = query_chars_set.intersection(&candidate_chars).count();
943 let min_len = query_chars_set.len().min(candidate_chars.len());
944
945 if min_len > 0 && overlap * 2 > min_len {
946 return Some(400 + overlap as u32);
947 }
948 }
949
950 None
951}
952
953fn semantic_type_match_score(candidate: &str, query: &str) -> Option<u32> {
954 let candidate_tokens = semantic_type_tokens(candidate);
955 let query_tokens = semantic_type_tokens(query);
956
957 if candidate_tokens.is_empty() || query_tokens.is_empty() {
958 return None;
959 }
960
961 let overlap = candidate_tokens.intersection(&query_tokens).count();
962 if overlap == 0 {
963 return None;
964 }
965
966 if query_tokens.contains("food") && !candidate_tokens.contains("food") {
967 return None;
968 }
969
970 Some(560 + (overlap as u32 * 30))
971}
972
973fn semantic_type_tokens(value: &str) -> HashSet<&'static str> {
974 let mut tokens = HashSet::new();
975
976 if contains_any(
977 value,
978 &[
979 "饮食", "食物", "食材", "面食", "菜肴", "菜品", "餐饮", "吃", "餐", "口味",
980 ],
981 ) {
982 tokens.insert("food");
983 }
984 if contains_any(value, &["忌口", "过敏", "不喜欢", "避开", "不能吃"]) {
985 tokens.insert("restriction");
986 }
987 if contains_any(
988 value,
989 &["前端", "界面", "设计", "ui", "颜色", "主题", "信息密度"],
990 ) {
991 tokens.insert("frontend_design");
992 }
993 if contains_any(value, &["rust", "错误", "thiserror", "anyhow"]) {
994 tokens.insert("rust_error");
995 }
996 if contains_any(value, &["中文", "英文", "语言", "交流"]) {
997 tokens.insert("language");
998 }
999 if contains_any(value, &["名字", "称呼", "叫我"]) {
1000 tokens.insert("identity");
1001 }
1002
1003 tokens
1004}
1005
1006fn contains_any(value: &str, terms: &[&str]) -> bool {
1007 terms.iter().any(|term| value.contains(term))
1008}
1009
1010fn normalize_search_term(value: &str) -> String {
1011 value
1012 .chars()
1013 .filter(|ch| !ch.is_whitespace())
1014 .flat_map(char::to_lowercase)
1015 .collect()
1016}
1017
1018fn simplify_search_term(value: &str) -> String {
1019 let mut simplified = value.to_string();
1020 for generic_term in [
1021 "用户",
1022 "我的",
1023 "喜欢的",
1024 "喜欢",
1025 "偏好",
1026 "类型",
1027 "种类",
1028 "类别",
1029 "信息",
1030 "的",
1031 ] {
1032 simplified = simplified.replace(generic_term, "");
1033 }
1034 simplified
1035}
1036
1037fn escape_model_text(value: &str) -> String {
1038 value
1039 .replace('&', "&")
1040 .replace('<', "<")
1041 .replace('>', ">")
1042 .replace('"', """)
1043}
1044
1045#[cfg(test)]
1046mod tests {
1047 use super::*;
1048
1049 #[test]
1050 fn new_tree_contains_headless_only() {
1051 let tree = MemoryTree::new();
1052
1053 assert_eq!(tree.len(), 1);
1054 assert_eq!(tree.memory_len(), 0);
1055 assert!(tree.contains(HEADLESS_POINT_ID));
1056 assert!(tree.validate().is_ok());
1057 }
1058
1059 #[test]
1060 fn inserts_root_and_child_points() {
1061 let mut tree = MemoryTree::new();
1062
1063 let root_id = tree
1064 .insert_root("likes noodles", "food preference")
1065 .unwrap();
1066 let child_id = tree
1067 .insert_child(&root_id, "likes zhajiangmian", "noodle preference")
1068 .unwrap();
1069
1070 assert_eq!(tree.root_ids(), vec![root_id.clone()]);
1071 assert_eq!(tree.child_ids(&root_id).unwrap(), vec![child_id]);
1072 assert_eq!(tree.memory_len(), 2);
1073 assert!(tree.validate().is_ok());
1074 }
1075
1076 #[test]
1077 fn updates_point_content_through_tree() {
1078 let mut tree = MemoryTree::new();
1079 let root_id = tree
1080 .insert_root("likes noodles", "noodle preference")
1081 .unwrap();
1082
1083 tree.update_types(&root_id, "food preference").unwrap();
1084 tree.update_storage(&root_id, "likes noodles and ribs")
1085 .unwrap();
1086
1087 let root = tree.get(&root_id).unwrap();
1088 assert_eq!(root.types.as_deref(), Some("food preference"));
1089 assert_eq!(root.storage.as_deref(), Some("likes noodles and ribs"));
1090 }
1091
1092 #[test]
1093 fn reparents_point_with_index_updates() {
1094 let mut tree = MemoryTree::new();
1095 let root_a = tree.insert_root("food", "food preference").unwrap();
1096 let root_b = tree.insert_root("sports", "sports preference").unwrap();
1097 let child = tree
1098 .insert_child(&root_a, "likes tennis", "tennis preference")
1099 .unwrap();
1100
1101 tree.reparent(&child, &root_b).unwrap();
1102
1103 assert!(tree.child_ids(&root_a).unwrap().is_empty());
1104 assert_eq!(tree.child_ids(&root_b).unwrap(), vec![child.clone()]);
1105 assert_eq!(
1106 tree.get(&child).unwrap().parent_id.as_deref(),
1107 Some(root_b.as_str())
1108 );
1109 assert!(tree.validate().is_ok());
1110 }
1111
1112 #[test]
1113 fn returns_subtree_from_start_node() {
1114 let mut tree = MemoryTree::new();
1115 let root = tree.insert_root("food", "food preference").unwrap();
1116 let child_a = tree
1117 .insert_child(&root, "likes noodles", "noodle preference")
1118 .unwrap();
1119 let child_b = tree
1120 .insert_child(&root, "likes ribs", "dish preference")
1121 .unwrap();
1122 let grandchild = tree
1123 .insert_child(&child_a, "likes zhajiangmian", "specific noodle preference")
1124 .unwrap();
1125
1126 let subtree = tree.subtree_ids(&root).unwrap();
1127
1128 assert_eq!(subtree.len(), 4);
1129 assert_eq!(subtree[0], root);
1130 assert!(subtree.contains(&child_a));
1131 assert!(subtree.contains(&child_b));
1132 assert!(subtree.contains(&grandchild));
1133 }
1134
1135 #[test]
1136 fn returns_path_from_headless_to_point() {
1137 let mut tree = MemoryTree::new();
1138 let root = tree.insert_root("food", "food preference").unwrap();
1139 let child = tree
1140 .insert_child(&root, "likes noodles", "noodle preference")
1141 .unwrap();
1142
1143 let path = tree.path_ids(&child).unwrap();
1144
1145 assert_eq!(
1146 path,
1147 vec![HEADLESS_POINT_ID.to_string(), root.clone(), child.clone()]
1148 );
1149 }
1150
1151 #[test]
1152 fn prevents_cycle_when_reparenting() {
1153 let mut tree = MemoryTree::new();
1154 let root = tree.insert_root("food", "food preference").unwrap();
1155 let child = tree
1156 .insert_child(&root, "likes noodles", "noodle preference")
1157 .unwrap();
1158 let grandchild = tree
1159 .insert_child(&child, "likes zhajiangmian", "specific noodle preference")
1160 .unwrap();
1161
1162 let result = tree.reparent(&root, &grandchild);
1163
1164 assert!(matches!(result, Err(MemoryError::CycleDetected { .. })));
1165 }
1166
1167 #[test]
1168 fn rejects_missing_parent() {
1169 let mut tree = MemoryTree::new();
1170
1171 let result = tree.insert_child("missing", "likes noodles", "noodle preference");
1172
1173 assert!(matches!(result, Err(MemoryError::ParentNotFound { .. })));
1174 }
1175
1176 #[test]
1177 fn rebuilds_tree_from_points() {
1178 let mut original = MemoryTree::new();
1179 let root = original.insert_root("food", "food preference").unwrap();
1180 let child = original
1181 .insert_child(&root, "likes noodles", "noodle preference")
1182 .unwrap();
1183
1184 let restored = MemoryTree::from_points(original.to_points()).unwrap();
1185
1186 assert_eq!(restored.memory_len(), 2);
1187 assert_eq!(restored.child_ids(&root).unwrap(), vec![child]);
1188 assert!(restored.validate().is_ok());
1189 }
1190
1191 #[test]
1192 fn preserves_headless_point_when_rebuilding_from_points() {
1193 let mut points = MemoryTree::new().to_points();
1194 let headless = points
1195 .iter_mut()
1196 .find(|point| point.point_id == HEADLESS_POINT_ID)
1197 .unwrap();
1198 headless.created_at = "stable-created-at".to_string();
1199 headless.updated_at = "stable-updated-at".to_string();
1200
1201 let restored = MemoryTree::from_points(points).unwrap();
1202 let restored_headless = restored.get(HEADLESS_POINT_ID).unwrap();
1203
1204 assert_eq!(restored_headless.created_at, "stable-created-at");
1205 assert_eq!(restored_headless.updated_at, "stable-updated-at");
1206 }
1207
1208 #[test]
1209 fn finds_type_neighborhood_with_parent_self_and_children() {
1210 let mut tree = MemoryTree::new();
1211 let food = tree.insert_root("用户喜欢吃面食和菜", "饮食偏好").unwrap();
1212 let noodles = tree
1213 .insert_child(&food, "用户喜欢吃炸酱面", "面食偏好")
1214 .unwrap();
1215 let zhajiangmian = tree
1216 .insert_child(&noodles, "用户特别喜欢炸酱面", "炸酱面偏好")
1217 .unwrap();
1218 let swimming = tree.insert_root("用户喜欢游泳", "运动偏好").unwrap();
1219
1220 let options = MemoryTypeSearchOptions {
1221 relation_depth: 1,
1222 max_parent_types: 3,
1223 max_matches_per_type: 1,
1224 max_neighborhood_nodes: 16,
1225 };
1226 let neighborhoods = tree
1227 .find_type_neighborhoods(&["面食".to_string()], &options)
1228 .unwrap();
1229
1230 assert_eq!(neighborhoods.len(), 1);
1231 assert_eq!(neighborhoods[0].matched_point_id, noodles);
1232
1233 let point_ids: Vec<&str> = neighborhoods[0]
1234 .points
1235 .iter()
1236 .map(|point| point.point_id.as_str())
1237 .collect();
1238
1239 assert!(point_ids.contains(&food.as_str()));
1240 assert!(point_ids.contains(&neighborhoods[0].matched_point_id.as_str()));
1241 assert!(point_ids.contains(&zhajiangmian.as_str()));
1242 assert!(!point_ids.contains(&swimming.as_str()));
1243
1244 assert!(neighborhoods[0]
1245 .points
1246 .iter()
1247 .any(|point| point.point_id == food && point.relation_depth == -1));
1248 assert!(neighborhoods[0]
1249 .points
1250 .iter()
1251 .any(|point| point.point_id == zhajiangmian && point.relation_depth == 1));
1252 }
1253
1254 #[test]
1255 fn limits_possible_parent_types_before_searching() {
1256 let mut tree = MemoryTree::new();
1257 let food = tree.insert_root("用户喜欢吃面食", "饮食偏好").unwrap();
1258 let sport = tree.insert_root("用户喜欢游泳", "运动偏好").unwrap();
1259
1260 let options = MemoryTypeSearchOptions {
1261 relation_depth: 0,
1262 max_parent_types: 1,
1263 max_matches_per_type: 5,
1264 max_neighborhood_nodes: 16,
1265 };
1266
1267 let neighborhoods = tree
1268 .find_type_neighborhoods(&["饮食".to_string(), "运动".to_string()], &options)
1269 .unwrap();
1270
1271 assert_eq!(neighborhoods.len(), 1);
1272 assert_eq!(neighborhoods[0].matched_point_id, food);
1273 assert_ne!(neighborhoods[0].matched_point_id, sport);
1274 }
1275
1276 #[test]
1277 fn formats_type_neighborhoods_for_model() {
1278 let mut tree = MemoryTree::new();
1279 let food = tree.insert_root("用户喜欢吃面食", "饮食偏好").unwrap();
1280 let options = MemoryTypeSearchOptions::default();
1281 let neighborhoods = tree
1282 .find_type_neighborhoods(&["饮食".to_string()], &options)
1283 .unwrap();
1284
1285 let formatted = MemoryTree::format_type_neighborhoods_for_model(&neighborhoods);
1286
1287 assert!(formatted.contains("<existing-memory-candidates>"));
1288 assert!(formatted.contains(&food));
1289 assert!(formatted.contains("饮食偏好"));
1290 assert!(formatted.contains("用户喜欢吃面食"));
1291 }
1292
1293 #[test]
1294 fn falls_back_to_roots_when_type_search_has_no_matches() {
1295 let mut tree = MemoryTree::new();
1296 let noodles = tree
1297 .insert_root("用户喜欢吃炸酱面", "用户喜欢的面食")
1298 .unwrap();
1299 let swimming = tree.insert_root("用户喜欢游泳", "运动偏好").unwrap();
1300 let options = MemoryTypeSearchOptions {
1301 relation_depth: 0,
1302 max_parent_types: 3,
1303 max_matches_per_type: 1,
1304 max_neighborhood_nodes: 16,
1305 };
1306
1307 let neighborhoods = tree
1308 .find_type_neighborhoods(&["菜肴偏好".to_string()], &options)
1309 .unwrap();
1310
1311 assert_eq!(neighborhoods.len(), 1);
1312 assert_eq!(neighborhoods[0].matched_point_id, noodles);
1313 assert_ne!(neighborhoods[0].matched_point_id, swimming);
1314 }
1315
1316 #[test]
1317 fn recalls_storage_from_matched_subtree() {
1318 let mut tree = MemoryTree::new();
1319 let food = tree
1320 .insert_root("用户喜欢吃炸酱面和糖醋排骨等食物", "用户喜欢的食物")
1321 .unwrap();
1322 let ribs = tree
1323 .insert_child(&food, "用户喜欢吃糖醋排骨", "用户喜欢吃的菜")
1324 .unwrap();
1325 let _noodles = tree
1326 .insert_child(&food, "用户喜欢吃炸酱面", "用户喜欢吃的面食")
1327 .unwrap();
1328 let _sport = tree.insert_root("用户周末喜欢跑步", "运动习惯").unwrap();
1329
1330 let branches = tree
1331 .recall_subtree_memories(
1332 &["食物偏好".to_string()],
1333 &MemoryRecallOptions {
1334 max_types: 3,
1335 max_matches_per_type: 2,
1336 max_returned_memories: 16,
1337 },
1338 )
1339 .unwrap();
1340
1341 assert_eq!(branches.len(), 1);
1342 assert_eq!(branches[0].matched_point_id, food);
1343 assert!(branches[0]
1344 .memories
1345 .contains(&"用户喜欢吃炸酱面和糖醋排骨等食物".to_string()));
1346 assert!(branches[0]
1347 .memories
1348 .contains(&"用户喜欢吃糖醋排骨".to_string()));
1349 assert!(branches[0]
1350 .memories
1351 .contains(&"用户喜欢吃炸酱面".to_string()));
1352 assert!(!branches[0]
1353 .memories
1354 .contains(&"用户周末喜欢跑步".to_string()));
1355
1356 tree.deactivate(&ribs).unwrap();
1357 let branches = tree
1358 .recall_subtree_memories(&["食物偏好".to_string()], &MemoryRecallOptions::default())
1359 .unwrap();
1360 assert!(!branches[0]
1361 .memories
1362 .contains(&"用户喜欢吃糖醋排骨".to_string()));
1363 }
1364
1365 #[test]
1366 fn recalls_related_food_roots_by_semantic_type() {
1367 let mut tree = MemoryTree::new();
1368 tree.insert_root("用户喜欢吃炸酱面", "用户喜欢吃的面食")
1369 .unwrap();
1370 tree.insert_root("用户不喜欢吃香菜", "用户不喜欢的食材")
1371 .unwrap();
1372 tree.insert_root("用户对花生过敏,饮食建议必须避开花生", "食物过敏信息")
1373 .unwrap();
1374 tree.insert_root("用户周末喜欢跑步", "运动习惯").unwrap();
1375
1376 let branches = tree
1377 .recall_subtree_memories(
1378 &["饮食偏好".to_string(), "忌口偏好".to_string()],
1379 &MemoryRecallOptions {
1380 max_types: 3,
1381 max_matches_per_type: 8,
1382 max_returned_memories: 16,
1383 },
1384 )
1385 .unwrap();
1386 let memories = branches
1387 .iter()
1388 .flat_map(|branch| branch.memories.iter())
1389 .cloned()
1390 .collect::<Vec<_>>()
1391 .join("\n");
1392
1393 assert!(memories.contains("炸酱面"));
1394 assert!(memories.contains("香菜"));
1395 assert!(memories.contains("花生"));
1396 assert!(!memories.contains("跑步"));
1397 }
1398}