Skip to main content

brainwires_cognition/prompting/
generator.rs

1//! Prompt Generation with SEAL+BKS+PKS Integration
2//!
3//! This module generates dynamic prompts using SEAL quality scores,
4//! BKS shared knowledge, and PKS user preferences.
5
6use super::clustering::TaskClusterManager;
7use super::library::TechniqueLibrary;
8use super::techniques::{
9    ComplexityLevel, PromptingTechnique, TechniqueCategory, TechniqueMetadata,
10};
11#[cfg(feature = "knowledge")]
12use crate::knowledge::bks_pks::{BehavioralKnowledgeCache, PersonalKnowledgeCache};
13use crate::prompting::seal::SealProcessingResult;
14use anyhow::{Result, anyhow};
15use std::sync::Arc;
16use tokio::sync::Mutex;
17
18/// Generates optimized prompts based on task characteristics
19pub struct PromptGenerator {
20    library: TechniqueLibrary,
21    cluster_manager: TaskClusterManager,
22    bks_cache: Option<Arc<Mutex<BehavioralKnowledgeCache>>>,
23    pks_cache: Option<Arc<Mutex<PersonalKnowledgeCache>>>,
24}
25
26impl PromptGenerator {
27    /// Create a new prompt generator
28    pub fn new(library: TechniqueLibrary, cluster_manager: TaskClusterManager) -> Self {
29        Self {
30            library,
31            cluster_manager,
32            bks_cache: None,
33            pks_cache: None,
34        }
35    }
36
37    /// Set knowledge caches for BKS/PKS integration
38    pub fn with_knowledge(
39        mut self,
40        bks: Arc<Mutex<BehavioralKnowledgeCache>>,
41        pks: Arc<Mutex<PersonalKnowledgeCache>>,
42    ) -> Self {
43        self.bks_cache = Some(bks);
44        self.pks_cache = Some(pks);
45        self
46    }
47
48    /// Generate optimized prompt (SEAL+BKS+PKS-informed)
49    ///
50    /// This is the main entry point that orchestrates:
51    /// 1. Task classification using cluster matching
52    /// 2. Multi-source technique selection (PKS > BKS > cluster default)
53    /// 3. SEAL quality filtering
54    /// 4. Dynamic prompt composition
55    ///
56    /// # Arguments
57    /// * `task_description` - The task to generate a prompt for
58    /// * `task_embedding` - Pre-computed embedding of the task
59    /// * `seal_result` - Optional SEAL processing result for enhancement
60    ///
61    /// # Returns
62    /// * Generated system prompt string
63    pub async fn generate_prompt(
64        &self,
65        task_description: &str,
66        task_embedding: &[f32],
67        seal_result: Option<&SealProcessingResult>,
68    ) -> Result<GeneratedPrompt> {
69        // Step 1: Find matching cluster (SEAL-enhanced)
70        let (cluster, similarity) = self
71            .cluster_manager
72            .find_matching_cluster(task_embedding, seal_result)?;
73
74        // Step 2: Get SEAL quality score
75        let seal_quality = seal_result.map(|r| r.quality_score).unwrap_or(0.5);
76
77        // Step 3: Select techniques (multi-source)
78        let techniques = self
79            .select_techniques_multi_source(cluster.id.as_str(), seal_quality, seal_result)
80            .await?;
81
82        // Step 4: Generate prompt from techniques
83        let prompt_text =
84            self.compose_prompt(task_description, &techniques, cluster.description.as_str());
85
86        Ok(GeneratedPrompt {
87            system_prompt: prompt_text,
88            cluster_id: cluster.id.clone(),
89            techniques: techniques.iter().map(|t| t.technique.clone()).collect(),
90            seal_quality,
91            similarity_score: similarity,
92        })
93    }
94
95    /// Select techniques from multiple sources (cluster + BKS + PKS + SEAL quality)
96    ///
97    /// Priority order: PKS (user preference) > BKS (collective learning) > cluster default
98    async fn select_techniques_multi_source<'a>(
99        &'a self,
100        cluster_id: &str,
101        seal_quality: f32,
102        _seal_result: Option<&SealProcessingResult>,
103    ) -> Result<Vec<&'a TechniqueMetadata>> {
104        // Source 1: Get cluster's default techniques
105        let cluster = self
106            .cluster_manager
107            .get_cluster_by_id(cluster_id)
108            .ok_or_else(|| anyhow!("Cluster not found: {}", cluster_id))?;
109
110        // Source 2: BKS recommended techniques (from our own cache, not library's)
111        let bks_techniques = self.get_bks_recommended_techniques(cluster_id).await?;
112
113        // Source 3: PKS user preferences (if available)
114        let pks_techniques = self.get_pks_preferred_techniques(cluster_id).await?;
115
116        // Build selection directly from cluster techniques
117        let mut selected = Vec::new();
118
119        // 1. Always include role playing (paper's rule)
120        if let Some(role) = self.library.get(&PromptingTechnique::RolePlaying)
121            && role.min_seal_quality <= seal_quality
122        {
123            selected.push(role);
124        }
125
126        // 2. Select emotional stimulus from cluster techniques
127        let emotion_options: Vec<&TechniqueMetadata> = cluster
128            .techniques
129            .iter()
130            .filter_map(|t| self.library.get(t))
131            .filter(|t| {
132                t.category == TechniqueCategory::EmotionalStimulus
133                    && t.min_seal_quality <= seal_quality
134            })
135            .collect();
136
137        if let Some(emotion) =
138            self.select_best_by_priority(&pks_techniques, &bks_techniques, &emotion_options)
139        {
140            selected.push(emotion);
141        }
142
143        // 3. Select reasoning technique
144        let reasoning_options: Vec<&TechniqueMetadata> = cluster
145            .techniques
146            .iter()
147            .filter_map(|t| self.library.get(t))
148            .filter(|t| {
149                t.category == TechniqueCategory::Reasoning && t.min_seal_quality <= seal_quality
150            })
151            .collect();
152
153        if let Some(reasoning) = self.select_reasoning_by_complexity(
154            &pks_techniques,
155            &bks_techniques,
156            &reasoning_options,
157            seal_quality,
158        ) {
159            selected.push(reasoning);
160        }
161
162        // 4. Optionally select "Others" category
163        if seal_quality > 0.6 {
164            let support_options: Vec<&TechniqueMetadata> = cluster
165                .techniques
166                .iter()
167                .filter_map(|t| self.library.get(t))
168                .filter(|t| {
169                    t.category == TechniqueCategory::Others && t.min_seal_quality <= seal_quality
170                })
171                .collect();
172
173            if let Some(support) =
174                self.select_best_by_priority(&pks_techniques, &bks_techniques, &support_options)
175            {
176                selected.push(support);
177            }
178        }
179
180        Ok(selected)
181    }
182
183    /// Select reasoning technique based on complexity
184    fn select_reasoning_by_complexity<'a>(
185        &self,
186        pks: &[PromptingTechnique],
187        bks: &[PromptingTechnique],
188        options: &[&'a TechniqueMetadata],
189        seal_quality: f32,
190    ) -> Option<&'a TechniqueMetadata> {
191        // Filter by complexity based on SEAL quality
192        let complexity = if seal_quality < 0.5 {
193            ComplexityLevel::Simple
194        } else if seal_quality < 0.8 {
195            ComplexityLevel::Moderate
196        } else {
197            ComplexityLevel::Advanced
198        };
199
200        options
201            .iter()
202            .filter(|t| {
203                t.complexity_level == complexity || t.complexity_level == ComplexityLevel::Simple
204            })
205            .max_by_key(|t| {
206                // Prioritize: PKS > BKS > complexity match
207                let pks_bonus = if pks.contains(&t.technique) { 100 } else { 0 };
208                let bks_bonus = if bks.contains(&t.technique) { 50 } else { 0 };
209                let complexity_bonus = if t.complexity_level == complexity {
210                    10
211                } else {
212                    0
213                };
214                pks_bonus + bks_bonus + complexity_bonus
215            })
216            .copied()
217    }
218
219    /// Get BKS recommended techniques for a cluster
220    ///
221    /// Queries the BKS cache directly for techniques that have been promoted
222    /// based on collective user experience.
223    async fn get_bks_recommended_techniques(
224        &self,
225        cluster_id: &str,
226    ) -> Result<Vec<PromptingTechnique>> {
227        if let Some(ref bks_cache) = self.bks_cache {
228            let bks = bks_cache.lock().await;
229
230            // Query BKS for truths matching the cluster context
231            let truths = bks.get_matching_truths(cluster_id);
232
233            let mut recommended = Vec::new();
234            for truth in truths {
235                // Parse technique from truth rule/rationale
236                let text = format!("{} {}", truth.rule, truth.rationale);
237
238                // Check all known techniques (15 from the paper)
239                let all_techniques = [
240                    PromptingTechnique::RolePlaying,
241                    PromptingTechnique::EmotionPrompting,
242                    PromptingTechnique::StressPrompting,
243                    PromptingTechnique::ChainOfThought,
244                    PromptingTechnique::LogicOfThought,
245                    PromptingTechnique::LeastToMost,
246                    PromptingTechnique::ThreadOfThought,
247                    PromptingTechnique::PlanAndSolve,
248                    PromptingTechnique::SkeletonOfThought,
249                    PromptingTechnique::ScratchpadPrompting,
250                    PromptingTechnique::DecomposedPrompting,
251                    PromptingTechnique::IgnoreIrrelevantConditions,
252                    PromptingTechnique::HighlightedCoT,
253                    PromptingTechnique::SkillsInContext,
254                    PromptingTechnique::AutomaticInformationFiltering,
255                ];
256
257                for technique in all_techniques {
258                    if text.contains(technique.to_str()) {
259                        recommended.push(technique);
260                    }
261                }
262            }
263
264            Ok(recommended)
265        } else {
266            Ok(Vec::new())
267        }
268    }
269
270    /// Get PKS preferred techniques for a cluster
271    async fn get_pks_preferred_techniques(
272        &self,
273        cluster_id: &str,
274    ) -> Result<Vec<PromptingTechnique>> {
275        if let Some(ref pks_cache) = self.pks_cache {
276            let pks = pks_cache.lock().await;
277
278            // Query PKS for user's preferred techniques
279            // Example fact key: "preferred_technique:numerical_reasoning"
280            let key = format!("preferred_technique:{}", cluster_id);
281            if let Some(fact) = pks.get_fact_by_key(&key) {
282                // Parse techniques from fact value
283                // Example value: "ChainOfThought,PlanAndSolve"
284                let techniques: Vec<PromptingTechnique> = fact
285                    .value
286                    .split(',')
287                    .filter_map(|s: &str| PromptingTechnique::parse_id(s.trim()).ok())
288                    .collect();
289                Ok(techniques)
290            } else {
291                Ok(Vec::new())
292            }
293        } else {
294            Ok(Vec::new())
295        }
296    }
297
298    /// Select best technique by priority (PKS > BKS > default)
299    fn select_best_by_priority<'a>(
300        &self,
301        pks: &[PromptingTechnique],
302        bks: &[PromptingTechnique],
303        options: &[&'a TechniqueMetadata],
304    ) -> Option<&'a TechniqueMetadata> {
305        options
306            .iter()
307            .max_by_key(|t| {
308                // PKS > BKS > cluster default
309                if pks.contains(&t.technique) {
310                    2
311                } else if bks.contains(&t.technique) {
312                    1
313                } else {
314                    0
315                }
316            })
317            .copied()
318    }
319
320    /// Compose prompt from selected techniques
321    fn compose_prompt(
322        &self,
323        task_description: &str,
324        techniques: &[&TechniqueMetadata],
325        cluster_description: &str,
326    ) -> String {
327        let mut prompt_parts = Vec::new();
328
329        // Role assignment (always first)
330        if let Some(role_technique) = techniques
331            .iter()
332            .find(|t| t.category == TechniqueCategory::RoleAssignment)
333        {
334            let role_section = self.apply_technique_template(
335                role_technique,
336                task_description,
337                cluster_description,
338            );
339            prompt_parts.push(role_section);
340        }
341
342        // Emotional stimulus
343        if let Some(emotion_technique) = techniques
344            .iter()
345            .find(|t| t.category == TechniqueCategory::EmotionalStimulus)
346        {
347            prompt_parts.push(self.apply_technique_template(
348                emotion_technique,
349                task_description,
350                cluster_description,
351            ));
352        }
353
354        // Reasoning technique
355        if let Some(reasoning_technique) = techniques
356            .iter()
357            .find(|t| t.category == TechniqueCategory::Reasoning)
358        {
359            prompt_parts.push(self.apply_technique_template(
360                reasoning_technique,
361                task_description,
362                cluster_description,
363            ));
364        }
365
366        // Additional support techniques
367        for technique in techniques
368            .iter()
369            .filter(|t| t.category == TechniqueCategory::Others)
370        {
371            prompt_parts.push(self.apply_technique_template(
372                technique,
373                task_description,
374                cluster_description,
375            ));
376        }
377
378        // Task description
379        prompt_parts.push(format!("\n# Task\n\n{}", task_description));
380
381        prompt_parts.join("\n\n")
382    }
383
384    /// Apply technique template with variable substitution
385    fn apply_technique_template(
386        &self,
387        technique: &TechniqueMetadata,
388        task_description: &str,
389        cluster_description: &str,
390    ) -> String {
391        // Simple template substitution
392        let mut result = technique.template.clone();
393
394        // Extract role and domain from cluster/task for Role Playing
395        if technique.technique == PromptingTechnique::RolePlaying {
396            let (role, domain) = self.infer_role_and_domain(task_description, cluster_description);
397            result = result.replace("{role}", &role).replace("{domain}", &domain);
398        }
399
400        // Extract task type and quality for Emotion Prompting
401        if technique.technique == PromptingTechnique::EmotionPrompting {
402            let task_type = self.infer_task_type(task_description);
403            let quality = "precision and accuracy";
404            result = result
405                .replace("{task_type}", &task_type)
406                .replace("{quality}", quality);
407        }
408
409        result
410            .replace("{task}", task_description)
411            .replace("{cluster}", cluster_description)
412    }
413
414    /// Infer role and domain from task/cluster description
415    fn infer_role_and_domain(
416        &self,
417        task_description: &str,
418        cluster_description: &str,
419    ) -> (String, String) {
420        let task_lower = task_description.to_lowercase();
421        let cluster_lower = cluster_description.to_lowercase();
422
423        // Simple heuristics
424        if task_lower.contains("code")
425            || task_lower.contains("function")
426            || task_lower.contains("implement")
427        {
428            (
429                "software engineer".to_string(),
430                "software development".to_string(),
431            )
432        } else if task_lower.contains("algorithm") || task_lower.contains("optimize") {
433            (
434                "computer scientist".to_string(),
435                "algorithms and data structures".to_string(),
436            )
437        } else if task_lower.contains("calculate") || task_lower.contains("numerical") {
438            (
439                "mathematician".to_string(),
440                "numerical analysis".to_string(),
441            )
442        } else if task_lower.contains("analyze") || task_lower.contains("understand") {
443            ("analyst".to_string(), "problem analysis".to_string())
444        } else if cluster_lower.contains("code") {
445            ("developer".to_string(), "software engineering".to_string())
446        } else {
447            ("expert".to_string(), "problem solving".to_string())
448        }
449    }
450
451    /// Infer task type for Emotion Prompting
452    fn infer_task_type(&self, task_description: &str) -> String {
453        let task_lower = task_description.to_lowercase();
454
455        if task_lower.contains("calculate") || task_lower.contains("compute") {
456            "calculation".to_string()
457        } else if task_lower.contains("implement") || task_lower.contains("create") {
458            "implementation".to_string()
459        } else if task_lower.contains("analyze") || task_lower.contains("understand") {
460            "analysis".to_string()
461        } else if task_lower.contains("fix") || task_lower.contains("debug") {
462            "debugging".to_string()
463        } else {
464            "task".to_string()
465        }
466    }
467
468    /// Get reference to cluster manager
469    pub fn cluster_manager(&self) -> &TaskClusterManager {
470        &self.cluster_manager
471    }
472
473    /// Get mutable reference to cluster manager
474    pub fn cluster_manager_mut(&mut self) -> &mut TaskClusterManager {
475        &mut self.cluster_manager
476    }
477}
478
479/// Result of prompt generation
480#[derive(Debug, Clone)]
481pub struct GeneratedPrompt {
482    /// The generated system prompt text
483    pub system_prompt: String,
484    /// ID of the cluster that was matched
485    pub cluster_id: String,
486    /// Techniques that were selected
487    pub techniques: Vec<PromptingTechnique>,
488    /// SEAL quality score used
489    pub seal_quality: f32,
490    /// Similarity score to matched cluster
491    pub similarity_score: f32,
492}
493
494#[cfg(test)]
495mod tests {
496    use super::*;
497    use crate::prompting::clustering::TaskCluster;
498
499    #[test]
500    fn test_infer_role_and_domain() {
501        let generator = PromptGenerator::new(TechniqueLibrary::new(), TaskClusterManager::new());
502
503        let (role, domain) = generator.infer_role_and_domain("Implement a function", "code");
504        assert_eq!(role, "software engineer");
505        assert_eq!(domain, "software development");
506
507        let (role, domain) = generator.infer_role_and_domain("Calculate prime numbers", "math");
508        assert_eq!(role, "mathematician");
509        assert_eq!(domain, "numerical analysis");
510    }
511
512    #[test]
513    fn test_infer_task_type() {
514        let generator = PromptGenerator::new(TechniqueLibrary::new(), TaskClusterManager::new());
515
516        assert_eq!(
517            generator.infer_task_type("Calculate the sum"),
518            "calculation"
519        );
520        assert_eq!(
521            generator.infer_task_type("Implement a class"),
522            "implementation"
523        );
524        assert_eq!(generator.infer_task_type("Analyze the code"), "analysis");
525        assert_eq!(generator.infer_task_type("Fix the bug"), "debugging");
526    }
527
528    #[tokio::test]
529    async fn test_prompt_generation_basic() {
530        let mut cluster_manager = TaskClusterManager::new();
531
532        // Create a test cluster
533        let cluster = TaskCluster::new(
534            "test_cluster".to_string(),
535            "Code generation tasks".to_string(),
536            vec![0.5; 768],
537            vec![
538                PromptingTechnique::RolePlaying,
539                PromptingTechnique::EmotionPrompting,
540                PromptingTechnique::ChainOfThought,
541            ],
542            vec!["Write a function".to_string()],
543        );
544        cluster_manager.add_cluster(cluster);
545
546        let generator = PromptGenerator::new(TechniqueLibrary::new(), cluster_manager);
547
548        // Generate prompt
549        let task_embedding = vec![0.5; 768];
550        let result = generator
551            .generate_prompt("Write a function to sort an array", &task_embedding, None)
552            .await
553            .unwrap();
554
555        // Verify structure
556        assert!(!result.system_prompt.is_empty());
557        assert_eq!(result.cluster_id, "test_cluster");
558        assert!(!result.techniques.is_empty());
559    }
560}