use crate::{
agents::{Agent, AgentResponse},
llm::LLMClient,
types::{AgentContext, AgentType, Result},
};
use async_trait::async_trait;
const VALID_AGENTS: &[&str] = &[
"product",
"invoice",
"sales",
"finance",
"hr",
"orchestrator",
"research",
];
pub struct RouterAgent {
llm: Box<dyn LLMClient>,
}
impl RouterAgent {
pub fn new(llm: Box<dyn LLMClient>) -> Self {
Self { llm }
}
fn parse_routing_decision(output: &str) -> Option<String> {
let trimmed = output.trim().to_lowercase();
if VALID_AGENTS.contains(&trimmed.as_str()) {
return Some(trimmed);
}
for word in trimmed.split(|c: char| c.is_whitespace() || c == ':' || c == ',' || c == '.') {
let word = word.trim();
if VALID_AGENTS.contains(&word) {
return Some(word.to_string());
}
}
for agent in VALID_AGENTS {
if trimmed.contains(agent) {
return Some(agent.to_string());
}
}
None
}
pub async fn route(&self, query: &str, _context: &AgentContext) -> Result<AgentType> {
let system_prompt = self.system_prompt();
let response = self.llm.generate_with_system(&system_prompt, query).await?;
let agent_name = Self::parse_routing_decision(&response);
match agent_name.as_deref() {
Some("product") => Ok(AgentType::Product),
Some("invoice") => Ok(AgentType::Invoice),
Some("sales") => Ok(AgentType::Sales),
Some("finance") => Ok(AgentType::Finance),
Some("hr") => Ok(AgentType::HR),
Some("orchestrator") | Some("research") => Ok(AgentType::Orchestrator),
_ => {
tracing::debug!(
"Router could not parse output '{}', defaulting to orchestrator",
response
);
Ok(AgentType::Orchestrator)
}
}
}
}
#[async_trait]
impl Agent for RouterAgent {
async fn execute(&self, _input: &str, _context: &AgentContext) -> Result<AgentResponse> {
Ok(AgentResponse {
content: "router".to_string(),
usage: None,
metadata: None,
})
}
fn system_prompt(&self) -> String {
r#"You are a routing agent that classifies user queries and routes them to the appropriate specialized agent.
Available agents:
- product: Product information, recommendations, catalog queries
- invoice: Invoice processing, billing questions, payment status
- sales: Sales data, analytics, performance metrics
- finance: Financial reports, budgets, expense analysis
- hr: Human resources, employee information, policies
- orchestrator: Complex queries requiring multiple agents or research
Analyze the user's query and respond with ONLY the agent name (lowercase, one word).
Examples:
- "What products do we have?" → product
- "Show me last quarter's sales" → sales
- "What's our hiring policy?" → hr
- "Create a comprehensive market analysis" → orchestrator
Respond with ONLY the agent name, nothing else."#.to_string()
}
fn agent_type(&self) -> AgentType {
AgentType::Router
}
}