//! 质量门控 //! 验证生成的技能/工作流是否满足质量标准 //! 包括:置信度阈值、触发词冲突检查、格式校验 use crate::skill_generator::SkillCandidate; /// 质量验证报告 #[derive(Debug, Clone)] pub struct QualityReport { pub passed: bool, pub issues: Vec, pub confidence: f32, } /// 质量门控验证器 pub struct QualityGate { min_confidence: f32, existing_triggers: Vec, } impl QualityGate { pub fn new(min_confidence: f32, existing_triggers: Vec) -> Self { Self { min_confidence, existing_triggers, } } /// 验证技能候选项 pub fn validate_skill(&self, candidate: &SkillCandidate) -> QualityReport { let mut issues = Vec::new(); // 1. 置信度检查 if candidate.confidence < self.min_confidence { issues.push(format!( "置信度 {:.2} 低于阈值 {:.2}", candidate.confidence, self.min_confidence )); } // 2. 名称非空 if candidate.name.trim().is_empty() { issues.push("技能名称不能为空".to_string()); } // 3. 至少一个触发词 if candidate.triggers.is_empty() { issues.push("至少需要一个触发词".to_string()); } // 4. 触发词不与现有技能冲突 let conflicts: Vec<_> = candidate .triggers .iter() .filter(|t| self.existing_triggers.iter().any(|et| et == *t)) .collect(); if !conflicts.is_empty() { issues.push(format!("触发词冲突: {:?}", conflicts)); } // 5. SKILL.md 正文非空 if candidate.body_markdown.trim().is_empty() { issues.push("技能正文不能为空".to_string()); } QualityReport { passed: issues.is_empty(), issues, confidence: candidate.confidence, } } } #[cfg(test)] mod tests { use super::*; fn make_valid_candidate() -> SkillCandidate { SkillCandidate { name: "每日报表".to_string(), description: "生成每日报表".to_string(), triggers: vec!["报表".to_string(), "日报".to_string()], tools: vec!["researcher".to_string()], body_markdown: "# 每日报表\n步骤1\n步骤2".to_string(), source_pattern: "报表生成".to_string(), confidence: 0.85, version: 1, } } #[test] fn test_validate_valid_skill() { let gate = QualityGate::new(0.7, vec!["搜索".to_string()]); let candidate = make_valid_candidate(); let report = gate.validate_skill(&candidate); assert!(report.passed); assert!(report.issues.is_empty()); } #[test] fn test_validate_low_confidence() { let gate = QualityGate::new(0.7, vec![]); let mut candidate = make_valid_candidate(); candidate.confidence = 0.5; let report = gate.validate_skill(&candidate); assert!(!report.passed); assert!(report.issues.iter().any(|i| i.contains("置信度"))); } #[test] fn test_validate_empty_name() { let gate = QualityGate::new(0.5, vec![]); let mut candidate = make_valid_candidate(); candidate.name = "".to_string(); let report = gate.validate_skill(&candidate); assert!(!report.passed); assert!(report.issues.iter().any(|i| i.contains("名称"))); } #[test] fn test_validate_empty_triggers() { let gate = QualityGate::new(0.5, vec![]); let mut candidate = make_valid_candidate(); candidate.triggers = vec![]; let report = gate.validate_skill(&candidate); assert!(!report.passed); assert!(report.issues.iter().any(|i| i.contains("触发词"))); } #[test] fn test_validate_trigger_conflict() { let gate = QualityGate::new(0.5, vec!["报表".to_string()]); let candidate = make_valid_candidate(); let report = gate.validate_skill(&candidate); assert!(!report.passed); assert!(report.issues.iter().any(|i| i.contains("冲突"))); } #[test] fn test_validate_empty_body() { let gate = QualityGate::new(0.5, vec![]); let mut candidate = make_valid_candidate(); candidate.body_markdown = "".to_string(); let report = gate.validate_skill(&candidate); assert!(!report.passed); assert!(report.issues.iter().any(|i| i.contains("正文"))); } #[test] fn test_validate_multiple_issues() { let gate = QualityGate::new(0.9, vec![]); let mut candidate = make_valid_candidate(); candidate.confidence = 0.3; candidate.triggers = vec![]; candidate.body_markdown = "".to_string(); let report = gate.validate_skill(&candidate); assert!(!report.passed); assert!(report.issues.len() >= 3); } }