rag_plusplus_core/trajectory/chain/
links.rs

1//! Cross-Chain Links
2//!
3//! Support for linking related content across different conversation chains.
4//! Enables knowledge transfer detection and cross-conversation navigation.
5
6use crate::trajectory::graph::NodeId;
7use super::manager::ChainId;
8
9/// Strength of a cross-chain link.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct LinkStrength {
12    /// Semantic similarity (0.0 - 1.0)
13    pub semantic: f32,
14    /// Temporal proximity (0.0 - 1.0)
15    pub temporal: f32,
16    /// Thematic overlap (0.0 - 1.0)
17    pub thematic: f32,
18    /// Combined score
19    pub combined: f32,
20}
21
22impl LinkStrength {
23    /// Create a new link strength with all factors.
24    pub fn new(semantic: f32, temporal: f32, thematic: f32) -> Self {
25        // Default weighting: semantic 0.5, temporal 0.2, thematic 0.3
26        let combined = 0.5 * semantic + 0.2 * temporal + 0.3 * thematic;
27        Self {
28            semantic,
29            temporal,
30            thematic,
31            combined,
32        }
33    }
34
35    /// Create link strength from semantic similarity only.
36    pub fn from_semantic(similarity: f32) -> Self {
37        Self::new(similarity, 0.5, 0.5)
38    }
39
40    /// Check if this is a strong link (above threshold).
41    pub fn is_strong(&self, threshold: f32) -> bool {
42        self.combined >= threshold
43    }
44}
45
46impl Default for LinkStrength {
47    fn default() -> Self {
48        Self {
49            semantic: 0.0,
50            temporal: 0.0,
51            thematic: 0.0,
52            combined: 0.0,
53        }
54    }
55}
56
57/// A link between nodes in different chains.
58///
59/// Represents semantic, temporal, or thematic relationships
60/// between content in different conversations.
61#[derive(Debug, Clone)]
62pub struct CrossChainLink {
63    /// Source chain
64    pub source_chain: ChainId,
65    /// Source node
66    pub source_node: NodeId,
67    /// Target chain
68    pub target_chain: ChainId,
69    /// Target node
70    pub target_node: NodeId,
71    /// Link strength
72    pub strength: LinkStrength,
73    /// Link type (semantic, reference, continuation, etc.)
74    pub link_type: CrossChainLinkType,
75    /// Optional description
76    pub description: Option<String>,
77}
78
79/// Types of cross-chain links.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
81pub enum CrossChainLinkType {
82    /// Semantic similarity (similar content)
83    Semantic,
84    /// Explicit reference ("as we discussed before")
85    Reference,
86    /// Continuation of topic
87    Continuation,
88    /// Related concept
89    Related,
90    /// Same topic, different approach
91    Alternative,
92    /// Knowledge transfer ("similar to project X")
93    KnowledgeTransfer,
94}
95
96impl CrossChainLink {
97    /// Create a new cross-chain link.
98    pub fn new(
99        source_chain: ChainId,
100        source_node: NodeId,
101        target_chain: ChainId,
102        target_node: NodeId,
103        strength: LinkStrength,
104        link_type: CrossChainLinkType,
105    ) -> Self {
106        Self {
107            source_chain,
108            source_node,
109            target_chain,
110            target_node,
111            strength,
112            link_type,
113            description: None,
114        }
115    }
116
117    /// Create a semantic link.
118    pub fn semantic(
119        source_chain: ChainId,
120        source_node: NodeId,
121        target_chain: ChainId,
122        target_node: NodeId,
123        similarity: f32,
124    ) -> Self {
125        Self::new(
126            source_chain,
127            source_node,
128            target_chain,
129            target_node,
130            LinkStrength::from_semantic(similarity),
131            CrossChainLinkType::Semantic,
132        )
133    }
134
135    /// Add a description to this link.
136    pub fn with_description(mut self, description: impl Into<String>) -> Self {
137        self.description = Some(description.into());
138        self
139    }
140
141    /// Check if this link is bidirectional.
142    pub fn is_bidirectional(&self) -> bool {
143        // Semantic and related links are typically bidirectional
144        matches!(
145            self.link_type,
146            CrossChainLinkType::Semantic | CrossChainLinkType::Related
147        )
148    }
149
150    /// Get the reverse link (swap source and target).
151    pub fn reverse(&self) -> Self {
152        Self {
153            source_chain: self.target_chain.clone(),
154            source_node: self.target_node,
155            target_chain: self.source_chain.clone(),
156            target_node: self.source_node,
157            strength: self.strength,
158            link_type: self.link_type,
159            description: self.description.clone(),
160        }
161    }
162}
163
164/// Find cross-chain links between nodes based on embeddings.
165///
166/// This is a placeholder for the actual implementation which would:
167/// 1. Compute embedding similarities between nodes across chains
168/// 2. Apply thresholds to filter weak links
169/// 3. Detect knowledge transfer patterns
170///
171/// # Arguments
172///
173/// * `chain_embeddings` - Map of chain_id -> (node_id, embedding)
174/// * `threshold` - Minimum similarity threshold
175///
176/// # Returns
177///
178/// Vector of cross-chain links above the threshold.
179pub fn find_cross_chain_links(
180    _chain_embeddings: &[(ChainId, Vec<(NodeId, Vec<f32>)>)],
181    _threshold: f32,
182) -> Vec<CrossChainLink> {
183    // Placeholder implementation
184    // In production, this would:
185    // 1. Build an index of all embeddings
186    // 2. For each embedding, find k-nearest neighbors from other chains
187    // 3. Create links for those above threshold
188    Vec::new()
189}
190
191/// Knowledge transfer patterns to detect.
192///
193/// These patterns indicate when a user is referencing prior knowledge
194/// from another conversation.
195pub const KNOWLEDGE_TRANSFER_PATTERNS: &[&str] = &[
196    "as we discussed",
197    "like we did before",
198    "similar to what we",
199    "remember when we",
200    "building on the",
201    "like in the",
202    "same as before",
203    "just like last time",
204    "from our previous",
205    "we already covered",
206];
207
208/// Detect knowledge transfer patterns in text.
209pub fn detect_knowledge_transfer(text: &str) -> Option<&'static str> {
210    let lower = text.to_lowercase();
211    for pattern in KNOWLEDGE_TRANSFER_PATTERNS {
212        if lower.contains(pattern) {
213            return Some(pattern);
214        }
215    }
216    None
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_link_strength() {
225        let strength = LinkStrength::new(0.8, 0.5, 0.6);
226        assert!(strength.combined > 0.0);
227        assert!(strength.is_strong(0.5));
228        assert!(!strength.is_strong(0.9));
229    }
230
231    #[test]
232    fn test_cross_chain_link() {
233        let link = CrossChainLink::semantic(
234            "chain1".to_string(),
235            1,
236            "chain2".to_string(),
237            2,
238            0.85,
239        );
240
241        assert_eq!(link.source_chain, "chain1");
242        assert_eq!(link.target_chain, "chain2");
243        assert!(link.is_bidirectional());
244
245        let reverse = link.reverse();
246        assert_eq!(reverse.source_chain, "chain2");
247        assert_eq!(reverse.target_chain, "chain1");
248    }
249
250    #[test]
251    fn test_knowledge_transfer_detection() {
252        assert!(detect_knowledge_transfer("As we discussed before, this should work").is_some());
253        assert!(detect_knowledge_transfer("Similar to what we did in the auth project").is_some());
254        assert!(detect_knowledge_transfer("This is completely new").is_none());
255    }
256
257    #[test]
258    fn test_link_with_description() {
259        let link = CrossChainLink::semantic(
260            "chain1".to_string(),
261            1,
262            "chain2".to_string(),
263            2,
264            0.85,
265        ).with_description("Both discuss authentication");
266
267        assert_eq!(link.description, Some("Both discuss authentication".to_string()));
268    }
269}