Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
HIGH-1: 提取共享 json_utils.rs,skill_generator/workflow_composer 去重
HIGH-2: FeedbackCollector Vec→HashMap,消除 unwrap() panic 风险
HIGH-3: ProfileUpdater 改为 collect_updates() 返回字段列表,
growth.rs 直接 async 调用 update_field(),不再用 no-op 闭包
MEDIUM-1: EvolutionMiddleware 注入后自动 drain,防止重复注入
MEDIUM-2: PatternAggregator tools 提取改为直接收集 context 值
MEDIUM-3: evolution_engine.rs 移除 4 个未使用 imports
MEDIUM-4: workflow_composer parse_response pattern 参数加下划线
MEDIUM-7: SkillCandidate 添加 version 字段(默认=1)
测试: zclaw-growth 128 tests, zclaw-runtime 86 tests, workspace 0 failures
161 lines
4.9 KiB
Rust
161 lines
4.9 KiB
Rust
//! 质量门控
|
|
//! 验证生成的技能/工作流是否满足质量标准
|
|
//! 包括:置信度阈值、触发词冲突检查、格式校验
|
|
|
|
use crate::skill_generator::SkillCandidate;
|
|
|
|
/// 质量验证报告
|
|
#[derive(Debug, Clone)]
|
|
pub struct QualityReport {
|
|
pub passed: bool,
|
|
pub issues: Vec<String>,
|
|
pub confidence: f32,
|
|
}
|
|
|
|
/// 质量门控验证器
|
|
pub struct QualityGate {
|
|
min_confidence: f32,
|
|
existing_triggers: Vec<String>,
|
|
}
|
|
|
|
impl QualityGate {
|
|
pub fn new(min_confidence: f32, existing_triggers: Vec<String>) -> 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);
|
|
}
|
|
}
|