Files
2026-05-25 15:09:42 +08:00

101 lines
3.8 KiB
Python

# IR 验证器:Schema 校验 + Principles 规则检查
import json
import logging
import jsonschema
import yaml
from server.config import settings
from server.services.ir_engine.generator import ir_generator
logger = logging.getLogger(__name__)
class IRValidator:
"""Validate IR against skill schema and soul principles."""
async def validate(self, ir_yaml: str, skill_name: str = "default") -> dict:
"""Validate IR YAML content and return {valid, issues}."""
issues = []
# 1. Parse YAML
try:
ir_data = yaml.safe_load(ir_yaml)
except yaml.YAMLError as e:
return {"valid": False, "issues": [{
"severity": "error",
"message": f"YAML 解析失败: {e}",
}]}
if not isinstance(ir_data, dict):
return {"valid": False, "issues": [{
"severity": "error",
"message": "IR 内容必须是 YAML 字典/对象",
}]}
# 2. Schema validation
skill = ir_generator.load_skill(skill_name)
schema = skill.get("schema", {})
if schema:
try:
jsonschema.validate(instance=ir_data, schema=schema)
except jsonschema.ValidationError as e:
issues.append({
"severity": "error",
"message": f"Schema 校验失败: {e.message}",
"location": ".".join(str(p) for p in e.absolute_path),
})
# 3. Principles validation
principles = self._load_principles()
for principle in principles:
rule = principle.get("rule", "")
pid = principle.get("id", "")
# Check completeness: each feature must have both positive and negative paths
if pid == "completeness":
features = ir_data.get("features", [])
if not features:
issues.append({
"severity": "warning",
"message": f"原则 '{pid}': 未检测到功能点,无法验证完整性",
})
# Check traceability: features should have descriptions
if pid == "traceability":
features = ir_data.get("features", [])
for i, feat in enumerate(features):
if not feat.get("description"):
issues.append({
"severity": "warning",
"message": f"原则 '{pid}': 功能点 {feat.get('feature_name', f'#{i}')} 缺少描述",
})
# Check no-hallucination: warn about potentially vague features
if pid == "no-hallucination":
features = ir_data.get("features", [])
for feat in features:
desc = feat.get("description", "")
if "应该" in desc or "可能" in desc or "猜测" in desc:
issues.append({
"severity": "warning",
"message": f"原则 '{pid}': 功能点 '{feat.get('feature_name', '')}' 描述包含不确定措辞",
})
valid = len([i for i in issues if i["severity"] == "error"]) == 0
logger.info("IR validation: valid=%s, issues=%d", valid, len(issues))
return {"valid": valid, "issues": issues}
def _load_principles(self) -> list[dict]:
"""Load principles from .zeekerwatchmen/soul/principles.yaml."""
principles_path = settings.SOUL_DIR / "principles.yaml"
if not principles_path.exists():
return []
with open(principles_path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
return data.get("principles", []) if data else []
ir_validator = IRValidator()