converge_knowledge/agentic/
skills.rs1use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Skill {
17 pub id: Uuid,
19
20 pub category: String,
22
23 pub name: String,
25
26 pub description: String,
28
29 pub patterns: Vec<SkillPattern>,
31
32 pub preconditions: Vec<String>,
34
35 pub postconditions: Vec<String>,
37
38 pub success_rate: f32,
40
41 pub usage_count: u64,
43
44 pub created_at: DateTime<Utc>,
46
47 pub last_used: DateTime<Utc>,
49
50 pub tags: Vec<String>,
52}
53
54impl Skill {
55 pub fn new(
57 category: impl Into<String>,
58 name: impl Into<String>,
59 patterns: Vec<SkillPattern>,
60 ) -> Self {
61 let now = Utc::now();
62 Self {
63 id: Uuid::new_v4(),
64 category: category.into(),
65 name: name.into(),
66 description: String::new(),
67 patterns,
68 preconditions: Vec::new(),
69 postconditions: Vec::new(),
70 success_rate: 1.0,
71 usage_count: 0,
72 created_at: now,
73 last_used: now,
74 tags: Vec::new(),
75 }
76 }
77
78 pub fn with_description(mut self, description: impl Into<String>) -> Self {
80 self.description = description.into();
81 self
82 }
83
84 pub fn with_preconditions(mut self, preconditions: Vec<String>) -> Self {
86 self.preconditions = preconditions;
87 self
88 }
89
90 pub fn with_postconditions(mut self, postconditions: Vec<String>) -> Self {
92 self.postconditions = postconditions;
93 self
94 }
95
96 pub fn with_success_rate(mut self, rate: f32) -> Self {
98 self.success_rate = rate.clamp(0.0, 1.0);
99 self
100 }
101
102 pub fn with_usage_count(mut self, count: u64) -> Self {
104 self.usage_count = count;
105 self
106 }
107
108 pub fn with_tags(mut self, tags: Vec<String>) -> Self {
110 self.tags = tags;
111 self
112 }
113
114 pub fn record_usage(&mut self, succeeded: bool) {
116 self.usage_count += 1;
117 self.last_used = Utc::now();
118
119 let alpha = 0.1;
121 let outcome = if succeeded { 1.0 } else { 0.0 };
122 self.success_rate = alpha * outcome + (1.0 - alpha) * self.success_rate;
123 }
124
125 pub fn to_code(&self) -> String {
127 self.patterns
128 .iter()
129 .map(|p| format!("// {}\n{}", p.name, p.template))
130 .collect::<Vec<_>>()
131 .join("\n\n")
132 }
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct SkillPattern {
138 pub name: String,
140
141 pub template: String,
143
144 pub parameters: Vec<PatternParameter>,
146
147 pub example: Option<String>,
149}
150
151impl SkillPattern {
152 pub fn new(name: impl Into<String>, template: impl Into<String>) -> Self {
154 Self {
155 name: name.into(),
156 template: template.into(),
157 parameters: Vec::new(),
158 example: None,
159 }
160 }
161
162 pub fn with_parameters(mut self, params: Vec<PatternParameter>) -> Self {
164 self.parameters = params;
165 self
166 }
167
168 pub fn with_example(mut self, example: impl Into<String>) -> Self {
170 self.example = Some(example.into());
171 self
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct PatternParameter {
178 pub name: String,
180
181 pub param_type: String,
183
184 pub description: String,
186
187 pub default: Option<String>,
189}
190
191pub struct SkillLibrary {
193 skills: Vec<Skill>,
194}
195
196impl SkillLibrary {
197 pub fn new() -> Self {
199 Self { skills: Vec::new() }
200 }
201
202 pub fn add_skill(&mut self, skill: Skill) {
204 if let Some(existing) = self
206 .skills
207 .iter_mut()
208 .find(|s| s.name == skill.name && s.category == skill.category)
209 {
210 if skill.success_rate > existing.success_rate {
212 *existing = skill;
213 }
214 } else {
215 self.skills.push(skill);
216 }
217 }
218
219 pub fn find_by_category(&self, category: &str) -> Vec<&Skill> {
221 self.skills
222 .iter()
223 .filter(|s| s.category == category)
224 .collect()
225 }
226
227 pub fn find_by_tag(&self, tag: &str) -> Vec<&Skill> {
229 self.skills
230 .iter()
231 .filter(|s| s.tags.iter().any(|t| t == tag))
232 .collect()
233 }
234
235 pub fn search(&self, query: &str, limit: usize) -> Vec<&Skill> {
237 let query_lower = query.to_lowercase();
238 let keywords: Vec<&str> = query_lower.split_whitespace().collect();
239
240 let mut scored: Vec<(f32, &Skill)> = self
241 .skills
242 .iter()
243 .map(|s| {
244 let name_lower = s.name.to_lowercase();
245 let desc_lower = s.description.to_lowercase();
246 let cat_lower = s.category.to_lowercase();
247
248 let score: f32 = keywords
249 .iter()
250 .map(|k| {
251 let mut kw_score = 0.0;
252 if name_lower.contains(k) {
253 kw_score += 2.0;
254 }
255 if desc_lower.contains(k) {
256 kw_score += 1.0;
257 }
258 if cat_lower.contains(k) {
259 kw_score += 1.5;
260 }
261 kw_score
262 })
263 .sum();
264
265 let adjusted = score
267 * (0.5 + s.success_rate)
268 * (1.0 + (s.usage_count as f32).ln().max(0.0) * 0.1);
269
270 (adjusted, s)
271 })
272 .filter(|(score, _)| *score > 0.0)
273 .collect();
274
275 scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
276
277 scored.into_iter().take(limit).map(|(_, s)| s).collect()
278 }
279
280 pub fn most_used(&self, limit: usize) -> Vec<&Skill> {
282 let mut skills: Vec<_> = self.skills.iter().collect();
283 skills.sort_by(|a, b| b.usage_count.cmp(&a.usage_count));
284 skills.into_iter().take(limit).collect()
285 }
286
287 pub fn most_reliable(&self, limit: usize) -> Vec<&Skill> {
289 let mut skills: Vec<_> = self.skills.iter().filter(|s| s.usage_count >= 3).collect();
290 skills.sort_by(|a, b| {
291 b.success_rate
292 .partial_cmp(&a.success_rate)
293 .unwrap_or(std::cmp::Ordering::Equal)
294 });
295 skills.into_iter().take(limit).collect()
296 }
297
298 pub fn len(&self) -> usize {
300 self.skills.len()
301 }
302
303 pub fn is_empty(&self) -> bool {
305 self.skills.is_empty()
306 }
307}
308
309impl Default for SkillLibrary {
310 fn default() -> Self {
311 Self::new()
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
327 fn test_skill_creation() {
328 let skill = Skill::new(
329 "error_handling",
330 "Rust Result Pattern",
331 vec![
332 SkillPattern::new(
333 "define_error",
334 "#[derive(Debug, thiserror::Error)]\nenum AppError { ... }",
335 ),
336 SkillPattern::new(
337 "use_result",
338 "fn process() -> Result<Output, AppError> { ... }",
339 ),
340 SkillPattern::new("propagate", "let value = operation()?;"),
341 ],
342 )
343 .with_description("Standard Rust error handling with custom error types")
344 .with_preconditions(vec![
345 "Function can fail".to_string(),
346 "Need to propagate errors".to_string(),
347 ])
348 .with_postconditions(vec![
349 "Errors are properly typed".to_string(),
350 "Caller can handle or propagate".to_string(),
351 ])
352 .with_tags(vec!["rust".to_string(), "errors".to_string()]);
353
354 assert_eq!(skill.patterns.len(), 3);
355 assert_eq!(skill.preconditions.len(), 2);
356 assert_eq!(skill.postconditions.len(), 2);
357
358 let code = skill.to_code();
360 assert!(code.contains("define_error"));
361 assert!(code.contains("Result<Output, AppError>"));
362 }
363
364 #[test]
371 fn test_skill_search() {
372 let mut library = SkillLibrary::new();
373
374 library.add_skill(
375 Skill::new("testing", "Unit Test Pattern", vec![])
376 .with_description("Writing unit tests with assertions")
377 .with_success_rate(0.9)
378 .with_usage_count(50),
379 );
380
381 library.add_skill(
382 Skill::new("testing", "Integration Test Pattern", vec![])
383 .with_description("Testing API endpoints end-to-end")
384 .with_success_rate(0.85)
385 .with_usage_count(20),
386 );
387
388 library.add_skill(
389 Skill::new("deployment", "Docker Build Pattern", vec![])
390 .with_description("Building Docker containers")
391 .with_success_rate(0.95)
392 .with_usage_count(30),
393 );
394
395 let results = library.search("test assertions", 5);
397 assert!(!results.is_empty());
398 assert_eq!(results[0].name, "Unit Test Pattern");
399
400 let testing = library.find_by_category("testing");
402 assert_eq!(testing.len(), 2);
403 }
404
405 #[test]
412 fn test_usage_tracking() {
413 let mut skill = Skill::new("math", "Division Pattern", vec![]).with_success_rate(1.0);
414
415 skill.record_usage(true);
417 skill.record_usage(true);
418 skill.record_usage(true);
419 assert!(skill.success_rate > 0.95);
420
421 skill.record_usage(false);
423 assert!(skill.success_rate < 1.0);
424 assert!(skill.success_rate > 0.8);
425
426 assert_eq!(skill.usage_count, 4);
427 }
428}