Skip to main content

otherone_memory/
tree.rs

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('&', "&amp;")
1040        .replace('<', "&lt;")
1041        .replace('>', "&gt;")
1042        .replace('"', "&quot;")
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}