brainwires_cognition/prompting/
generator.rs1use 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
18pub 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 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 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 pub async fn generate_prompt(
64 &self,
65 task_description: &str,
66 task_embedding: &[f32],
67 seal_result: Option<&SealProcessingResult>,
68 ) -> Result<GeneratedPrompt> {
69 let (cluster, similarity) = self
71 .cluster_manager
72 .find_matching_cluster(task_embedding, seal_result)?;
73
74 let seal_quality = seal_result.map(|r| r.quality_score).unwrap_or(0.5);
76
77 let techniques = self
79 .select_techniques_multi_source(cluster.id.as_str(), seal_quality, seal_result)
80 .await?;
81
82 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 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 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 let bks_techniques = self.get_bks_recommended_techniques(cluster_id).await?;
112
113 let pks_techniques = self.get_pks_preferred_techniques(cluster_id).await?;
115
116 let mut selected = Vec::new();
118
119 if let Some(role) = self.library.get(&PromptingTechnique::RolePlaying)
121 && role.min_seal_quality <= seal_quality
122 {
123 selected.push(role);
124 }
125
126 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 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 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 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 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 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 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 let truths = bks.get_matching_truths(cluster_id);
232
233 let mut recommended = Vec::new();
234 for truth in truths {
235 let text = format!("{} {}", truth.rule, truth.rationale);
237
238 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 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 let key = format!("preferred_technique:{}", cluster_id);
281 if let Some(fact) = pks.get_fact_by_key(&key) {
282 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 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 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 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 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 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 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 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 prompt_parts.push(format!("\n# Task\n\n{}", task_description));
380
381 prompt_parts.join("\n\n")
382 }
383
384 fn apply_technique_template(
386 &self,
387 technique: &TechniqueMetadata,
388 task_description: &str,
389 cluster_description: &str,
390 ) -> String {
391 let mut result = technique.template.clone();
393
394 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 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 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 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 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 pub fn cluster_manager(&self) -> &TaskClusterManager {
470 &self.cluster_manager
471 }
472
473 pub fn cluster_manager_mut(&mut self) -> &mut TaskClusterManager {
475 &mut self.cluster_manager
476 }
477}
478
479#[derive(Debug, Clone)]
481pub struct GeneratedPrompt {
482 pub system_prompt: String,
484 pub cluster_id: String,
486 pub techniques: Vec<PromptingTechnique>,
488 pub seal_quality: f32,
490 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 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 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 assert!(!result.system_prompt.is_empty());
557 assert_eq!(result.cluster_id, "test_cluster");
558 assert!(!result.techniques.is_empty());
559 }
560}