fastskill_core/core/
routing.rs1use crate::core::metadata::{MetadataService, SkillMetadata};
4use crate::core::service::ServiceError;
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct RoutedSkill {
11 pub skill_id: String,
12 pub relevance_score: f32,
13 }
15
16#[async_trait]
17pub trait RoutingService: Send + Sync {
18 async fn find_relevant_skills(
19 &self,
20 query: &str,
21 context: Option<QueryContext>,
22 ) -> Result<Vec<RoutedSkill>, ServiceError>;
23 }
25
26#[derive(Debug, Clone)]
27pub struct QueryContext {
28 pub available_tokens: Option<usize>,
30 pub conversation_history: Option<Vec<String>>,
32 pub user_preferences: Option<std::collections::HashMap<String, String>>,
34}
35
36pub struct RoutingServiceImpl {
37 metadata_service: Arc<dyn MetadataService>,
38}
39
40impl RoutingServiceImpl {
41 pub fn new(metadata_service: Arc<dyn MetadataService>) -> Self {
42 Self { metadata_service }
43 }
44
45 async fn score_skills_for_query(
47 &self,
48 skills: &[SkillMetadata],
49 query: &str,
50 context: Option<&QueryContext>,
51 ) -> Vec<(f32, SkillMetadata)> {
52 let mut scored_skills = Vec::new();
53
54 for skill in skills {
55 let score = self.calculate_relevance_score(skill, query, context);
56 scored_skills.push((score, skill.clone()));
57 }
58
59 scored_skills.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
62 scored_skills
63 }
64
65 fn calculate_relevance_score(
67 &self,
68 skill: &SkillMetadata,
69 query: &str,
70 context: Option<&QueryContext>,
71 ) -> f32 {
72 let query_lower = query.to_lowercase();
73 let mut score = 0.0;
74
75 if skill.name.to_lowercase() == query_lower {
77 score += 1.0;
78 } else if skill.name.to_lowercase().contains(&query_lower) {
79 score += 0.8;
80 }
81
82 if skill.description.to_lowercase().contains(&query_lower) {
84 score += 0.6;
85 }
86
87 if let Some(context) = context {
89 if let Some(available_tokens) = context.available_tokens {
90 if available_tokens < 2000 && skill.token_estimate < 100 {
91 score += 0.2; }
93 }
94 }
95
96 score
97 }
98}
99
100#[async_trait]
101impl RoutingService for RoutingServiceImpl {
102 async fn find_relevant_skills(
103 &self,
104 query: &str,
105 context: Option<QueryContext>,
106 ) -> Result<Vec<RoutedSkill>, ServiceError> {
107 let all_metadata = self.metadata_service.discover_skills("").await?;
109
110 let scored_skills = self
112 .score_skills_for_query(&all_metadata, query, context.as_ref())
113 .await;
114
115 let routed_skills: Vec<RoutedSkill> = scored_skills
117 .into_iter()
118 .take(20) .map(|(score, metadata)| RoutedSkill {
120 skill_id: metadata.id.to_string(),
121 relevance_score: score,
122 })
123 .collect();
124
125 Ok(routed_skills)
126 }
127}