Skip to main content

terraphim_router/
keyword.rs

1//! Keyword-based capability extraction for routing.
2//!
3//! This module provides keyword matching to extract capabilities from text,
4//! enabling intelligent routing based on prompt content.
5
6use std::collections::HashSet;
7use terraphim_types::capability::Capability;
8
9/// Maps keywords to capabilities
10#[derive(Debug, Clone)]
11pub struct KeywordRouter {
12    mappings: Vec<KeywordMapping>,
13}
14
15#[derive(Debug, Clone)]
16struct KeywordMapping {
17    keywords: Vec<String>,
18    capability: Capability,
19    priority: u32,
20}
21
22impl KeywordRouter {
23    /// Create a new KeywordRouter with default mappings
24    pub fn new() -> Self {
25        Self {
26            mappings: Self::default_mappings(),
27        }
28    }
29
30    /// Create with custom mappings
31    pub fn with_mappings(mappings: Vec<(Vec<String>, Capability, u32)>) -> Self {
32        let mappings = mappings
33            .into_iter()
34            .map(|(keywords, capability, priority)| KeywordMapping {
35                keywords,
36                capability,
37                priority,
38            })
39            .collect();
40
41        Self { mappings }
42    }
43
44    /// Extract capabilities from text
45    pub fn extract_capabilities(&self, text: &str) -> Vec<Capability> {
46        let text_lower = text.to_lowercase();
47        let mut caps = HashSet::new();
48        let mut matched_keywords = Vec::new();
49
50        for mapping in &self.mappings {
51            for keyword in &mapping.keywords {
52                if text_lower.contains(&keyword.to_lowercase()) {
53                    caps.insert(mapping.capability);
54                    matched_keywords.push((keyword.clone(), mapping.priority));
55                    break; // Only match once per mapping
56                }
57            }
58        }
59
60        // Sort by priority (higher priority first)
61        matched_keywords.sort_by(|a, b| b.1.cmp(&a.1));
62
63        caps.into_iter().collect()
64    }
65
66    /// Check if text contains any capability-indicating keywords
67    pub fn has_keywords(&self, text: &str) -> bool {
68        !self.extract_capabilities(text).is_empty()
69    }
70
71    /// Get the default keyword mappings
72    fn default_mappings() -> Vec<KeywordMapping> {
73        vec![
74            // Deep thinking (high priority)
75            KeywordMapping {
76                keywords: vec![
77                    "think".to_string(),
78                    "thinking".to_string(),
79                    "reason".to_string(),
80                    "reasoning".to_string(),
81                    "analyze deeply".to_string(),
82                    "complex analysis".to_string(),
83                    "deep dive".to_string(),
84                    "carefully consider".to_string(),
85                ],
86                capability: Capability::DeepThinking,
87                priority: 100,
88            },
89            // Fast thinking (lower priority)
90            KeywordMapping {
91                keywords: vec![
92                    "quick".to_string(),
93                    "fast".to_string(),
94                    "simple".to_string(),
95                    "brief".to_string(),
96                    "short".to_string(),
97                    "summary".to_string(),
98                ],
99                capability: Capability::FastThinking,
100                priority: 50,
101            },
102            // Code generation
103            KeywordMapping {
104                keywords: vec![
105                    "implement".to_string(),
106                    "code".to_string(),
107                    "write function".to_string(),
108                    "create".to_string(),
109                    "build".to_string(),
110                    "develop".to_string(),
111                    "program".to_string(),
112                ],
113                capability: Capability::CodeGeneration,
114                priority: 90,
115            },
116            // Code review
117            KeywordMapping {
118                keywords: vec![
119                    "review".to_string(),
120                    "check".to_string(),
121                    "audit".to_string(),
122                    "inspect".to_string(),
123                    "evaluate code".to_string(),
124                ],
125                capability: Capability::CodeReview,
126                priority: 85,
127            },
128            // Architecture
129            KeywordMapping {
130                keywords: vec![
131                    "design".to_string(),
132                    "architecture".to_string(),
133                    "structure".to_string(),
134                    "system design".to_string(),
135                    "pattern".to_string(),
136                ],
137                capability: Capability::Architecture,
138                priority: 88,
139            },
140            // Testing
141            KeywordMapping {
142                keywords: vec![
143                    "test".to_string(),
144                    "testing".to_string(),
145                    "unit test".to_string(),
146                    "integration test".to_string(),
147                    "spec".to_string(),
148                ],
149                capability: Capability::Testing,
150                priority: 80,
151            },
152            // Refactoring
153            KeywordMapping {
154                keywords: vec![
155                    "refactor".to_string(),
156                    "restructure".to_string(),
157                    "clean up".to_string(),
158                    "improve".to_string(),
159                    "optimize code".to_string(),
160                ],
161                capability: Capability::Refactoring,
162                priority: 75,
163            },
164            // Documentation
165            KeywordMapping {
166                keywords: vec![
167                    "document".to_string(),
168                    "documentation".to_string(),
169                    "readme".to_string(),
170                    "explain how".to_string(),
171                    "guide".to_string(),
172                ],
173                capability: Capability::Documentation,
174                priority: 70,
175            },
176            // Explanation
177            KeywordMapping {
178                keywords: vec![
179                    "explain".to_string(),
180                    "clarify".to_string(),
181                    "describe".to_string(),
182                    "what is".to_string(),
183                    "how does".to_string(),
184                ],
185                capability: Capability::Explanation,
186                priority: 65,
187            },
188            // Security audit
189            KeywordMapping {
190                keywords: vec![
191                    "security".to_string(),
192                    "secure".to_string(),
193                    "vulnerability".to_string(),
194                    "audit".to_string(),
195                    "threat".to_string(),
196                    "sanitize".to_string(),
197                ],
198                capability: Capability::SecurityAudit,
199                priority: 95,
200            },
201            // Performance
202            KeywordMapping {
203                keywords: vec![
204                    "performance".to_string(),
205                    "optimize".to_string(),
206                    "speed".to_string(),
207                    "fast".to_string(),
208                    "efficient".to_string(),
209                    "benchmark".to_string(),
210                ],
211                capability: Capability::Performance,
212                priority: 78,
213            },
214        ]
215    }
216}
217
218impl Default for KeywordRouter {
219    fn default() -> Self {
220        Self::new()
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn test_extract_deep_thinking() {
230        let router = KeywordRouter::new();
231
232        let caps =
233            router.extract_capabilities("I need you to think carefully about this complex problem");
234
235        assert!(caps.contains(&Capability::DeepThinking));
236    }
237
238    #[test]
239    fn test_extract_code_generation() {
240        let router = KeywordRouter::new();
241
242        let caps = router.extract_capabilities("Please implement a function to parse JSON");
243
244        assert!(caps.contains(&Capability::CodeGeneration));
245    }
246
247    #[test]
248    fn test_extract_security_audit() {
249        let router = KeywordRouter::new();
250
251        let caps = router.extract_capabilities("Audit this code for security vulnerabilities");
252
253        assert!(caps.contains(&Capability::SecurityAudit));
254    }
255
256    #[test]
257    fn test_multiple_capabilities() {
258        let router = KeywordRouter::new();
259
260        let caps = router.extract_capabilities(
261            "Implement a secure authentication system and write tests for it",
262        );
263
264        assert!(caps.contains(&Capability::CodeGeneration));
265        assert!(caps.contains(&Capability::SecurityAudit));
266        assert!(caps.contains(&Capability::Testing));
267    }
268
269    #[test]
270    fn test_no_capabilities() {
271        let router = KeywordRouter::new();
272
273        let caps = router.extract_capabilities("Hello, how are you today?");
274
275        assert!(caps.is_empty());
276    }
277
278    #[test]
279    fn test_case_insensitive() {
280        let router = KeywordRouter::new();
281
282        let caps1 = router.extract_capabilities("IMPLEMENT this feature");
283        let caps2 = router.extract_capabilities("implement this feature");
284        let caps3 = router.extract_capabilities("Implement this feature");
285
286        assert_eq!(caps1, caps2);
287        assert_eq!(caps2, caps3);
288    }
289
290    #[test]
291    fn test_has_keywords() {
292        let router = KeywordRouter::new();
293
294        assert!(router.has_keywords("Think about this problem"));
295        assert!(!router.has_keywords("Hello world"));
296    }
297}
298
299#[cfg(test)]
300mod proptest_tests {
301    use super::*;
302    use proptest::prelude::*;
303
304    proptest! {
305        #[test]
306        fn extract_capabilities_never_panics(text in "\\PC{0,500}") {
307            let router = KeywordRouter::new();
308            // Should never panic, regardless of input
309            let _ = router.extract_capabilities(&text);
310        }
311
312        #[test]
313        fn extract_capabilities_returns_subset_of_all(text in "\\PC{0,200}") {
314            let router = KeywordRouter::new();
315            let all_caps = Capability::all();
316            let extracted = router.extract_capabilities(&text);
317
318            for cap in &extracted {
319                prop_assert!(all_caps.contains(cap));
320            }
321        }
322
323        #[test]
324        fn has_keywords_consistent_with_extract(text in "\\PC{0,200}") {
325            let router = KeywordRouter::new();
326            let has = router.has_keywords(&text);
327            let extracted = router.extract_capabilities(&text);
328
329            prop_assert_eq!(has, !extracted.is_empty());
330        }
331    }
332}