Skip to main content

subcog/hooks/search_intent/
mod.rs

1//! Search intent detection for proactive memory surfacing.
2//!
3//! Detects user intent to search for information and extracts topics for memory injection.
4//!
5//! # Architecture
6//!
7//! This module is organized into focused submodules:
8//!
9//! - [`types`]: Core types (`SearchIntentType`, `DetectionSource`, `SearchIntent`)
10//! - [`keyword`]: Fast pattern-based detection (<10ms)
11//! - [`llm`]: LLM-powered classification for higher accuracy
12//! - [`hybrid`]: Combined detection with timeout support
13//!
14//! # Detection Modes
15//!
16//! | Mode | Latency | Accuracy | Use Case |
17//! |------|---------|----------|----------|
18//! | Keyword | <10ms | Good | Default, always available |
19//! | LLM | ~200ms | Excellent | When LLM provider configured |
20//! | Hybrid | <200ms | Best | Combines both with fallback |
21//!
22//! # Intent Types and Detection
23//!
24//! | Intent | Trigger Patterns | Namespace Weights |
25//! |--------|------------------|-------------------|
26//! | `HowTo` | "how do I", "how to", "implement", "create" | patterns (2.0), learnings (1.5) |
27//! | `Location` | "where is", "find", "locate" | context (1.5), patterns (1.2) |
28//! | `Explanation` | "what is", "explain", "describe" | decisions (1.5), context (1.2) |
29//! | `Comparison` | "difference between", "vs", "compare" | decisions (2.0), patterns (1.5) |
30//! | `Troubleshoot` | "error", "fix", "not working", "debug" | blockers (2.0), learnings (1.5) |
31//! | `General` | "search", "show me" | All namespaces weighted equally |
32//!
33//! # Detection Flow
34//!
35//! ```text
36//! User Prompt
37//!     │
38//!     ├─► Keyword Detection (<10ms)
39//!     │       │
40//!     │       └─► Intent + Confidence + Topics
41//!     │
42//!     └─► LLM Classification (200ms timeout) [optional]
43//!             │
44//!             └─► Intent + Confidence + Topics
45//!                     │
46//!                     └─► Merge Results (hybrid mode)
47//!                             │
48//!                             └─► Final Intent + Topics
49//! ```
50//!
51//! # Confidence-Based Memory Injection
52//!
53//! | Confidence | Memory Count | Behavior |
54//! |------------|--------------|----------|
55//! | ≥ 0.8 (high) | 15 memories | Full context injection |
56//! | ≥ 0.5 (medium) | 10 memories | Standard injection |
57//! | < 0.5 (low) | 5 memories | Minimal injection |
58//!
59//! # Configuration
60//!
61//! | Environment Variable | Description | Default |
62//! |---------------------|-------------|---------|
63//! | `SUBCOG_SEARCH_INTENT_ENABLED` | Enable intent detection | `true` |
64//! | `SUBCOG_SEARCH_INTENT_USE_LLM` | Enable LLM classification | `true` |
65//! | `SUBCOG_SEARCH_INTENT_LLM_TIMEOUT_MS` | LLM timeout | `200` |
66//! | `SUBCOG_SEARCH_INTENT_MIN_CONFIDENCE` | Minimum confidence | `0.5` |
67//!
68//! # Examples
69//!
70//! ```rust,ignore
71//! use subcog::hooks::search_intent::{detect_search_intent, SearchIntentType};
72//!
73//! // Simple keyword detection
74//! if let Some(intent) = detect_search_intent("How do I implement OAuth?") {
75//!     assert_eq!(intent.intent_type, SearchIntentType::HowTo);
76//!     println!("Detected: {} with confidence {}", intent.intent_type, intent.confidence);
77//! }
78//! ```
79
80mod hybrid;
81mod keyword;
82mod llm;
83mod types;
84
85// Re-export public types
86pub use types::{DetectionSource, SearchIntent, SearchIntentType};
87
88// Re-export detection functions
89pub use hybrid::{detect_search_intent_hybrid, detect_search_intent_with_timeout};
90pub use keyword::detect_search_intent;
91pub use llm::classify_intent_with_llm;
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_module_exports() {
99        // Verify all public items are accessible
100        let intent_type = SearchIntentType::HowTo;
101        assert_eq!(intent_type.as_str(), "howto");
102
103        let source = DetectionSource::Keyword;
104        assert_eq!(source.as_str(), "keyword");
105
106        let intent = SearchIntent::default();
107        assert_eq!(intent.intent_type, SearchIntentType::General);
108
109        // Verify functions are accessible
110        let result = detect_search_intent("How do I test?");
111        assert!(result.is_some());
112    }
113
114    #[test]
115    fn test_end_to_end_keyword_detection() {
116        let prompts_and_intents = [
117            ("How do I implement caching?", SearchIntentType::HowTo),
118            ("Where is the config file?", SearchIntentType::Location),
119            (
120                "What is dependency injection?",
121                SearchIntentType::Explanation,
122            ),
123            (
124                "What's the difference between sync and async?",
125                SearchIntentType::Comparison,
126            ),
127            (
128                "Why am I getting this error?",
129                SearchIntentType::Troubleshoot,
130            ),
131        ];
132
133        for (prompt, expected_intent) in prompts_and_intents {
134            let result = detect_search_intent(prompt);
135            assert!(result.is_some(), "Should detect intent for: {prompt}");
136            assert_eq!(
137                result.unwrap().intent_type,
138                expected_intent,
139                "Wrong intent for: {prompt}"
140            );
141        }
142    }
143
144    #[test]
145    fn test_namespace_weights_vary_by_intent() {
146        let howto_weights = SearchIntentType::HowTo.namespace_weights();
147        let troubleshoot_weights = SearchIntentType::Troubleshoot.namespace_weights();
148
149        // HowTo should prioritize patterns
150        assert!(
151            howto_weights
152                .iter()
153                .any(|(ns, w)| *ns == "patterns" && *w >= 2.0)
154        );
155
156        // Troubleshoot should prioritize blockers
157        assert!(
158            troubleshoot_weights
159                .iter()
160                .any(|(ns, w)| *ns == "blockers" && *w >= 2.0)
161        );
162    }
163}