1use 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
17pub 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 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 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 pub async fn generate_prompt(
63 &self,
64 task_description: &str,
65 task_embedding: &[f32],
66 seal_result: Option<&SealProcessingResult>,
67 ) -> Result<GeneratedPrompt> {
68 let (cluster, similarity) = self
70 .cluster_manager
71 .find_matching_cluster(task_embedding, seal_result)?;
72
73 let seal_quality = seal_result.map(|r| r.quality_score).unwrap_or(0.5);
75
76 let techniques = self
78 .select_techniques_multi_source(cluster.id.as_str(), seal_quality, seal_result)
79 .await?;
80
81 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 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 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 let bks_techniques = self.get_bks_recommended_techniques(cluster_id).await?;
111
112 let pks_techniques = self.get_pks_preferred_techniques(cluster_id).await?;
114
115 let mut selected = Vec::new();
117
118 if let Some(role) = self.library.get(&PromptingTechnique::RolePlaying)
120 && role.min_seal_quality <= seal_quality
121 {
122 selected.push(role);
123 }
124
125 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 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 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 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 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 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 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 let truths = bks.get_matching_truths(cluster_id);
231
232 let mut recommended = Vec::new();
233 for truth in truths {
234 let text = format!("{} {}", truth.rule, truth.rationale);
236
237 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 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 let key = format!("preferred_technique:{}", cluster_id);
280 if let Some(fact) = pks.get_fact_by_key(&key) {
281 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 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 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 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 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 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 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 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 prompt_parts.push(format!("\n# Task\n\n{}", task_description));
379
380 prompt_parts.join("\n\n")
381 }
382
383 fn apply_technique_template(
385 &self,
386 technique: &TechniqueMetadata,
387 task_description: &str,
388 cluster_description: &str,
389 ) -> String {
390 let mut result = technique.template.clone();
392
393 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 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 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 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 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 pub fn cluster_manager(&self) -> &TaskClusterManager {
469 &self.cluster_manager
470 }
471
472 pub fn cluster_manager_mut(&mut self) -> &mut TaskClusterManager {
474 &mut self.cluster_manager
475 }
476}
477
478#[derive(Debug, Clone)]
480pub struct GeneratedPrompt {
481 pub system_prompt: String,
483 pub cluster_id: String,
485 pub techniques: Vec<PromptingTechnique>,
487 pub seal_quality: f32,
489 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 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 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 assert!(!result.system_prompt.is_empty());
556 assert_eq!(result.cluster_id, "test_cluster");
557 assert!(!result.techniques.is_empty());
558 }
559}