1use super::default::types::{ProjectRequirement, Subtask};
43use super::llm::{ChatMessage, LLMProvider, parse_llm_json};
44use serde::{Deserialize, Serialize};
45use std::collections::HashMap;
46use std::sync::Arc;
47use tokio::sync::RwLock;
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct AgentInfo {
56 pub id: String,
58 pub name: String,
60 pub description: String,
62 pub capabilities: Vec<String>,
64 pub supported_task_types: Vec<String>,
66 pub prompt_template: Option<String>,
68 pub current_load: u32,
70 pub available: bool,
72 pub performance_score: f32,
74 pub metadata: HashMap<String, String>,
76}
77
78impl AgentInfo {
79 pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
81 Self {
82 id: id.into(),
83 name: name.into(),
84 description: String::new(),
85 capabilities: Vec::new(),
86 supported_task_types: Vec::new(),
87 prompt_template: None,
88 current_load: 0,
89 available: true,
90 performance_score: 0.8,
91 metadata: HashMap::new(),
92 }
93 }
94
95 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
97 self.description = desc.into();
98 self
99 }
100
101 pub fn with_capability(mut self, cap: impl Into<String>) -> Self {
103 self.capabilities.push(cap.into());
104 self
105 }
106
107 pub fn with_capabilities(mut self, caps: Vec<String>) -> Self {
109 self.capabilities.extend(caps);
110 self
111 }
112
113 pub fn with_task_type(mut self, task_type: impl Into<String>) -> Self {
115 self.supported_task_types.push(task_type.into());
116 self
117 }
118
119 pub fn with_prompt_template(mut self, template: impl Into<String>) -> Self {
121 self.prompt_template = Some(template.into());
122 self
123 }
124
125 pub fn with_performance_score(mut self, score: f32) -> Self {
127 self.performance_score = score;
128 self
129 }
130}
131
132#[async_trait::async_trait]
141pub trait AgentProvider: Send + Sync {
142 async fn list_agents(&self) -> Vec<AgentInfo>;
144
145 async fn get_agent(&self, agent_id: &str) -> Option<AgentInfo>;
147
148 async fn filter_by_capabilities(&self, capabilities: &[String]) -> Vec<AgentInfo> {
150 let agents = self.list_agents().await;
151 agents
152 .into_iter()
153 .filter(|agent| {
154 capabilities.is_empty()
155 || capabilities
156 .iter()
157 .any(|cap| agent.capabilities.contains(cap))
158 })
159 .collect()
160 }
161
162 async fn filter_by_task_type(&self, task_type: &str) -> Vec<AgentInfo> {
164 let agents = self.list_agents().await;
165 agents
166 .into_iter()
167 .filter(|agent| {
168 agent.supported_task_types.is_empty()
169 || agent.supported_task_types.contains(&task_type.to_string())
170 })
171 .collect()
172 }
173
174 async fn update_agent_status(&self, agent_id: &str, load: u32, available: bool);
176
177 async fn register_agent(&self, _agent: AgentInfo) -> anyhow::Result<()> {
179 Ok(())
180 }
181
182 async fn unregister_agent(&self, _agent_id: &str) -> anyhow::Result<()> {
184 Ok(())
185 }
186}
187
188#[derive(Clone)]
196pub struct InMemoryAgentProvider {
197 agents: Arc<RwLock<HashMap<String, AgentInfo>>>,
198}
199
200impl InMemoryAgentProvider {
201 pub fn new() -> Self {
202 Self {
203 agents: Arc::new(RwLock::new(HashMap::new())),
204 }
205 }
206
207 pub async fn add_agent(&self, agent: AgentInfo) {
209 let mut agents = self.agents.write().await;
210 agents.insert(agent.id.clone(), agent);
211 }
212
213 pub async fn remove_agent(&self, agent_id: &str) {
215 let mut agents = self.agents.write().await;
216 agents.remove(agent_id);
217 }
218}
219
220impl Default for InMemoryAgentProvider {
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226#[async_trait::async_trait]
227impl AgentProvider for InMemoryAgentProvider {
228 async fn list_agents(&self) -> Vec<AgentInfo> {
229 let agents = self.agents.read().await;
230 agents.values().filter(|a| a.available).cloned().collect()
231 }
232
233 async fn get_agent(&self, agent_id: &str) -> Option<AgentInfo> {
234 let agents = self.agents.read().await;
235 agents.get(agent_id).cloned()
236 }
237
238 async fn update_agent_status(&self, agent_id: &str, load: u32, available: bool) {
239 let mut agents = self.agents.write().await;
240 if let Some(agent) = agents.get_mut(agent_id) {
241 agent.current_load = load;
242 agent.available = available;
243 }
244 }
245
246 async fn register_agent(&self, agent: AgentInfo) -> anyhow::Result<()> {
247 self.add_agent(agent).await;
248 Ok(())
249 }
250
251 async fn unregister_agent(&self, agent_id: &str) -> anyhow::Result<()> {
252 self.remove_agent(agent_id).await;
253 Ok(())
254 }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct RoutingContext {
264 pub subtask: Subtask,
266 pub todo_id: String,
268 pub raw_idea: String,
270 pub requirement: Option<ProjectRequirement>,
272 pub metadata: HashMap<String, String>,
274 pub conversation_history: Vec<String>,
276}
277
278impl RoutingContext {
279 pub fn new(subtask: Subtask, todo_id: &str) -> Self {
280 Self {
281 subtask,
282 todo_id: todo_id.to_string(),
283 raw_idea: String::new(),
284 requirement: None,
285 metadata: HashMap::new(),
286 conversation_history: Vec::new(),
287 }
288 }
289
290 pub fn with_raw_idea(mut self, idea: &str) -> Self {
291 self.raw_idea = idea.to_string();
292 self
293 }
294
295 pub fn with_requirement(mut self, req: ProjectRequirement) -> Self {
296 self.requirement = Some(req);
297 self
298 }
299
300 pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
301 self.metadata.insert(key.to_string(), value.to_string());
302 self
303 }
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct RoutingDecision {
309 pub agent_id: String,
311 pub reason: String,
313 pub confidence: f32,
315 pub alternatives: Vec<String>,
317 pub decision_type: RoutingDecisionType,
319 pub needs_human_confirmation: bool,
321 pub execution_params: HashMap<String, String>,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
327pub enum RoutingDecisionType {
328 ExactMatch,
330 CapabilityMatch,
332 LLMInference,
334 RuleMatch,
336 Default,
338 HumanAssigned,
340}
341
342#[async_trait::async_trait]
351pub trait AgentRouter: Send + Sync {
352 fn name(&self) -> &str;
354
355 async fn route(
357 &self,
358 context: &RoutingContext,
359 available_agents: &[AgentInfo],
360 ) -> anyhow::Result<RoutingDecision>;
361
362 async fn route_batch(
364 &self,
365 contexts: &[RoutingContext],
366 available_agents: &[AgentInfo],
367 ) -> anyhow::Result<Vec<RoutingDecision>> {
368 let mut results = Vec::new();
369 for ctx in contexts {
370 let decision = self.route(ctx, available_agents).await?;
371 results.push(decision);
372 }
373 Ok(results)
374 }
375
376 async fn validate_decision(
378 &self,
379 decision: &RoutingDecision,
380 available_agents: &[AgentInfo],
381 ) -> bool {
382 available_agents.iter().any(|a| a.id == decision.agent_id)
383 }
384}
385
386pub struct LLMAgentRouter {
394 llm: Arc<dyn LLMProvider>,
396 system_prompt: Option<String>,
398 routing_prompt_template: Option<String>,
400 explain_decisions: bool,
402 confidence_threshold: f32,
404}
405
406impl LLMAgentRouter {
407 pub fn new(llm: Arc<dyn LLMProvider>) -> Self {
408 Self {
409 llm,
410 system_prompt: None,
411 routing_prompt_template: None,
412 explain_decisions: true,
413 confidence_threshold: 0.7,
414 }
415 }
416
417 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
418 self.system_prompt = Some(prompt.into());
419 self
420 }
421
422 pub fn with_routing_prompt_template(mut self, template: impl Into<String>) -> Self {
423 self.routing_prompt_template = Some(template.into());
424 self
425 }
426
427 pub fn with_confidence_threshold(mut self, threshold: f32) -> Self {
428 self.confidence_threshold = threshold;
429 self
430 }
431
432 pub fn with_explain_decisions(mut self, explain: bool) -> Self {
433 self.explain_decisions = explain;
434 self
435 }
436
437 fn generate_routing_prompt(&self, context: &RoutingContext, agents: &[AgentInfo]) -> String {
439 if let Some(ref template) = self.routing_prompt_template {
440 template
442 .replace("{subtask_description}", &context.subtask.description)
443 .replace(
444 "{required_capabilities}",
445 &context.subtask.required_capabilities.join(", "),
446 )
447 .replace("{raw_idea}", &context.raw_idea)
448 .replace(
449 "{agents}",
450 &agents
451 .iter()
452 .map(|a| {
453 format!(
454 "- {}: {} (能力: {})",
455 a.id,
456 a.description,
457 a.capabilities.join(", ")
458 )
459 })
460 .collect::<Vec<_>>()
461 .join("\n"),
462 )
463 } else {
464 format!(
466 r#"请为以下任务选择最合适的执行Agent。
467
468## 任务信息
469- 任务描述: {}
470- 所需能力: {}
471- 原始需求: {}
472
473## 可用Agent列表
474{}
475
476## 要求
477请以JSON格式返回路由决策:
478```json
479{{
480 "agent_id": "选中的Agent ID",
481 "reason": "选择理由",
482 "confidence": 0.0-1.0的置信度,
483 "alternatives": ["备选Agent ID列表"]
484}}
485```
486
487请基于任务需求和Agent能力做出最佳匹配。"#,
488 context.subtask.description,
489 context.subtask.required_capabilities.join(", "),
490 context.raw_idea,
491 agents
492 .iter()
493 .map(|a| format!(
494 "- ID: {}\n 名称: {}\n 描述: {}\n 能力: {}\n 任务类型: {}\n 性能评分: {:.2}",
495 a.id,
496 a.name,
497 a.description,
498 a.capabilities.join(", "),
499 a.supported_task_types.join(", "),
500 a.performance_score
501 ))
502 .collect::<Vec<_>>()
503 .join("\n\n")
504 )
505 }
506 }
507
508 fn default_system_prompt(&self) -> &'static str {
510 r#"你是一个任务路由专家,负责将任务分配给最合适的执行Agent。
511
512你需要考虑以下因素:
5131. 任务所需的能力与Agent的能力是否匹配
5142. Agent的历史性能评分
5153. 任务类型与Agent支持的任务类型是否匹配
5164. Agent的当前负载情况
517
518做出决策时,请确保:
519- 选择能力最匹配的Agent
520- 如果有多个匹配的Agent,优先选择性能评分高的
521- 如果没有完全匹配的,选择最接近的并降低置信度
522- 始终提供清晰的决策理由"#
523 }
524}
525
526#[async_trait::async_trait]
527impl AgentRouter for LLMAgentRouter {
528 fn name(&self) -> &str {
529 "llm_router"
530 }
531
532 async fn route(
533 &self,
534 context: &RoutingContext,
535 available_agents: &[AgentInfo],
536 ) -> anyhow::Result<RoutingDecision> {
537 if available_agents.is_empty() {
538 return Err(anyhow::anyhow!("No available agents"));
539 }
540
541 let messages = vec![
543 ChatMessage::system(
544 self.system_prompt
545 .as_deref()
546 .unwrap_or_else(|| self.default_system_prompt()),
547 ),
548 ChatMessage::user(self.generate_routing_prompt(context, available_agents)),
549 ];
550
551 let response = self.llm.chat(messages).await?;
553
554 #[derive(Deserialize)]
556 struct LLMRoutingResponse {
557 agent_id: String,
558 reason: String,
559 confidence: Option<f32>,
560 alternatives: Option<Vec<String>>,
561 }
562
563 match parse_llm_json::<LLMRoutingResponse>(&response) {
564 Ok(parsed) => {
565 let confidence = parsed.confidence.unwrap_or(0.8);
566 Ok(RoutingDecision {
567 agent_id: parsed.agent_id,
568 reason: parsed.reason,
569 confidence,
570 alternatives: parsed.alternatives.unwrap_or_default(),
571 decision_type: RoutingDecisionType::LLMInference,
572 needs_human_confirmation: confidence < self.confidence_threshold,
573 execution_params: HashMap::new(),
574 })
575 }
576 Err(_) => {
577 let fallback_agent = available_agents
579 .first()
580 .ok_or_else(|| anyhow::anyhow!("No available agents"))?;
581
582 Ok(RoutingDecision {
583 agent_id: fallback_agent.id.clone(),
584 reason: "LLM响应解析失败,使用默认分配".to_string(),
585 confidence: 0.5,
586 alternatives: available_agents
587 .iter()
588 .skip(1)
589 .map(|a| a.id.clone())
590 .collect(),
591 decision_type: RoutingDecisionType::Default,
592 needs_human_confirmation: true,
593 execution_params: HashMap::new(),
594 })
595 }
596 }
597 }
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
606pub struct RoutingRule {
607 pub id: String,
609 pub name: String,
611 pub priority: i32,
613 pub conditions: Vec<RuleCondition>,
615 pub condition_logic: ConditionLogic,
617 pub target_agent_id: String,
619 pub enabled: bool,
621}
622
623#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct RuleCondition {
626 pub field: RuleField,
628 pub operator: RuleOperator,
630 pub value: String,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
636pub enum RuleField {
637 SubtaskDescription,
639 RequiredCapability,
641 TaskType,
643 Priority,
645 Metadata(String),
647 RawIdea,
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
653pub enum RuleOperator {
654 Equals,
656 NotEquals,
658 Contains,
660 NotContains,
662 StartsWith,
664 EndsWith,
666 Regex,
668 In,
670 NotIn,
672}
673
674#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
676pub enum ConditionLogic {
677 All,
679 Any,
681}
682
683pub struct RuleBasedRouter {
685 rules: Arc<RwLock<Vec<RoutingRule>>>,
687 default_agent_id: Option<String>,
689 confirm_on_match: bool,
691}
692
693impl RuleBasedRouter {
694 pub fn new() -> Self {
695 Self {
696 rules: Arc::new(RwLock::new(Vec::new())),
697 default_agent_id: None,
698 confirm_on_match: false,
699 }
700 }
701
702 pub fn with_default_agent(mut self, agent_id: impl Into<String>) -> Self {
703 self.default_agent_id = Some(agent_id.into());
704 self
705 }
706
707 pub fn with_confirm_on_match(mut self, confirm: bool) -> Self {
708 self.confirm_on_match = confirm;
709 self
710 }
711
712 pub async fn add_rule(&self, rule: RoutingRule) {
714 let mut rules = self.rules.write().await;
715 rules.push(rule);
716 rules.sort_by(|a, b| b.priority.cmp(&a.priority));
718 }
719
720 pub async fn remove_rule(&self, rule_id: &str) {
722 let mut rules = self.rules.write().await;
723 rules.retain(|r| r.id != rule_id);
724 }
725
726 fn check_condition(&self, condition: &RuleCondition, context: &RoutingContext) -> bool {
728 let field_value = match &condition.field {
729 RuleField::SubtaskDescription => context.subtask.description.clone(),
730 RuleField::RequiredCapability => context.subtask.required_capabilities.join(","),
731 RuleField::TaskType => context
732 .metadata
733 .get("task_type")
734 .cloned()
735 .unwrap_or_default(),
736 RuleField::Priority => context
737 .metadata
738 .get("priority")
739 .cloned()
740 .unwrap_or_default(),
741 RuleField::Metadata(key) => context.metadata.get(key).cloned().unwrap_or_default(),
742 RuleField::RawIdea => context.raw_idea.clone(),
743 };
744
745 match &condition.operator {
746 RuleOperator::Equals => field_value == condition.value,
747 RuleOperator::NotEquals => field_value != condition.value,
748 RuleOperator::Contains => field_value.contains(&condition.value),
749 RuleOperator::NotContains => !field_value.contains(&condition.value),
750 RuleOperator::StartsWith => field_value.starts_with(&condition.value),
751 RuleOperator::EndsWith => field_value.ends_with(&condition.value),
752 RuleOperator::Regex => regex::Regex::new(&condition.value)
753 .map(|re| re.is_match(&field_value))
754 .unwrap_or(false),
755 RuleOperator::In => condition.value.split(',').any(|v| v.trim() == field_value),
756 RuleOperator::NotIn => !condition.value.split(',').any(|v| v.trim() == field_value),
757 }
758 }
759
760 fn check_rule(&self, rule: &RoutingRule, context: &RoutingContext) -> bool {
762 if !rule.enabled {
763 return false;
764 }
765
766 match rule.condition_logic {
767 ConditionLogic::All => rule
768 .conditions
769 .iter()
770 .all(|c| self.check_condition(c, context)),
771 ConditionLogic::Any => rule
772 .conditions
773 .iter()
774 .any(|c| self.check_condition(c, context)),
775 }
776 }
777}
778
779impl Default for RuleBasedRouter {
780 fn default() -> Self {
781 Self::new()
782 }
783}
784
785#[async_trait::async_trait]
786impl AgentRouter for RuleBasedRouter {
787 fn name(&self) -> &str {
788 "rule_based_router"
789 }
790
791 async fn route(
792 &self,
793 context: &RoutingContext,
794 available_agents: &[AgentInfo],
795 ) -> anyhow::Result<RoutingDecision> {
796 let rules = self.rules.read().await;
797
798 for rule in rules.iter() {
800 if self.check_rule(rule, context) {
801 if available_agents
803 .iter()
804 .any(|a| a.id == rule.target_agent_id)
805 {
806 return Ok(RoutingDecision {
807 agent_id: rule.target_agent_id.clone(),
808 reason: format!("匹配规则: {} ({})", rule.name, rule.id),
809 confidence: 1.0,
810 alternatives: Vec::new(),
811 decision_type: RoutingDecisionType::RuleMatch,
812 needs_human_confirmation: self.confirm_on_match,
813 execution_params: HashMap::new(),
814 });
815 }
816 }
817 }
818
819 let agent_id = self
821 .default_agent_id
822 .clone()
823 .or_else(|| available_agents.first().map(|a| a.id.clone()))
824 .ok_or_else(|| anyhow::anyhow!("No available agents"))?;
825
826 Ok(RoutingDecision {
827 agent_id,
828 reason: "无匹配规则,使用默认分配".to_string(),
829 confidence: 0.5,
830 alternatives: available_agents.iter().map(|a| a.id.clone()).collect(),
831 decision_type: RoutingDecisionType::Default,
832 needs_human_confirmation: true,
833 execution_params: HashMap::new(),
834 })
835 }
836}
837
838pub struct CapabilityRouter {
846 capability_weights: HashMap<String, f32>,
848 load_balancing: bool,
850 performance_weight: f32,
852}
853
854impl CapabilityRouter {
855 pub fn new() -> Self {
856 Self {
857 capability_weights: HashMap::new(),
858 load_balancing: true,
859 performance_weight: 0.3,
860 }
861 }
862
863 pub fn with_capability_weight(mut self, capability: impl Into<String>, weight: f32) -> Self {
864 self.capability_weights.insert(capability.into(), weight);
865 self
866 }
867
868 pub fn with_load_balancing(mut self, enabled: bool) -> Self {
869 self.load_balancing = enabled;
870 self
871 }
872
873 pub fn with_performance_weight(mut self, weight: f32) -> Self {
874 self.performance_weight = weight;
875 self
876 }
877
878 fn calculate_match_score(&self, agent: &AgentInfo, required_caps: &[String]) -> f32 {
880 if required_caps.is_empty() {
881 return 1.0;
882 }
883
884 let mut score = 0.0;
885 let mut total_weight = 0.0;
886
887 for cap in required_caps {
888 let weight = self.capability_weights.get(cap).copied().unwrap_or(1.0);
889 total_weight += weight;
890
891 if agent.capabilities.contains(cap) {
892 score += weight;
893 }
894 }
895
896 let capability_score = if total_weight > 0.0 {
897 score / total_weight
898 } else {
899 1.0
900 };
901
902 let load_score = if self.load_balancing {
904 1.0 - (agent.current_load as f32 / 100.0)
905 } else {
906 1.0
907 };
908
909 let performance_score = agent.performance_score;
910
911 capability_score * 0.5 + load_score * 0.2 + performance_score * self.performance_weight
913 }
914}
915
916impl Default for CapabilityRouter {
917 fn default() -> Self {
918 Self::new()
919 }
920}
921
922#[async_trait::async_trait]
923impl AgentRouter for CapabilityRouter {
924 fn name(&self) -> &str {
925 "capability_router"
926 }
927
928 async fn route(
929 &self,
930 context: &RoutingContext,
931 available_agents: &[AgentInfo],
932 ) -> anyhow::Result<RoutingDecision> {
933 if available_agents.is_empty() {
934 return Err(anyhow::anyhow!("No available agents"));
935 }
936
937 let required_caps = &context.subtask.required_capabilities;
938
939 let mut scored_agents: Vec<(&AgentInfo, f32)> = available_agents
941 .iter()
942 .map(|a| (a, self.calculate_match_score(a, required_caps)))
943 .collect();
944
945 scored_agents.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
947
948 let (best_agent, best_score) = scored_agents[0];
949
950 Ok(RoutingDecision {
951 agent_id: best_agent.id.clone(),
952 reason: format!(
953 "能力匹配得分最高 ({:.2}),匹配能力: {:?}",
954 best_score,
955 required_caps
956 .iter()
957 .filter(|c| best_agent.capabilities.contains(c))
958 .collect::<Vec<_>>()
959 ),
960 confidence: best_score,
961 alternatives: scored_agents
962 .iter()
963 .skip(1)
964 .map(|(a, _)| a.id.clone())
965 .collect(),
966 decision_type: RoutingDecisionType::CapabilityMatch,
967 needs_human_confirmation: best_score < 0.5,
968 execution_params: HashMap::new(),
969 })
970 }
971}
972
973pub struct CompositeRouter {
981 routers: Vec<Arc<dyn AgentRouter>>,
983 fallback_router: Option<Arc<dyn AgentRouter>>,
985}
986
987impl CompositeRouter {
988 pub fn new() -> Self {
989 Self {
990 routers: Vec::new(),
991 fallback_router: None,
992 }
993 }
994
995 pub fn add_router(mut self, router: Arc<dyn AgentRouter>) -> Self {
997 self.routers.push(router);
998 self
999 }
1000
1001 pub fn with_fallback(mut self, router: Arc<dyn AgentRouter>) -> Self {
1003 self.fallback_router = Some(router);
1004 self
1005 }
1006}
1007
1008impl Default for CompositeRouter {
1009 fn default() -> Self {
1010 Self::new()
1011 }
1012}
1013
1014#[async_trait::async_trait]
1015impl AgentRouter for CompositeRouter {
1016 fn name(&self) -> &str {
1017 "composite_router"
1018 }
1019
1020 async fn route(
1021 &self,
1022 context: &RoutingContext,
1023 available_agents: &[AgentInfo],
1024 ) -> anyhow::Result<RoutingDecision> {
1025 for router in &self.routers {
1027 match router.route(context, available_agents).await {
1028 Ok(decision) if decision.confidence >= 0.5 => {
1029 return Ok(decision);
1030 }
1031 _ => continue,
1032 }
1033 }
1034
1035 if let Some(ref fallback) = self.fallback_router {
1037 return fallback.route(context, available_agents).await;
1038 }
1039
1040 let agent = available_agents
1042 .first()
1043 .ok_or_else(|| anyhow::anyhow!("No available agents"))?;
1044
1045 Ok(RoutingDecision {
1046 agent_id: agent.id.clone(),
1047 reason: "所有路由器均无高置信度匹配,使用默认分配".to_string(),
1048 confidence: 0.3,
1049 alternatives: available_agents
1050 .iter()
1051 .skip(1)
1052 .map(|a| a.id.clone())
1053 .collect(),
1054 decision_type: RoutingDecisionType::Default,
1055 needs_human_confirmation: true,
1056 execution_params: HashMap::new(),
1057 })
1058 }
1059}
1060
1061#[cfg(test)]
1066mod tests {
1067 use super::*;
1068
1069 #[tokio::test]
1070 async fn test_in_memory_agent_provider() {
1071 let provider = InMemoryAgentProvider::new();
1072
1073 provider
1075 .add_agent(
1076 AgentInfo::new("agent_1", "Test Agent 1")
1077 .with_capability("backend")
1078 .with_performance_score(0.9),
1079 )
1080 .await;
1081
1082 provider
1083 .add_agent(
1084 AgentInfo::new("agent_2", "Test Agent 2")
1085 .with_capability("frontend")
1086 .with_performance_score(0.85),
1087 )
1088 .await;
1089
1090 let agents = provider.list_agents().await;
1092 assert_eq!(agents.len(), 2);
1093
1094 let backend_agents = provider
1096 .filter_by_capabilities(&["backend".to_string()])
1097 .await;
1098 assert_eq!(backend_agents.len(), 1);
1099 assert_eq!(backend_agents[0].id, "agent_1");
1100 }
1101
1102 #[tokio::test]
1103 async fn test_capability_router() {
1104 let router = CapabilityRouter::new()
1105 .with_load_balancing(true)
1106 .with_performance_weight(0.3);
1107
1108 let agents = vec![
1109 AgentInfo::new("agent_1", "Backend Agent")
1110 .with_capability("backend")
1111 .with_capability("database")
1112 .with_performance_score(0.9),
1113 AgentInfo::new("agent_2", "Frontend Agent")
1114 .with_capability("frontend")
1115 .with_capability("ui")
1116 .with_performance_score(0.85),
1117 ];
1118
1119 let context = RoutingContext::new(
1120 Subtask {
1121 id: "task_1".to_string(),
1122 description: "Build API".to_string(),
1123 required_capabilities: vec!["backend".to_string()],
1124 order: 1,
1125 depends_on: Vec::new(),
1126 },
1127 "todo_1",
1128 );
1129
1130 let decision = router.route(&context, &agents).await.unwrap();
1131 assert_eq!(decision.agent_id, "agent_1");
1132 assert_eq!(decision.decision_type, RoutingDecisionType::CapabilityMatch);
1133 }
1134
1135 #[tokio::test]
1136 async fn test_rule_based_router() {
1137 let router = RuleBasedRouter::new();
1138
1139 router
1141 .add_rule(RoutingRule {
1142 id: "rule_1".to_string(),
1143 name: "Backend Rule".to_string(),
1144 priority: 10,
1145 conditions: vec![RuleCondition {
1146 field: RuleField::RequiredCapability,
1147 operator: RuleOperator::Contains,
1148 value: "backend".to_string(),
1149 }],
1150 condition_logic: ConditionLogic::All,
1151 target_agent_id: "backend_agent".to_string(),
1152 enabled: true,
1153 })
1154 .await;
1155
1156 let agents = vec![
1157 AgentInfo::new("backend_agent", "Backend Agent"),
1158 AgentInfo::new("frontend_agent", "Frontend Agent"),
1159 ];
1160
1161 let context = RoutingContext::new(
1162 Subtask {
1163 id: "task_1".to_string(),
1164 description: "Build API".to_string(),
1165 required_capabilities: vec!["backend".to_string()],
1166 order: 1,
1167 depends_on: Vec::new(),
1168 },
1169 "todo_1",
1170 );
1171
1172 let decision = router.route(&context, &agents).await.unwrap();
1173 assert_eq!(decision.agent_id, "backend_agent");
1174 assert_eq!(decision.decision_type, RoutingDecisionType::RuleMatch);
1175 }
1176
1177 #[tokio::test]
1178 async fn test_composite_router() {
1179 let rule_router = Arc::new(RuleBasedRouter::new());
1180 let capability_router = Arc::new(CapabilityRouter::new());
1181
1182 let composite = CompositeRouter::new()
1183 .add_router(rule_router)
1184 .with_fallback(capability_router);
1185
1186 let agents = vec![
1187 AgentInfo::new("agent_1", "Agent 1")
1188 .with_capability("general")
1189 .with_performance_score(0.8),
1190 ];
1191
1192 let context = RoutingContext::new(
1193 Subtask {
1194 id: "task_1".to_string(),
1195 description: "General task".to_string(),
1196 required_capabilities: vec![],
1197 order: 1,
1198 depends_on: Vec::new(),
1199 },
1200 "todo_1",
1201 );
1202
1203 let decision = composite.route(&context, &agents).await.unwrap();
1204 assert_eq!(decision.agent_id, "agent_1");
1205 }
1206}