use crate::config::AgentDef;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TaskComplexity {
Simple,
Medium,
Complex,
}
pub fn classify_complexity(message: &str) -> TaskComplexity {
let lower = message.to_lowercase();
let word_count = message.split_whitespace().count();
let complex_keywords = [
"refactor",
"redesign",
"architect",
"migrate",
"rewrite entire",
"across all files",
"全面",
"리팩터",
"performance optimization",
"benchmark",
"security audit",
"implement from scratch",
"design pattern",
];
let complex_score: usize = complex_keywords
.iter()
.filter(|k| lower.contains(*k))
.count();
let simple_keywords = [
"read",
"show",
"what is",
"list",
"find",
"check",
"status",
"version",
"help",
"뭐야",
"보여줘",
"확인",
];
let simple_score: usize = simple_keywords
.iter()
.filter(|k| lower.contains(*k))
.count();
if complex_score >= 2 || (word_count > 100 && complex_score >= 1) {
TaskComplexity::Complex
} else if simple_score >= 2 || word_count < 10 {
TaskComplexity::Simple
} else {
TaskComplexity::Medium
}
}
fn model_for_tier<'a>(tier: &str, agents: &'a [AgentDef], default_model: &'a str) -> &'a str {
agents
.iter()
.find(|a| a.tier.as_deref() == Some(tier) && a.model != "default")
.map(|a| a.model.as_str())
.unwrap_or(default_model)
}
pub fn select_model<'a>(
complexity: TaskComplexity,
default_model: &'a str,
agents: &'a [AgentDef],
) -> &'a str {
match complexity {
TaskComplexity::Simple => model_for_tier("light", agents, default_model),
TaskComplexity::Medium => default_model,
TaskComplexity::Complex => model_for_tier("heavy", agents, default_model),
}
}
#[derive(Debug, Clone)]
pub struct RoutingDecision {
pub model: String,
pub complexity: TaskComplexity,
pub reason: String,
}
pub fn route(
message: &str,
default_model: &str,
auto_route: bool,
agents: &[AgentDef],
) -> RoutingDecision {
let complexity = classify_complexity(message);
if !auto_route {
return RoutingDecision {
model: default_model.to_string(),
complexity,
reason: "Auto-routing disabled".to_string(),
};
}
let selected = select_model(complexity, default_model, agents);
let reason = match complexity {
TaskComplexity::Simple => format!("Simple task → using light-tier model ({selected})"),
TaskComplexity::Medium => format!("Medium complexity → using default model ({selected})"),
TaskComplexity::Complex => format!("Complex task → using heavy-tier model ({selected})"),
};
RoutingDecision {
model: selected.to_string(),
complexity,
reason,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_classification() {
assert_eq!(
classify_complexity("show me the file"),
TaskComplexity::Simple,
);
assert_eq!(classify_complexity("what is this?"), TaskComplexity::Simple,);
}
#[test]
fn test_complex_classification() {
assert_eq!(
classify_complexity("refactor the entire architecture and redesign the module system"),
TaskComplexity::Complex,
);
}
fn make_agents(light: &str, heavy: &str) -> Vec<AgentDef> {
vec![
AgentDef {
name: "ask".into(),
model: light.into(),
tier: Some("light".into()),
..Default::default()
},
AgentDef {
name: "code".into(),
model: "glm-4.7".into(),
tier: Some("medium".into()),
..Default::default()
},
AgentDef {
name: "architect".into(),
model: heavy.into(),
tier: Some("heavy".into()),
..Default::default()
},
]
}
#[test]
fn test_model_selection_tier() {
let agents = make_agents("glm-5-turbo", "glm-5.1");
assert_eq!(
select_model(TaskComplexity::Simple, "glm-4.7", &agents),
"glm-5-turbo"
);
assert_eq!(
select_model(TaskComplexity::Complex, "glm-4.7", &agents),
"glm-5.1"
);
assert_eq!(
select_model(TaskComplexity::Medium, "glm-4.7", &agents),
"glm-4.7"
);
}
#[test]
fn test_model_selection_fallback() {
assert_eq!(
select_model(TaskComplexity::Simple, "glm-4.7", &[]),
"glm-4.7"
);
assert_eq!(
select_model(TaskComplexity::Complex, "glm-4.7", &[]),
"glm-4.7"
);
}
}