Skip to main content

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