"""
运行时配置验证器
提供实时配置验证、配置约束检查和配置兼容性验证功能。
支持动态配置验证、依赖关系检查和性能影响评估。
"""
import json
import logging
import re
from dataclasses import dataclass
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
logger = logging.getLogger(__name__)
[docs]
class ValidationLevel(Enum):
"""验证级别"""
BASIC = "basic" # 基础语法验证
STANDARD = "standard" # 标准业务逻辑验证
STRICT = "strict" # 严格验证(包括性能影响)
ENTERPRISE = "enterprise" # 企业级验证(包括安全和合规)
[docs]
class ValidationSeverity(Enum):
"""验证严重程度"""
INFO = "info" # 信息性提示
WARNING = "warning" # 警告,不阻止运行
ERROR = "error" # 错误,阻止运行
CRITICAL = "critical" # 严重错误,立即停止
[docs]
@dataclass
class ValidationResult:
"""验证结果"""
is_valid: bool
severity: ValidationSeverity
path: str
message: str
suggestion: Optional[str] = None
impact: Optional[str] = None
[docs]
def to_dict(self) -> Dict[str, Any]:
return {
"is_valid": self.is_valid,
"severity": self.severity.value,
"path": self.path,
"message": self.message,
"suggestion": self.suggestion,
"impact": self.impact,
}
[docs]
class ConfigConstraint:
"""配置约束基类"""
[docs]
def __init__(self, name: str, description: str):
self.name = name
self.description = description
[docs]
def validate(self, value: Any, path: str, full_config: Dict[str, Any]) -> List[ValidationResult]:
"""验证配置值"""
raise NotImplementedError
[docs]
class RangeConstraint(ConfigConstraint):
"""数值范围约束"""
[docs]
def __init__(
self, name: str, min_val: Union[int, float], max_val: Union[int, float], description: Optional[str] = None
):
super().__init__(name, description or f"值必须在 {min_val} 到 {max_val} 之间")
self.min_val = min_val
self.max_val = max_val
[docs]
def validate(self, value: Any, path: str, full_config: Dict[str, Any]) -> List[ValidationResult]:
results = []
if not isinstance(value, (int, float)):
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.ERROR,
path=path,
message=f"值必须是数字类型,当前类型: {type(value).__name__}",
suggestion="请使用数字值",
)
)
return results
if not (self.min_val <= value <= self.max_val):
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.ERROR,
path=path,
message=f"值 {value} 超出范围 [{self.min_val}, {self.max_val}]",
suggestion=f"请使用 {self.min_val} 到 {self.max_val} 之间的值",
)
)
else:
results.append(
ValidationResult(
is_valid=True, severity=ValidationSeverity.INFO, path=path, message=f"数值范围验证通过: {value}"
)
)
return results
[docs]
class PatternConstraint(ConfigConstraint):
"""正则表达式模式约束"""
[docs]
def __init__(self, name: str, pattern: str, description: Optional[str] = None):
super().__init__(name, description or f"必须匹配模式: {pattern}")
self.pattern = re.compile(pattern)
self.pattern_str = pattern
[docs]
def validate(self, value: Any, path: str, full_config: Dict[str, Any]) -> List[ValidationResult]:
results = []
if not isinstance(value, str):
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.ERROR,
path=path,
message=f"值必须是字符串类型,当前类型: {type(value).__name__}",
suggestion="请使用字符串值",
)
)
return results
if not self.pattern.match(value):
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.ERROR,
path=path,
message=f"值 '{value}' 不匹配模式 '{self.pattern_str}'",
suggestion="请检查格式并确保符合要求的模式",
)
)
else:
results.append(
ValidationResult(
is_valid=True, severity=ValidationSeverity.INFO, path=path, message=f"模式验证通过: {value}"
)
)
return results
[docs]
class DependencyConstraint(ConfigConstraint):
"""依赖关系约束"""
[docs]
def __init__(self, name: str, depends_on: List[str], description: Optional[str] = None):
super().__init__(name, description or f"依赖于: {', '.join(depends_on)}")
self.depends_on = depends_on
[docs]
def validate(self, value: Any, path: str, full_config: Dict[str, Any]) -> List[ValidationResult]:
results = []
for dep_path in self.depends_on:
if not self._get_nested_value(full_config, dep_path):
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.ERROR,
path=path,
message=f"缺少依赖配置: {dep_path}",
suggestion=f"请先配置 {dep_path}",
impact="可能导致功能无法正常工作",
)
)
if not results:
results.append(
ValidationResult(is_valid=True, severity=ValidationSeverity.INFO, path=path, message="依赖关系验证通过")
)
return results
def _get_nested_value(self, config: Dict[str, Any], path: str) -> Any:
"""获取嵌套配置值"""
keys = path.split(".")
current = config
for key in keys:
if not isinstance(current, dict) or key not in current:
return None
current = current[key]
return current
[docs]
class RuntimeConfigValidator:
"""运行时配置验证器"""
[docs]
def __init__(self, validation_level: ValidationLevel = ValidationLevel.STANDARD):
self.validation_level = validation_level
self.constraints: Dict[str, List[ConfigConstraint]] = {}
self.custom_validators: Dict[str, Callable[[Any, str, Dict[str, Any]], List[ValidationResult]]] = {}
# 注册默认约束
self._register_default_constraints()
def _register_default_constraints(self) -> None:
"""注册默认配置约束"""
# 存储路径约束
self.add_constraint(
"storage.base_path", PatternConstraint("storage_path", r'^[^<>:"|?*]+$', "存储路径不能包含非法字符")
)
# 处理器配置约束
self.add_constraint(
"processors.*.config.batch_size", RangeConstraint("batch_size", 1, 1000, "批处理大小必须在1-1000之间")
)
# LLM配置约束
self.add_constraint(
"llm.settings.temperature", RangeConstraint("temperature", 0.0, 2.0, "温度参数必须在0.0-2.0之间")
)
self.add_constraint(
"llm.settings.max_tokens", RangeConstraint("max_tokens", 1, 32000, "最大tokens必须在1-32000之间")
)
# 向量数据库约束
self.add_constraint(
"vector_db.document_store.embedding_dim",
RangeConstraint("embedding_dim", 64, 4096, "嵌入维度必须在64-4096之间"),
)
# 依赖关系约束
self.add_constraint(
"processors.embedding_processor",
DependencyConstraint("embedding_dependency", ["llm.settings.api_key"], "嵌入处理器需要LLM API密钥"),
)
[docs]
def add_constraint(self, path: str, constraint: ConfigConstraint) -> None:
"""添加配置约束"""
if path not in self.constraints:
self.constraints[path] = []
self.constraints[path].append(constraint)
[docs]
def add_custom_validator(
self, path: str, validator: Callable[[Any, str, Dict[str, Any]], List[ValidationResult]]
) -> None:
"""添加自定义验证器"""
self.custom_validators[path] = validator
[docs]
def validate_config(self, config: Dict[str, Any]) -> Tuple[bool, List[ValidationResult]]:
"""验证完整配置"""
all_results = []
# 基础结构验证
if self.validation_level.value in ["standard", "strict", "enterprise"]:
all_results.extend(self._validate_structure(config))
# 约束验证
all_results.extend(self._validate_constraints(config))
# 自定义验证
all_results.extend(self._validate_custom(config))
# 安全验证(企业级)
if self.validation_level == ValidationLevel.ENTERPRISE:
all_results.extend(self._validate_security(config))
# 性能影响验证(严格模式)
if self.validation_level in [ValidationLevel.STRICT, ValidationLevel.ENTERPRISE]:
all_results.extend(self._validate_performance_impact(config))
# 判断整体验证结果
has_errors = any(r.severity in [ValidationSeverity.ERROR, ValidationSeverity.CRITICAL] for r in all_results)
return not has_errors, all_results
def _validate_structure(self, config: Dict[str, Any]) -> List[ValidationResult]:
"""验证配置结构"""
results = []
required_sections = ["storage", "processors", "llm"]
for section in required_sections:
if section not in config:
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.ERROR,
path=section,
message=f"缺少必需的配置段: {section}",
suggestion=f"请添加 {section} 配置段",
)
)
return results
def _validate_constraints(self, config: Dict[str, Any]) -> List[ValidationResult]:
"""验证配置约束"""
results = []
for path_pattern, constraints in self.constraints.items():
# 处理通配符路径
if "*" in path_pattern:
matching_paths = self._find_matching_paths(config, path_pattern)
for actual_path in matching_paths:
value = self._get_nested_value(config, actual_path)
if value is not None:
for constraint in constraints:
results.extend(constraint.validate(value, actual_path, config))
else:
value = self._get_nested_value(config, path_pattern)
if value is not None:
for constraint in constraints:
results.extend(constraint.validate(value, path_pattern, config))
return results
def _validate_custom(self, config: Dict[str, Any]) -> List[ValidationResult]:
"""执行自定义验证"""
results = []
for path, validator in self.custom_validators.items():
try:
value = self._get_nested_value(config, path)
if value is not None:
results.extend(validator(value, path, config))
except Exception as e:
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.ERROR,
path=path,
message=f"自定义验证器执行失败: {str(e)}",
suggestion="请检查自定义验证器实现",
)
)
return results
def _validate_security(self, config: Dict[str, Any]) -> List[ValidationResult]:
"""安全验证(企业级)"""
results = []
# 检查敏感信息
sensitive_patterns = [
(r"password", "不应在配置中存储明文密码"),
(r"secret", "不应在配置中存储明文密钥"),
(r"key.*[a-zA-Z0-9]{20,}", "API密钥可能暴露"),
]
config_str = json.dumps(config, default=str).lower()
for pattern, message in sensitive_patterns:
if re.search(pattern, config_str):
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.WARNING,
path="security",
message=message,
suggestion="请使用环境变量或密钥管理系统",
impact="可能存在安全风险",
)
)
return results
def _validate_performance_impact(self, config: Dict[str, Any]) -> List[ValidationResult]:
"""性能影响验证"""
results = []
# 检查可能影响性能的配置
performance_checks = [
("processors.ocr_processor.config.batch_size", 100, "OCR批处理大小过大可能影响内存"),
("llm.settings.max_tokens", 8000, "LLM最大tokens过大可能影响响应时间"),
("vector_db.document_store.embedding_dim", 1536, "嵌入维度过大可能影响检索速度"),
]
for path, threshold, message in performance_checks:
value = self._get_nested_value(config, path)
if value and isinstance(value, (int, float)) and value > threshold:
results.append(
ValidationResult(
is_valid=True,
severity=ValidationSeverity.WARNING,
path=path,
message=f"{message}: 当前值 {value}",
suggestion=f"建议值不超过 {threshold}",
impact="可能影响系统性能",
)
)
return results
def _find_matching_paths(self, config: Dict[str, Any], pattern: str) -> List[str]:
"""查找匹配通配符模式的路径"""
paths = []
def traverse(current: Dict[str, Any], current_path: str = "") -> None:
for key, value in current.items():
new_path = f"{current_path}.{key}" if current_path else key
# 检查是否匹配模式
if self._path_matches_pattern(new_path, pattern):
paths.append(new_path)
# 递归遍历
if isinstance(value, dict):
traverse(value, new_path)
traverse(config)
return paths
def _path_matches_pattern(self, path: str, pattern: str) -> bool:
"""检查路径是否匹配模式"""
# 将模式转换为正则表达式
regex_pattern = pattern.replace(".", r"\.").replace("*", r"[^.]+")
return re.match(f"^{regex_pattern}$", path) is not None
def _get_nested_value(self, config: Dict[str, Any], path: str) -> Any:
"""获取嵌套配置值"""
keys = path.split(".")
current = config
for key in keys:
if not isinstance(current, dict) or key not in current:
return None
current = current[key]
return current
[docs]
def generate_validation_report(self, results: List[ValidationResult]) -> str:
"""生成验证报告"""
if not results:
return "✅ 配置验证通过,未发现问题。"
# 按严重程度分组
by_severity: Dict[str, List[ValidationResult]] = {}
for result in results:
severity = result.severity.value
if severity not in by_severity:
by_severity[severity] = []
by_severity[severity].append(result)
report_lines = ["# 配置验证报告\n"]
# 总览
errors = len(by_severity.get("error", [])) + len(by_severity.get("critical", []))
warnings = len(by_severity.get("warning", []))
if errors > 0:
report_lines.append(f"❌ 发现 {errors} 个错误,{warnings} 个警告")
elif warnings > 0:
report_lines.append(f"⚠️ 发现 {warnings} 个警告")
else:
report_lines.append("✅ 验证通过")
report_lines.append("")
# 详细结果
for severity in ["critical", "error", "warning", "info"]:
if severity in by_severity:
severity_results = by_severity[severity]
icon = {"critical": "🚨", "error": "❌", "warning": "⚠️", "info": "ℹ️"}[severity]
report_lines.append(f"## {icon} {severity.upper()} ({len(severity_results)})")
report_lines.append("")
self._append_result_details(report_lines, severity_results)
return "\n".join(report_lines)
@staticmethod
def _append_result_details(report_lines: List[str], results: List[ValidationResult]) -> None:
for result in results:
report_lines.append(f"**路径**: `{result.path}`")
report_lines.append(f"**问题**: {result.message}")
if result.suggestion:
report_lines.append(f"**建议**: {result.suggestion}")
if result.impact:
report_lines.append(f"**影响**: {result.impact}")
report_lines.append("")
# 便捷函数
[docs]
def validate_config_file(
config_path: str, validation_level: ValidationLevel = ValidationLevel.STANDARD
) -> Tuple[bool, str]:
"""验证配置文件"""
try:
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
validator = RuntimeConfigValidator(validation_level)
is_valid, results = validator.validate_config(config)
report = validator.generate_validation_report(results)
return is_valid, report
except FileNotFoundError:
return False, f"配置文件不存在: {config_path}"
except json.JSONDecodeError as e:
return False, f"配置文件JSON格式错误: {str(e)}"
except Exception as e:
return False, f"验证过程中发生错误: {str(e)}"
[docs]
def create_constraint_example() -> Callable[[Any, str, Dict[str, Any]], List[ValidationResult]]:
"""创建约束示例"""
# 自定义验证器示例
def validate_api_url(value: Any, path: str, config: Dict[str, Any]) -> List[ValidationResult]:
results = []
if not isinstance(value, str):
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.ERROR,
path=path,
message="API URL必须是字符串",
suggestion="请使用字符串格式的URL",
)
)
return results
if not value.startswith(("http://", "https://")):
results.append(
ValidationResult(
is_valid=False,
severity=ValidationSeverity.WARNING,
path=path,
message="API URL建议使用HTTPS协议",
suggestion="请使用 https:// 开头的URL以确保安全",
impact="HTTP协议可能存在安全风险",
)
)
return results
return validate_api_url