""" 隐私配置管理模块 管理环境信息采集的最小化策略和用户控制开关 """ import os import platform import sys from pathlib import Path from typing import Dict, Any, Optional from dataclasses import dataclass, field @dataclass class PrivacySettings: """隐私设置""" # 环境信息采集开关 send_os_info: bool = True # 操作系统信息 send_python_version: bool = True # Python 版本 send_architecture: bool = True # 系统架构 send_home_dir: bool = False # 用户主目录(默认关闭) send_workspace_path: bool = True # 工作空间路径 send_current_dir: bool = False # 当前工作目录(默认关闭) # 脱敏策略 anonymize_paths: bool = True # 路径脱敏(默认开启) anonymize_username: bool = True # 用户名脱敏(默认开启) # 场景化采集 chat_minimal_info: bool = True # 对话场景最小化信息(默认开启) guidance_full_info: bool = True # 指导场景提供完整信息(默认开启) def to_dict(self) -> Dict[str, bool]: """转换为字典""" return { 'send_os_info': self.send_os_info, 'send_python_version': self.send_python_version, 'send_architecture': self.send_architecture, 'send_home_dir': self.send_home_dir, 'send_workspace_path': self.send_workspace_path, 'send_current_dir': self.send_current_dir, 'anonymize_paths': self.anonymize_paths, 'anonymize_username': self.anonymize_username, 'chat_minimal_info': self.chat_minimal_info, 'guidance_full_info': self.guidance_full_info, } @classmethod def from_dict(cls, data: Dict[str, bool]) -> 'PrivacySettings': """从字典创建""" return cls(**{k: v for k, v in data.items() if k in cls.__annotations__}) class PrivacyManager: """隐私管理器""" def __init__(self, workspace: Path): self.workspace = workspace self.config_file = workspace / ".privacy_config.json" self.settings = self._load_settings() # 度量指标 self._metrics = { 'sensitive_fields_sent': 0, # 敏感字段上送次数 'anonymized_fields': 0, # 脱敏字段次数 'user_disabled_fields': 0, # 用户关闭的字段数 'total_requests': 0, # 总请求次数 } def _load_settings(self) -> PrivacySettings: """加载隐私设置""" if self.config_file.exists(): try: import json with open(self.config_file, 'r', encoding='utf-8') as f: data = json.load(f) return PrivacySettings.from_dict(data) except Exception: pass return PrivacySettings() def save_settings(self) -> None: """保存隐私设置""" import json self.workspace.mkdir(parents=True, exist_ok=True) with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(self.settings.to_dict(), f, indent=2, ensure_ascii=False) def update_settings(self, **kwargs) -> None: """更新设置""" for key, value in kwargs.items(): if hasattr(self.settings, key): setattr(self.settings, key, value) self.save_settings() # 更新度量:统计用户关闭的字段数 disabled_count = sum(1 for k, v in self.settings.to_dict().items() if k.startswith('send_') and not v) self._metrics['user_disabled_fields'] = disabled_count def anonymize_path(self, path: Path) -> str: """路径脱敏""" if not self.settings.anonymize_paths: return str(path) self._metrics['anonymized_fields'] += 1 # 替换用户名 path_str = str(path) if self.settings.anonymize_username: username = os.getenv('USERNAME') or os.getenv('USER') if username: path_str = path_str.replace(username, '') # 替换主目录 home = str(Path.home()) if home in path_str: path_str = path_str.replace(home, '') return path_str def get_environment_info(self, scenario: str = 'chat') -> str: """ 获取环境信息(按场景和设置过滤) Args: scenario: 场景类型 ('chat', 'guidance', 'execution') """ self._metrics['total_requests'] += 1 info_parts = [] # 场景化最小化策略 if scenario == 'chat' and self.settings.chat_minimal_info: # 对话场景:仅提供必要信息 if self.settings.send_os_info: os_name = platform.system() info_parts.append(f"操作系统: {os_name}") if self.settings.send_python_version: python_version = sys.version.split()[0] info_parts.append(f"Python版本: {python_version}") # 对话场景不发送路径信息 return "\n".join(info_parts) if info_parts else "(环境信息已最小化)" # 指导场景或执行场景:根据用户设置提供信息 if self.settings.send_os_info: os_name = platform.system() os_version = platform.version() os_release = platform.release() info_parts.append(f"操作系统: {os_name} {os_release} ({os_version})") if self.settings.send_python_version: python_version = sys.version.split()[0] info_parts.append(f"Python版本: {python_version}") if self.settings.send_architecture: arch = platform.machine() info_parts.append(f"系统架构: {arch}") if self.settings.send_home_dir: home_dir = Path.home() info_parts.append(f"用户主目录: {self.anonymize_path(home_dir)}") self._metrics['sensitive_fields_sent'] += 1 if self.settings.send_workspace_path: info_parts.append(f"工作空间: {self.anonymize_path(self.workspace)}") if self.settings.send_current_dir: cwd = Path(os.getcwd()) info_parts.append(f"当前目录: {self.anonymize_path(cwd)}") self._metrics['sensitive_fields_sent'] += 1 return "\n".join(info_parts) if info_parts else "(环境信息已禁用)" def get_metrics(self) -> Dict[str, Any]: """获取度量指标""" total = self._metrics['total_requests'] return { 'sensitive_fields_sent': self._metrics['sensitive_fields_sent'], 'anonymized_fields': self._metrics['anonymized_fields'], 'user_disabled_fields': self._metrics['user_disabled_fields'], 'total_requests': total, 'sensitive_ratio': self._metrics['sensitive_fields_sent'] / total if total > 0 else 0, 'anonymization_ratio': self._metrics['anonymized_fields'] / total if total > 0 else 0, } def export_metrics(self) -> str: """导出度量指标报告""" metrics = self.get_metrics() return f"""隐私保护度量报告 ================== 总请求次数: {metrics['total_requests']} 敏感字段上送次数: {metrics['sensitive_fields_sent']} 敏感字段上送比率: {metrics['sensitive_ratio']:.1%} 脱敏处理次数: {metrics['anonymized_fields']} 脱敏处理比率: {metrics['anonymization_ratio']:.1%} 用户关闭字段数: {metrics['user_disabled_fields']} 当前隐私设置: {self._format_settings()} """ def _format_settings(self) -> str: """格式化设置""" lines = [] settings_dict = self.settings.to_dict() lines.append("环境信息采集:") for key in ['send_os_info', 'send_python_version', 'send_architecture', 'send_home_dir', 'send_workspace_path', 'send_current_dir']: status = "✓" if settings_dict[key] else "✗" name = key.replace('send_', '').replace('_', ' ').title() lines.append(f" {status} {name}") lines.append("\n脱敏策略:") for key in ['anonymize_paths', 'anonymize_username']: status = "✓" if settings_dict[key] else "✗" name = key.replace('anonymize_', '').replace('_', ' ').title() lines.append(f" {status} {name}") lines.append("\n场景化策略:") for key in ['chat_minimal_info', 'guidance_full_info']: status = "✓" if settings_dict[key] else "✗" name = key.replace('_', ' ').title() lines.append(f" {status} {name}") return "\n".join(lines) # 全局单例 _privacy_manager: Optional[PrivacyManager] = None def get_privacy_manager(workspace: Path) -> PrivacyManager: """获取隐私管理器单例""" global _privacy_manager if _privacy_manager is None: _privacy_manager = PrivacyManager(workspace) return _privacy_manager def reset_privacy_manager() -> None: """重置隐私管理器(用于测试)""" global _privacy_manager _privacy_manager = None