Files
zclaw_openfang/crates/zclaw-growth/src/quality_gate.rs
iven a9ea9d8691
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
fix(growth): Evolution Engine 审计修复 — 7项全部完成
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
2026-04-18 22:15:43 +08:00

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);
}
}