"""QualityGate - 产出质量管理 多维度质量检查:必填字段、字数、JSON Schema、自定义验证器。 """ import importlib import logging from dataclasses import dataclass from typing import Any, Callable from agentkit.skills.base import Skill logger = logging.getLogger(__name__) @dataclass class QualityCheck: """单条质量检查结果""" name: str passed: bool message: str | None = None @dataclass class QualityResult: """质量检查汇总结果""" passed: bool checks: list[QualityCheck] can_retry: bool class QualityGate: """产出质量管理 — 多维度质量检查""" async def validate( self, output: dict[str, Any], skill: Skill, ) -> QualityResult: """对产出执行多维度质量检查 检查维度: 1. 必填字段检查 2. 最低字数检查 3. JSON Schema 验证(如 skill.config.output_schema 存在) 4. 自定义验证器(如 skill.config.quality_gate.custom_validator 存在) """ checks: list[QualityCheck] = [] qg = skill.config.quality_gate # 1. 必填字段检查 for field in qg.required_fields: present = field in output and output[field] is not None checks.append(QualityCheck( name=f"required_field:{field}", passed=present, message=f"Field '{field}' is missing" if not present else None, )) # 2. 最低字数检查 if qg.min_word_count > 0: content = output.get("content", "") if isinstance(content, str): word_count = len(content.split()) else: word_count = len(str(content).split()) passed = word_count >= qg.min_word_count checks.append(QualityCheck( name="min_word_count", passed=passed, message=( f"Word count {word_count} < minimum {qg.min_word_count}" if not passed else None ), )) # 3. JSON Schema 验证 if skill.config.output_schema: try: import jsonschema jsonschema.validate(output, skill.config.output_schema) checks.append(QualityCheck(name="schema", passed=True)) except jsonschema.ValidationError as e: checks.append(QualityCheck(name="schema", passed=False, message=str(e))) except ImportError: # jsonschema 未安装,跳过 pass # 4. 自定义验证器 if qg.custom_validator: try: validator = self._import_validator(qg.custom_validator) result = validator(output) # 支持异步验证器 if hasattr(result, "__await__"): result = await result checks.append(QualityCheck(name="custom", passed=bool(result))) except Exception as e: # 验证器导入/执行失败,跳过并记录警告 checks.append(QualityCheck( name="custom", passed=True, message=f"Validator skipped: {e}", )) return QualityResult( passed=all(c.passed for c in checks), checks=checks, can_retry=qg.max_retries > 0, ) # 允许的验证器模块前缀白名单 _ALLOWED_VALIDATOR_PREFIXES = ( "agentkit.", "app.agent_framework.", ) def _import_validator(self, dotted_path: str) -> Callable: """从点分路径导入自定义验证器函数 出于安全考虑,只允许导入白名单前缀下的模块。 """ # 安全校验:只允许白名单前缀的模块 if not any(dotted_path.startswith(prefix) for prefix in self._ALLOWED_VALIDATOR_PREFIXES): raise ImportError( f"Validator '{dotted_path}' is not in allowed module prefixes: " f"{self._ALLOWED_VALIDATOR_PREFIXES}" ) try: module_path, func_name = dotted_path.rsplit(".", 1) module = importlib.import_module(module_path) handler = getattr(module, func_name) if not callable(handler): raise ValueError(f"'{dotted_path}' is not callable") return handler except (ImportError, AttributeError, ValueError) as e: raise ImportError(f"Failed to import validator '{dotted_path}': {e}") from e