- Renamed `check_environment` to `check_api_key_configured` for clarity, simplifying the API key validation logic. - Removed the blocking behavior of the API key check during application startup, allowing the app to run while providing a prompt for configuration. - Updated `LocalAgentApp` to accept an `api_configured` parameter, enabling conditional messaging for API key setup. - Enhanced the `SandboxRunner` to support backup management and improved execution result handling with detailed metrics. - Integrated data governance strategies into the `HistoryManager`, ensuring compliance and improved data management. - Added privacy settings and metrics tracking across various components to enhance user experience and application safety.
249 lines
9.2 KiB
Python
249 lines
9.2 KiB
Python
"""
|
|
隐私配置管理模块
|
|
管理环境信息采集的最小化策略和用户控制开关
|
|
"""
|
|
|
|
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, '<USER>')
|
|
|
|
# 替换主目录
|
|
home = str(Path.home())
|
|
if home in path_str:
|
|
path_str = path_str.replace(home, '<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
|
|
|