feat: refactor API key configuration and enhance application initialization
- 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.
This commit is contained in:
248
app/privacy_config.py
Normal file
248
app/privacy_config.py
Normal file
@@ -0,0 +1,248 @@
|
||||
"""
|
||||
隐私配置管理模块
|
||||
管理环境信息采集的最小化策略和用户控制开关
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user