1use crate::{
2 agents::Agent,
3 llm::LLMClient,
4 types::{AgentContext, AgentType, Result},
5};
6use async_trait::async_trait;
7
8const VALID_AGENTS: &[&str] = &[
10 "product",
11 "invoice",
12 "sales",
13 "finance",
14 "hr",
15 "orchestrator",
16 "research",
17];
18
19pub struct RouterAgent {
20 llm: Box<dyn LLMClient>,
21}
22
23impl RouterAgent {
24 pub fn new(llm: Box<dyn LLMClient>) -> Self {
25 Self { llm }
26 }
27
28 fn parse_routing_decision(output: &str) -> Option<String> {
36 let trimmed = output.trim().to_lowercase();
37
38 if VALID_AGENTS.contains(&trimmed.as_str()) {
40 return Some(trimmed);
41 }
42
43 for word in trimmed.split(|c: char| c.is_whitespace() || c == ':' || c == ',' || c == '.') {
46 let word = word.trim();
47 if VALID_AGENTS.contains(&word) {
48 return Some(word.to_string());
49 }
50 }
51
52 for agent in VALID_AGENTS {
54 if trimmed.contains(agent) {
55 return Some(agent.to_string());
56 }
57 }
58
59 None
60 }
61
62 pub async fn route(&self, query: &str, _context: &AgentContext) -> Result<AgentType> {
64 let system_prompt = self.system_prompt();
65 let response = self.llm.generate_with_system(&system_prompt, query).await?;
66
67 let agent_name = Self::parse_routing_decision(&response);
69
70 match agent_name.as_deref() {
71 Some("product") => Ok(AgentType::Product),
72 Some("invoice") => Ok(AgentType::Invoice),
73 Some("sales") => Ok(AgentType::Sales),
74 Some("finance") => Ok(AgentType::Finance),
75 Some("hr") => Ok(AgentType::HR),
76 Some("orchestrator") | Some("research") => Ok(AgentType::Orchestrator),
77 _ => {
78 tracing::debug!(
80 "Router could not parse output '{}', defaulting to orchestrator",
81 response
82 );
83 Ok(AgentType::Orchestrator)
84 }
85 }
86 }
87}
88
89#[async_trait]
90impl Agent for RouterAgent {
91 async fn execute(&self, input: &str, context: &AgentContext) -> Result<String> {
92 let agent_type = self.route(input, context).await?;
93 let agent_name = match agent_type {
95 AgentType::Router => "router",
96 AgentType::Orchestrator => "orchestrator",
97 AgentType::Product => "product",
98 AgentType::Invoice => "invoice",
99 AgentType::Sales => "sales",
100 AgentType::Finance => "finance",
101 AgentType::HR => "hr",
102 };
103 Ok(agent_name.to_string())
104 }
105
106 fn system_prompt(&self) -> String {
107 r#"You are a routing agent that classifies user queries and routes them to the appropriate specialized agent.
108
109Available agents:
110- product: Product information, recommendations, catalog queries
111- invoice: Invoice processing, billing questions, payment status
112- sales: Sales data, analytics, performance metrics
113- finance: Financial reports, budgets, expense analysis
114- hr: Human resources, employee information, policies
115- orchestrator: Complex queries requiring multiple agents or research
116
117Analyze the user's query and respond with ONLY the agent name (lowercase, one word).
118Examples:
119- "What products do we have?" → product
120- "Show me last quarter's sales" → sales
121- "What's our hiring policy?" → hr
122- "Create a comprehensive market analysis" → orchestrator
123
124Respond with ONLY the agent name, nothing else."#.to_string()
125 }
126
127 fn agent_type(&self) -> AgentType {
128 AgentType::Router
129 }
130}