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:
553
app/agent.py
553
app/agent.py
@@ -28,12 +28,22 @@ from intent.labels import CHAT, EXECUTION, GUIDANCE
|
||||
from safety.rule_checker import check_code_safety
|
||||
from safety.llm_reviewer import review_code_safety, LLMReviewResult
|
||||
from executor.sandbox_runner import SandboxRunner, ExecutionResult
|
||||
from app.exceptions import (
|
||||
RequirementAnalysisException,
|
||||
CriticalInfoMissingException,
|
||||
AmbiguousRequirementException,
|
||||
LowConfidenceException,
|
||||
CheckerFailureException,
|
||||
classify_requirement_error
|
||||
)
|
||||
from ui.chat_view import ChatView
|
||||
from ui.task_guide_view import TaskGuideView
|
||||
from ui.history_view import HistoryView
|
||||
from ui.settings_view import SettingsView
|
||||
from ui.clarify_view import ClarifyView
|
||||
from ui.clear_confirm_dialog import show_clear_confirm_dialog
|
||||
from history.manager import get_history_manager, HistoryManager
|
||||
from app.privacy_config import get_privacy_manager, PrivacyManager
|
||||
|
||||
|
||||
class LocalAgentApp:
|
||||
@@ -46,11 +56,15 @@ class LocalAgentApp:
|
||||
3. 处理用户交互
|
||||
"""
|
||||
|
||||
def __init__(self, project_root: Path):
|
||||
def __init__(self, project_root: Path, api_configured: bool = True):
|
||||
self.project_root: Path = project_root
|
||||
self.workspace: Path = project_root / "workspace"
|
||||
self.runner: SandboxRunner = SandboxRunner(str(self.workspace))
|
||||
self.history: HistoryManager = get_history_manager(self.workspace)
|
||||
self.privacy: PrivacyManager = get_privacy_manager(self.workspace)
|
||||
|
||||
# API 配置状态
|
||||
self._api_configured = api_configured
|
||||
|
||||
# 当前任务状态
|
||||
self.current_task: Optional[Dict[str, Any]] = None
|
||||
@@ -66,6 +80,7 @@ class LocalAgentApp:
|
||||
self.history_view: Optional[HistoryView] = None
|
||||
self.settings_view: Optional[SettingsView] = None
|
||||
self.clarify_view: Optional[ClarifyView] = None
|
||||
self.privacy_view = None # 隐私设置视图
|
||||
|
||||
# 需求澄清状态
|
||||
self._clarify_state: Optional[Dict[str, Any]] = None
|
||||
@@ -103,9 +118,29 @@ class LocalAgentApp:
|
||||
on_show_settings=self._show_settings
|
||||
)
|
||||
|
||||
# 设置隐私设置回调
|
||||
self.chat_view.on_show_privacy = self._show_privacy
|
||||
|
||||
# 设置清空上下文的回调
|
||||
self.chat_view.set_clear_context_callback(self._clear_chat_context)
|
||||
|
||||
# 如果未配置 API Key,显示提示
|
||||
if not self._api_configured:
|
||||
self.chat_view.add_message(
|
||||
"⚠️ 尚未配置 API Key,请点击右上角「设置」按钮进行配置。\n"
|
||||
"获取 API Key: https://siliconflow.cn",
|
||||
'error'
|
||||
)
|
||||
|
||||
# 度量指标记录
|
||||
self._metrics = {
|
||||
'clarification_triggered': 0, # 澄清触发次数
|
||||
'direct_execution': 0, # 直接执行次数
|
||||
'user_modifications': 0, # 用户二次修改次数
|
||||
'ambiguity_failures': 0, # 需求歧义导致失败次数
|
||||
'total_tasks': 0 # 总任务数
|
||||
}
|
||||
|
||||
# 定期检查后台任务结果
|
||||
self._check_queue()
|
||||
|
||||
@@ -152,10 +187,20 @@ class LocalAgentApp:
|
||||
self.chat_view.hide_loading()
|
||||
|
||||
if error:
|
||||
# 记录配置变更后的首次调用失败
|
||||
from llm.config_metrics import get_config_metrics
|
||||
metrics = get_config_metrics(self.workspace)
|
||||
metrics.record_first_call(success=False, error_message=str(error))
|
||||
|
||||
self.chat_view.add_message(f"意图识别失败: {str(error)}", 'error')
|
||||
self.chat_view.set_input_enabled(True)
|
||||
return
|
||||
|
||||
# 记录配置变更后的首次调用成功
|
||||
from llm.config_metrics import get_config_metrics
|
||||
metrics = get_config_metrics(self.workspace)
|
||||
metrics.record_first_call(success=True)
|
||||
|
||||
if intent_result.label == CHAT:
|
||||
# 对话模式
|
||||
self._handle_chat(user_input, intent_result)
|
||||
@@ -225,42 +270,25 @@ class LocalAgentApp:
|
||||
|
||||
self.chat_view.set_input_enabled(True)
|
||||
|
||||
def _get_system_environment_info(self) -> str:
|
||||
"""获取当前系统运行环境信息"""
|
||||
info_parts = []
|
||||
def _get_system_environment_info(self, scenario: str = 'chat') -> str:
|
||||
"""
|
||||
获取当前系统运行环境信息(隐私保护版本)
|
||||
|
||||
# 操作系统信息
|
||||
os_name = platform.system()
|
||||
os_version = platform.version()
|
||||
os_release = platform.release()
|
||||
info_parts.append(f"操作系统: {os_name} {os_release} ({os_version})")
|
||||
|
||||
# Python 版本
|
||||
python_version = sys.version.split()[0]
|
||||
info_parts.append(f"Python版本: {python_version}")
|
||||
|
||||
# 系统架构
|
||||
arch = platform.machine()
|
||||
info_parts.append(f"系统架构: {arch}")
|
||||
|
||||
# 用户主目录
|
||||
home_dir = Path.home()
|
||||
info_parts.append(f"用户主目录: {home_dir}")
|
||||
|
||||
# 工作空间路径
|
||||
info_parts.append(f"工作空间: {self.workspace}")
|
||||
|
||||
# 当前工作目录
|
||||
cwd = os.getcwd()
|
||||
info_parts.append(f"当前目录: {cwd}")
|
||||
|
||||
return "\n".join(info_parts)
|
||||
Args:
|
||||
scenario: 场景类型 ('chat', 'guidance', 'execution')
|
||||
"""
|
||||
return self.privacy.get_environment_info(scenario)
|
||||
|
||||
def _build_chat_messages(self) -> List[Dict[str, str]]:
|
||||
"""构建带上下文的消息列表"""
|
||||
env_info = self._get_system_environment_info()
|
||||
env_info = self._get_system_environment_info(scenario='chat')
|
||||
|
||||
system_prompt = f"""你是一个智能助手,可以回答各种问题。请用中文回答。
|
||||
system_prompt = f"""你是 LocalAgent,一个本地运行的 AI 助手。你可以帮助用户回答问题、处理文件等任务。请用中文回答。
|
||||
|
||||
## 你的身份
|
||||
- 名称:LocalAgent
|
||||
- 定位:本地 AI 助手
|
||||
- 能力:回答问题、协助文件处理、提供操作指导
|
||||
|
||||
## 用户运行环境
|
||||
{env_info}
|
||||
@@ -268,7 +296,8 @@ class LocalAgentApp:
|
||||
## 注意事项
|
||||
- 如果用户的问题涉及之前的对话内容,请结合上下文进行回答
|
||||
- 根据用户的操作系统和环境,给出适合其系统的建议和解答
|
||||
- 如果涉及文件路径,请使用适合用户操作系统的路径格式"""
|
||||
- 如果涉及文件路径,请使用适合用户操作系统的路径格式
|
||||
- 当被问到"你是谁"时,请介绍自己是 LocalAgent,而不是其他 AI 助手"""
|
||||
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
messages.extend(self._chat_context)
|
||||
@@ -304,8 +333,8 @@ class LocalAgentApp:
|
||||
client = get_client()
|
||||
model = os.getenv("CHAT_MODEL_NAME") or os.getenv("GENERATION_MODEL_NAME")
|
||||
|
||||
# 获取环境信息
|
||||
env_info = self._get_system_environment_info()
|
||||
# 获取环境信息(指导场景)
|
||||
env_info = self._get_system_environment_info(scenario='guidance')
|
||||
|
||||
# 构建专门的操作指导 Prompt
|
||||
system_prompt = f"""你是一个操作指导助手。用户询问的是一个无法通过本地Python代码完成的任务(如软件设置、系统配置、GUI操作等)。
|
||||
@@ -348,6 +377,9 @@ class LocalAgentApp:
|
||||
|
||||
def _handle_execution(self, user_input: str, intent_result: IntentResult):
|
||||
"""处理执行任务"""
|
||||
# 记录总任务数
|
||||
self._metrics['total_tasks'] += 1
|
||||
|
||||
self.chat_view.add_message(
|
||||
f"识别为执行任务 (置信度: {intent_result.confidence:.0%})\n原因: {intent_result.reason}",
|
||||
'system'
|
||||
@@ -359,29 +391,81 @@ class LocalAgentApp:
|
||||
'intent_result': intent_result
|
||||
}
|
||||
|
||||
# 先查找是否有相似的成功任务
|
||||
similar_record = self.history.find_similar_success(user_input)
|
||||
if similar_record:
|
||||
# 询问用户是否复用
|
||||
task_desc = similar_record.task_summary or similar_record.user_input[:50]
|
||||
msg = (
|
||||
f"发现相似的成功任务:\n\n"
|
||||
f"任务: {task_desc}\n"
|
||||
f"时间: {similar_record.timestamp}\n\n"
|
||||
f"是否直接复用该任务的代码?\n"
|
||||
f"(选择[否]将生成新代码)"
|
||||
# 先查找是否有相似的成功任务(使用增强匹配)
|
||||
result = self.history.find_similar_success(user_input, return_details=True)
|
||||
if result:
|
||||
similar_record, similarity_score, differences = result
|
||||
|
||||
# 统计关键差异
|
||||
critical_diffs = [d for d in differences if d.importance == 'critical']
|
||||
|
||||
# 记录复用建议被提供
|
||||
from history.reuse_metrics import get_reuse_metrics
|
||||
metrics = get_reuse_metrics(self.workspace)
|
||||
metrics.record_reuse_offered(
|
||||
original_task_id=similar_record.task_id,
|
||||
similarity_score=similarity_score,
|
||||
differences_count=len(differences),
|
||||
critical_differences=len(critical_diffs)
|
||||
)
|
||||
result = messagebox.askyesno("发现相似任务", msg, icon='question')
|
||||
if result:
|
||||
# 复用代码
|
||||
|
||||
# 显示增强的复用确认对话框
|
||||
from ui.reuse_confirm_dialog import show_reuse_confirm_dialog
|
||||
|
||||
def on_reuse_confirm():
|
||||
# 用户接受复用
|
||||
metrics.record_reuse_accepted(
|
||||
original_task_id=similar_record.task_id,
|
||||
similarity_score=similarity_score,
|
||||
differences_count=len(differences),
|
||||
critical_differences=len(critical_diffs)
|
||||
)
|
||||
|
||||
# 复用代码 - 需要重新进行安全检查
|
||||
self.current_task['execution_plan'] = similar_record.execution_plan
|
||||
self.current_task['code'] = similar_record.code
|
||||
self.current_task['task_summary'] = similar_record.task_summary
|
||||
self.current_task['is_reuse'] = True
|
||||
self.current_task['reuse_original_task_id'] = similar_record.task_id
|
||||
|
||||
self.chat_view.add_message("复用历史成功代码,请确认执行", 'system')
|
||||
self._show_task_guide()
|
||||
return
|
||||
self.chat_view.add_message(
|
||||
f"复用历史成功代码 (相似度: {similarity_score:.0%}),正在进行安全复检...",
|
||||
'system'
|
||||
)
|
||||
|
||||
# 强制进行安全检查
|
||||
self._perform_safety_check(self.current_task['code'])
|
||||
|
||||
def on_reuse_reject():
|
||||
# 用户拒绝复用
|
||||
metrics.record_reuse_rejected(
|
||||
original_task_id=similar_record.task_id,
|
||||
similarity_score=similarity_score,
|
||||
differences_count=len(differences),
|
||||
critical_differences=len(critical_diffs)
|
||||
)
|
||||
|
||||
self.chat_view.add_message("将生成新代码", 'system')
|
||||
self.chat_view.show_loading("正在分析需求完整性")
|
||||
|
||||
# 继续正常流程
|
||||
self._run_in_thread(
|
||||
self._check_requirement_completeness,
|
||||
self._on_requirement_checked,
|
||||
user_input
|
||||
)
|
||||
|
||||
# 显示对话框
|
||||
show_reuse_confirm_dialog(
|
||||
parent=self.root,
|
||||
task_summary=similar_record.task_summary or similar_record.user_input[:50],
|
||||
timestamp=similar_record.timestamp,
|
||||
similarity_score=similarity_score,
|
||||
differences=differences,
|
||||
on_confirm=on_reuse_confirm,
|
||||
on_reject=on_reuse_reject
|
||||
)
|
||||
return
|
||||
|
||||
self.chat_view.show_loading("正在分析需求完整性")
|
||||
|
||||
@@ -453,30 +537,91 @@ class LocalAgentApp:
|
||||
|
||||
def _on_requirement_checked(self, result: Optional[Dict], error: Optional[Exception]):
|
||||
"""需求完整性检查完成回调"""
|
||||
if error:
|
||||
# 检查失败,继续正常流程
|
||||
self.chat_view.hide_loading()
|
||||
self.chat_view.add_message(f"需求分析失败,将直接生成代码: {str(error)}", 'system')
|
||||
self._continue_to_code_generation()
|
||||
return
|
||||
# 分类异常
|
||||
exception = classify_requirement_error(result, error)
|
||||
|
||||
is_complete = result.get('is_complete', True)
|
||||
confidence = result.get('confidence', 1.0)
|
||||
self.chat_view.hide_loading()
|
||||
|
||||
# 如果需求完整或置信度较高,直接继续
|
||||
if is_complete and confidence >= 0.7:
|
||||
self.chat_view.hide_loading()
|
||||
# 保存建议的默认值
|
||||
self.current_task['suggested_defaults'] = result.get('suggested_defaults', {})
|
||||
self._continue_to_code_generation()
|
||||
else:
|
||||
# 需求不完整,启动澄清流程
|
||||
self.chat_view.hide_loading()
|
||||
# 根据异常严重程度决定处理策略
|
||||
if isinstance(exception, CriticalInfoMissingException):
|
||||
# 关键信息缺失 - 强制澄清
|
||||
self._metrics['clarification_triggered'] += 1
|
||||
self.chat_view.add_message(
|
||||
f"需求信息不完整 (原因: {result.get('reason', '缺少关键信息')})\n正在启动需求澄清...",
|
||||
'system'
|
||||
f"❌ 关键信息缺失,无法继续执行\n"
|
||||
f"原因: {str(exception)}\n"
|
||||
f"缺失字段: {', '.join(exception.missing_fields)}\n"
|
||||
f"正在启动需求澄清流程...",
|
||||
'error'
|
||||
)
|
||||
self._start_clarification()
|
||||
|
||||
elif isinstance(exception, AmbiguousRequirementException):
|
||||
# 需求歧义 - 强制澄清
|
||||
self._metrics['clarification_triggered'] += 1
|
||||
self.chat_view.add_message(
|
||||
f"⚠️ 需求存在歧义\n"
|
||||
f"原因: {str(exception)}\n"
|
||||
f"模糊部分: {', '.join(exception.ambiguous_parts)}\n"
|
||||
f"正在启动需求澄清流程...",
|
||||
'warning'
|
||||
)
|
||||
self._start_clarification()
|
||||
|
||||
elif isinstance(exception, LowConfidenceException):
|
||||
# 低置信度 - 建议澄清但允许用户选择
|
||||
self._metrics['clarification_triggered'] += 1
|
||||
self.chat_view.add_message(
|
||||
f"⚠️ 需求置信度较低 ({exception.confidence:.0%})\n"
|
||||
f"原因: {str(exception)}\n"
|
||||
f"建议进行需求澄清以提高准确性",
|
||||
'warning'
|
||||
)
|
||||
# 提供选择:澄清或继续
|
||||
self._show_low_confidence_options(result)
|
||||
|
||||
elif isinstance(exception, CheckerFailureException):
|
||||
# 检查器失败 - 降级处理,但记录警告
|
||||
self._metrics['direct_execution'] += 1
|
||||
self.chat_view.add_message(
|
||||
f"⚠️ 需求完整性检查器异常: {str(exception)}\n"
|
||||
f"将尝试直接生成代码,但可能存在理解偏差",
|
||||
'warning'
|
||||
)
|
||||
# 保存建议的默认值(如果有)
|
||||
if result:
|
||||
self.current_task['suggested_defaults'] = result.get('suggested_defaults', {})
|
||||
self._continue_to_code_generation()
|
||||
|
||||
else:
|
||||
# 需求完整 - 直接继续
|
||||
self._metrics['direct_execution'] += 1
|
||||
# 保存建议的默认值
|
||||
if result:
|
||||
self.current_task['suggested_defaults'] = result.get('suggested_defaults', {})
|
||||
self._continue_to_code_generation()
|
||||
|
||||
def _show_low_confidence_options(self, result: Optional[Dict]):
|
||||
"""显示低置信度时的选项"""
|
||||
from tkinter import messagebox
|
||||
|
||||
choice = messagebox.askyesno(
|
||||
"需求澄清",
|
||||
"检测到需求描述可能不够清晰,建议进行澄清以提高准确性。\n\n"
|
||||
"是否启动需求澄清流程?\n\n"
|
||||
"选择「是」:通过问答澄清需求细节\n"
|
||||
"选择「否」:使用默认参数直接生成代码",
|
||||
icon='warning'
|
||||
)
|
||||
|
||||
if choice:
|
||||
# 用户选择澄清
|
||||
self._start_clarification()
|
||||
else:
|
||||
# 用户选择继续
|
||||
self._metrics['direct_execution'] += 1
|
||||
if result:
|
||||
self.current_task['suggested_defaults'] = result.get('suggested_defaults', {})
|
||||
self._continue_to_code_generation()
|
||||
|
||||
def _continue_to_code_generation(self):
|
||||
"""继续代码生成流程"""
|
||||
@@ -800,32 +945,8 @@ class LocalAgentApp:
|
||||
self.current_task['code'] = code
|
||||
self.chat_view.update_loading_text("正在进行安全检查")
|
||||
|
||||
# 硬规则检查(同步,很快)
|
||||
rule_result = check_code_safety(code)
|
||||
if not rule_result.passed:
|
||||
self.chat_view.hide_loading()
|
||||
violations = "\n".join(f" • {v}" for v in rule_result.violations)
|
||||
self.chat_view.add_message(
|
||||
f"安全检查未通过,任务已取消:\n{violations}",
|
||||
'error'
|
||||
)
|
||||
self.chat_view.set_input_enabled(True)
|
||||
self.current_task = None
|
||||
return
|
||||
|
||||
# 保存警告信息,传递给 LLM 审查
|
||||
self.current_task['warnings'] = rule_result.warnings
|
||||
|
||||
# 在后台线程进行 LLM 安全审查
|
||||
self._run_in_thread(
|
||||
lambda: review_code_safety(
|
||||
self.current_task['user_input'],
|
||||
self.current_task['execution_plan'],
|
||||
code,
|
||||
rule_result.warnings # 传递警告给 LLM
|
||||
),
|
||||
self._on_safety_reviewed
|
||||
)
|
||||
# 统一调用安全检查流程
|
||||
self._perform_safety_check(code)
|
||||
|
||||
def _on_safety_reviewed(self, review_result, error: Optional[Exception]):
|
||||
"""安全审查完成回调"""
|
||||
@@ -846,13 +967,58 @@ class LocalAgentApp:
|
||||
self.current_task = None
|
||||
return
|
||||
|
||||
# 代码生成完成,清空 input 和 output 目录
|
||||
self.runner.clear_workspace(clear_input=True, clear_output=True)
|
||||
# 安全检查通过,检查工作区是否有内容
|
||||
has_content, file_count, size_str = self.runner.check_workspace_content()
|
||||
|
||||
self.chat_view.add_message("安全检查通过,请确认执行", 'system')
|
||||
if has_content:
|
||||
# 有内容,显示确认对话框
|
||||
self._show_clear_confirm_dialog(file_count, size_str)
|
||||
else:
|
||||
# 无内容,直接进入任务引导
|
||||
self.chat_view.add_message("安全检查通过,请确认执行", 'system')
|
||||
self._show_task_guide()
|
||||
|
||||
def _show_clear_confirm_dialog(self, file_count: int, size_str: str):
|
||||
"""显示清理确认对话框"""
|
||||
# 检查是否有最近的备份
|
||||
latest_backup = self.runner.backup_manager.get_latest_backup()
|
||||
has_recent_backup = latest_backup is not None
|
||||
|
||||
# 显示任务引导视图
|
||||
self._show_task_guide()
|
||||
def on_confirm(create_backup: bool):
|
||||
"""用户确认清空"""
|
||||
# 清空工作区(根据用户选择决定是否备份)
|
||||
backup_id = self.runner.clear_workspace(
|
||||
clear_input=True,
|
||||
clear_output=True,
|
||||
create_backup=create_backup
|
||||
)
|
||||
|
||||
if backup_id:
|
||||
self.chat_view.add_message(
|
||||
f"已备份工作区内容(备份 ID: {backup_id}),安全检查通过,请确认执行",
|
||||
'system'
|
||||
)
|
||||
else:
|
||||
self.chat_view.add_message("安全检查通过,请确认执行", 'system')
|
||||
|
||||
# 显示任务引导视图
|
||||
self._show_task_guide()
|
||||
|
||||
def on_cancel():
|
||||
"""用户取消"""
|
||||
self.chat_view.add_message("已取消执行", 'system')
|
||||
self.chat_view.set_input_enabled(True)
|
||||
self.current_task = None
|
||||
|
||||
# 显示对话框
|
||||
show_clear_confirm_dialog(
|
||||
parent=self.root,
|
||||
file_count=file_count,
|
||||
total_size=size_str,
|
||||
has_recent_backup=has_recent_backup,
|
||||
on_confirm=on_confirm,
|
||||
on_cancel=on_cancel
|
||||
)
|
||||
|
||||
def _generate_execution_plan(self, user_input: str) -> str:
|
||||
"""生成执行计划(使用流式传输)"""
|
||||
@@ -965,7 +1131,11 @@ class LocalAgentApp:
|
||||
|
||||
# 在后台线程执行
|
||||
def do_execute():
|
||||
return self.runner.execute(self.current_task['code'])
|
||||
return self.runner.execute(
|
||||
self.current_task['code'],
|
||||
user_input=self.current_task.get('user_input', ''),
|
||||
is_retry=self.current_task.get('is_retry', False)
|
||||
)
|
||||
|
||||
self._run_in_thread(
|
||||
do_execute,
|
||||
@@ -976,6 +1146,9 @@ class LocalAgentApp:
|
||||
"""执行完成回调"""
|
||||
if error:
|
||||
messagebox.showerror("执行错误", f"执行失败: {str(error)}")
|
||||
# 记录失败指标
|
||||
if not result or not result.success:
|
||||
self._metrics['ambiguity_failures'] += 1
|
||||
else:
|
||||
# 保存历史记录
|
||||
if self.current_task:
|
||||
@@ -993,6 +1166,20 @@ class LocalAgentApp:
|
||||
log_path=result.log_path,
|
||||
task_summary=self.current_task.get('task_summary', '')
|
||||
)
|
||||
|
||||
# 记录失败指标
|
||||
if not result.success:
|
||||
self._metrics['ambiguity_failures'] += 1
|
||||
|
||||
# 如果是复用任务,记录执行结果
|
||||
if self.current_task.get('is_reuse') and self.current_task.get('reuse_original_task_id'):
|
||||
from history.reuse_metrics import get_reuse_metrics
|
||||
metrics = get_reuse_metrics(self.workspace)
|
||||
metrics.record_reuse_execution(
|
||||
original_task_id=self.current_task['reuse_original_task_id'],
|
||||
new_task_id=result.task_id,
|
||||
success=result.success
|
||||
)
|
||||
|
||||
self._show_execution_result(result)
|
||||
# 刷新输出文件列表
|
||||
@@ -1002,33 +1189,62 @@ class LocalAgentApp:
|
||||
self._back_to_chat()
|
||||
|
||||
def _show_execution_result(self, result: ExecutionResult):
|
||||
"""显示执行结果"""
|
||||
if result.success:
|
||||
status = "执行成功"
|
||||
else:
|
||||
status = "执行失败"
|
||||
"""显示执行结果(支持三态)"""
|
||||
status_display = result.get_status_display()
|
||||
|
||||
message = f"""{status}
|
||||
# 构建统计信息
|
||||
stats_info = ""
|
||||
if result.total_count > 0:
|
||||
stats_info = f"""
|
||||
统计信息:
|
||||
总数: {result.total_count} 个
|
||||
成功: {result.success_count} 个
|
||||
失败: {result.failed_count} 个
|
||||
成功率: {result.success_rate:.1%}
|
||||
"""
|
||||
|
||||
message = f"""{status_display}
|
||||
|
||||
任务 ID: {result.task_id}
|
||||
耗时: {result.duration_ms} ms
|
||||
|
||||
{stats_info}
|
||||
输出:
|
||||
{result.stdout if result.stdout else '(无输出)'}
|
||||
|
||||
{f'错误信息: {result.stderr}' if result.stderr else ''}
|
||||
"""
|
||||
|
||||
if result.success:
|
||||
# 成功时显示结果并询问是否打开输出目录
|
||||
if result.status == 'success':
|
||||
# 全部成功:询问是否打开输出目录
|
||||
open_output = messagebox.askyesno(
|
||||
"执行结果",
|
||||
message + "\n\n是否打开输出文件夹?"
|
||||
)
|
||||
if open_output:
|
||||
os.startfile(str(self.workspace / "output"))
|
||||
|
||||
elif result.status == 'partial':
|
||||
# 部分成功:提供三个选项
|
||||
from tkinter import messagebox as mb
|
||||
choice = mb.askquestion(
|
||||
"执行结果",
|
||||
message + f"\n\n部分文件处理失败({result.failed_count}/{result.total_count})\n\n" +
|
||||
"是否查看输出文件夹?\n" +
|
||||
"(选择「否」可查看日志了解失败原因)",
|
||||
icon='warning'
|
||||
)
|
||||
if choice == 'yes':
|
||||
os.startfile(str(self.workspace / "output"))
|
||||
else:
|
||||
# 询问是否查看日志
|
||||
open_log = mb.askyesno(
|
||||
"查看日志",
|
||||
"是否打开日志文件查看失败详情?"
|
||||
)
|
||||
if open_log and result.log_path:
|
||||
os.startfile(result.log_path)
|
||||
else:
|
||||
# 失败时显示结果并询问是否打开日志
|
||||
# 全部失败:询问是否打开日志
|
||||
open_log = messagebox.askyesno(
|
||||
"执行结果",
|
||||
message + "\n\n是否打开日志文件查看详情?"
|
||||
@@ -1096,10 +1312,11 @@ class LocalAgentApp:
|
||||
}
|
||||
|
||||
self.chat_view.add_message(f"复用历史任务: {record.task_summary or record.user_input[:30]}", 'system')
|
||||
self.chat_view.add_message("已加载历史代码,请确认执行", 'system')
|
||||
self.chat_view.add_message("已加载历史代码,正在进行安全复检...", 'system')
|
||||
self.chat_view.show_loading("正在进行安全检查")
|
||||
|
||||
# 直接显示任务引导视图(跳过代码生成)
|
||||
self._show_task_guide()
|
||||
# 强制进行安全检查(不跳过)
|
||||
self._perform_safety_check(self.current_task['code'])
|
||||
|
||||
def _on_retry_task(self, record):
|
||||
"""重试失败的任务(AI 修复)"""
|
||||
@@ -1183,31 +1400,8 @@ class LocalAgentApp:
|
||||
self.current_task['code'] = code
|
||||
self.chat_view.update_loading_text("正在进行安全检查")
|
||||
|
||||
# 硬规则检查
|
||||
rule_result = check_code_safety(code)
|
||||
if not rule_result.passed:
|
||||
self.chat_view.hide_loading()
|
||||
violations = "\n".join(f" • {v}" for v in rule_result.violations)
|
||||
self.chat_view.add_message(
|
||||
f"修复后的代码安全检查未通过:\n{violations}",
|
||||
'error'
|
||||
)
|
||||
self.chat_view.set_input_enabled(True)
|
||||
self.current_task = None
|
||||
return
|
||||
|
||||
self.current_task['warnings'] = rule_result.warnings
|
||||
|
||||
# LLM 安全审查
|
||||
self._run_in_thread(
|
||||
lambda: review_code_safety(
|
||||
self.current_task['user_input'],
|
||||
self.current_task['execution_plan'],
|
||||
code,
|
||||
rule_result.warnings
|
||||
),
|
||||
self._on_safety_reviewed
|
||||
)
|
||||
# 统一调用安全检查流程
|
||||
self._perform_safety_check(code)
|
||||
|
||||
def _show_settings(self):
|
||||
"""显示设置视图"""
|
||||
@@ -1231,11 +1425,82 @@ class LocalAgentApp:
|
||||
|
||||
self.chat_view.get_frame().pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
def _on_settings_saved(self):
|
||||
def _show_privacy(self):
|
||||
"""显示隐私设置视图"""
|
||||
# 隐藏聊天视图
|
||||
self.chat_view.get_frame().pack_forget()
|
||||
|
||||
# 创建隐私设置视图
|
||||
from ui.privacy_settings_view import PrivacySettingsView
|
||||
self.privacy_view = PrivacySettingsView(
|
||||
self.main_container,
|
||||
workspace=self.workspace,
|
||||
on_back=self._hide_privacy
|
||||
)
|
||||
self.privacy_view.show()
|
||||
|
||||
def _hide_privacy(self):
|
||||
"""隐藏隐私设置视图,返回聊天"""
|
||||
if self.privacy_view:
|
||||
self.privacy_view.hide()
|
||||
self.privacy_view = None
|
||||
|
||||
self.chat_view.get_frame().pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
def _on_settings_saved(self, connection_test_success: bool):
|
||||
"""设置保存后的回调"""
|
||||
# 配置已通过 set_key 保存并更新了环境变量
|
||||
# 可以在这里添加额外的处理逻辑
|
||||
pass
|
||||
# 客户端已在 settings_view 中重置并测试连接
|
||||
|
||||
# 更新 API 配置状态
|
||||
self._api_configured = connection_test_success
|
||||
|
||||
# 如果连接测试成功,在聊天视图中显示提示
|
||||
if connection_test_success:
|
||||
self.chat_view.add_message("✅ 配置已更新并生效,可以开始使用了", 'system')
|
||||
|
||||
def _perform_safety_check(self, code: str):
|
||||
"""
|
||||
统一的安全检查流程(硬规则 + LLM 审查)
|
||||
所有代码(新生成/复用/修复)都必须经过此流程
|
||||
"""
|
||||
# 记录复用任务复检
|
||||
from safety.security_metrics import get_metrics
|
||||
metrics = get_metrics()
|
||||
if self.current_task.get('is_reuse'):
|
||||
metrics.add_reuse_recheck()
|
||||
|
||||
# 硬规则检查(同步,很快)
|
||||
rule_result = check_code_safety(code)
|
||||
if not rule_result.passed:
|
||||
self.chat_view.hide_loading()
|
||||
violations = "\n".join(f" • {v}" for v in rule_result.violations)
|
||||
self.chat_view.add_message(
|
||||
f"安全检查未通过,任务已取消:\n{violations}",
|
||||
'error'
|
||||
)
|
||||
self.chat_view.set_input_enabled(True)
|
||||
|
||||
# 记录安全拦截指标
|
||||
if self.current_task.get('is_reuse'):
|
||||
metrics.add_reuse_block()
|
||||
|
||||
self.current_task = None
|
||||
return
|
||||
|
||||
# 保存警告信息,传递给 LLM 审查
|
||||
self.current_task['warnings'] = rule_result.warnings
|
||||
|
||||
# 在后台线程进行 LLM 安全审查
|
||||
self._run_in_thread(
|
||||
lambda: review_code_safety(
|
||||
self.current_task['user_input'],
|
||||
self.current_task['execution_plan'],
|
||||
code,
|
||||
rule_result.warnings # 传递警告给 LLM
|
||||
),
|
||||
self._on_safety_reviewed
|
||||
)
|
||||
|
||||
def run(self):
|
||||
"""运行应用"""
|
||||
|
||||
106
app/exceptions.py
Normal file
106
app/exceptions.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
需求分析异常分级系统
|
||||
用于区分不同类型的需求分析失败,并采取相应的处理策略
|
||||
"""
|
||||
|
||||
|
||||
class RequirementAnalysisException(Exception):
|
||||
"""需求分析异常基类"""
|
||||
|
||||
def __init__(self, message: str, severity: str = "medium"):
|
||||
"""
|
||||
Args:
|
||||
message: 异常描述
|
||||
severity: 严重程度 (critical/high/medium/low)
|
||||
"""
|
||||
super().__init__(message)
|
||||
self.severity = severity
|
||||
|
||||
|
||||
class CriticalInfoMissingException(RequirementAnalysisException):
|
||||
"""关键信息缺失异常 - 必须澄清才能继续"""
|
||||
|
||||
def __init__(self, message: str, missing_fields: list = None):
|
||||
super().__init__(message, severity="critical")
|
||||
self.missing_fields = missing_fields or []
|
||||
|
||||
|
||||
class AmbiguousRequirementException(RequirementAnalysisException):
|
||||
"""需求歧义异常 - 建议澄清"""
|
||||
|
||||
def __init__(self, message: str, ambiguous_parts: list = None):
|
||||
super().__init__(message, severity="high")
|
||||
self.ambiguous_parts = ambiguous_parts or []
|
||||
|
||||
|
||||
class LowConfidenceException(RequirementAnalysisException):
|
||||
"""低置信度异常 - 可以继续但建议澄清"""
|
||||
|
||||
def __init__(self, message: str, confidence: float = 0.0):
|
||||
super().__init__(message, severity="medium")
|
||||
self.confidence = confidence
|
||||
|
||||
|
||||
class CheckerFailureException(RequirementAnalysisException):
|
||||
"""检查器本身失败异常 - 可以降级处理"""
|
||||
|
||||
def __init__(self, message: str, original_error: Exception = None):
|
||||
super().__init__(message, severity="low")
|
||||
self.original_error = original_error
|
||||
|
||||
|
||||
def classify_requirement_error(result: dict = None, error: Exception = None) -> RequirementAnalysisException:
|
||||
"""
|
||||
根据检查结果或错误对象,分类异常类型
|
||||
|
||||
Args:
|
||||
result: 需求完整性检查结果
|
||||
error: 原始异常对象
|
||||
|
||||
Returns:
|
||||
分类后的异常对象
|
||||
"""
|
||||
# 如果是检查器本身失败
|
||||
if error is not None:
|
||||
return CheckerFailureException(
|
||||
f"需求完整性检查器失败: {str(error)}",
|
||||
original_error=error
|
||||
)
|
||||
|
||||
# 如果没有结果,视为检查器失败
|
||||
if result is None:
|
||||
return CheckerFailureException("需求完整性检查返回空结果")
|
||||
|
||||
is_complete = result.get('is_complete', True)
|
||||
confidence = result.get('confidence', 1.0)
|
||||
reason = result.get('reason', '未知原因')
|
||||
|
||||
# 明确标记为不完整
|
||||
if not is_complete:
|
||||
# 检查是否有关键信息缺失标记
|
||||
missing_info = result.get('missing_info', [])
|
||||
critical_fields = result.get('critical_fields', [])
|
||||
|
||||
if critical_fields or len(missing_info) > 2:
|
||||
# 关键信息缺失
|
||||
return CriticalInfoMissingException(
|
||||
f"关键信息缺失: {reason}",
|
||||
missing_fields=critical_fields or missing_info
|
||||
)
|
||||
else:
|
||||
# 一般歧义
|
||||
return AmbiguousRequirementException(
|
||||
f"需求存在歧义: {reason}",
|
||||
ambiguous_parts=missing_info
|
||||
)
|
||||
|
||||
# 标记为完整但置信度低
|
||||
if is_complete and confidence < 0.7:
|
||||
return LowConfidenceException(
|
||||
f"需求置信度较低 ({confidence:.1%}): {reason}",
|
||||
confidence=confidence
|
||||
)
|
||||
|
||||
# 其他情况视为检查器问题
|
||||
return CheckerFailureException(f"需求检查结果异常: {reason}")
|
||||
|
||||
165
app/metrics_logger.py
Normal file
165
app/metrics_logger.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
度量指标记录和导出模块
|
||||
用于记录需求分析相关的度量指标
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
class MetricsLogger:
|
||||
"""度量指标记录器"""
|
||||
|
||||
def __init__(self, workspace: Path):
|
||||
"""
|
||||
Args:
|
||||
workspace: 工作空间路径
|
||||
"""
|
||||
self.workspace = workspace
|
||||
self.metrics_file = workspace / "metrics" / "requirement_analysis.json"
|
||||
self.metrics_file.parent.mkdir(exist_ok=True)
|
||||
|
||||
# 加载现有指标
|
||||
self.metrics = self._load_metrics()
|
||||
|
||||
def _load_metrics(self) -> Dict[str, Any]:
|
||||
"""加载现有指标"""
|
||||
if self.metrics_file.exists():
|
||||
try:
|
||||
with open(self.metrics_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 返回默认指标结构
|
||||
return {
|
||||
'total_tasks': 0,
|
||||
'clarification_triggered': 0,
|
||||
'direct_execution': 0,
|
||||
'user_modifications': 0,
|
||||
'ambiguity_failures': 0,
|
||||
'history': []
|
||||
}
|
||||
|
||||
def _save_metrics(self):
|
||||
"""保存指标到文件"""
|
||||
try:
|
||||
with open(self.metrics_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.metrics, f, ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
print(f"保存度量指标失败: {e}")
|
||||
|
||||
def record_task(self, task_type: str, details: Dict[str, Any] = None):
|
||||
"""
|
||||
记录任务
|
||||
|
||||
Args:
|
||||
task_type: 任务类型 (clarification/direct_execution/modification/failure)
|
||||
details: 任务详情
|
||||
"""
|
||||
self.metrics['total_tasks'] += 1
|
||||
|
||||
if task_type == 'clarification':
|
||||
self.metrics['clarification_triggered'] += 1
|
||||
elif task_type == 'direct_execution':
|
||||
self.metrics['direct_execution'] += 1
|
||||
elif task_type == 'modification':
|
||||
self.metrics['user_modifications'] += 1
|
||||
elif task_type == 'failure':
|
||||
self.metrics['ambiguity_failures'] += 1
|
||||
|
||||
# 记录历史
|
||||
record = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'type': task_type,
|
||||
'details': details or {}
|
||||
}
|
||||
self.metrics['history'].append(record)
|
||||
|
||||
# 限制历史记录数量
|
||||
if len(self.metrics['history']) > 1000:
|
||||
self.metrics['history'] = self.metrics['history'][-1000:]
|
||||
|
||||
self._save_metrics()
|
||||
|
||||
def get_summary(self) -> Dict[str, Any]:
|
||||
"""获取指标摘要"""
|
||||
total = self.metrics['total_tasks']
|
||||
if total == 0:
|
||||
return {
|
||||
'total_tasks': 0,
|
||||
'clarification_rate': 0.0,
|
||||
'direct_execution_rate': 0.0,
|
||||
'modification_rate': 0.0,
|
||||
'failure_rate': 0.0
|
||||
}
|
||||
|
||||
return {
|
||||
'total_tasks': total,
|
||||
'clarification_triggered': self.metrics['clarification_triggered'],
|
||||
'direct_execution': self.metrics['direct_execution'],
|
||||
'user_modifications': self.metrics['user_modifications'],
|
||||
'ambiguity_failures': self.metrics['ambiguity_failures'],
|
||||
'clarification_rate': self.metrics['clarification_triggered'] / total,
|
||||
'direct_execution_rate': self.metrics['direct_execution'] / total,
|
||||
'modification_rate': self.metrics['user_modifications'] / total,
|
||||
'failure_rate': self.metrics['ambiguity_failures'] / total
|
||||
}
|
||||
|
||||
def export_report(self, output_path: Path = None) -> str:
|
||||
"""
|
||||
导出度量报告
|
||||
|
||||
Args:
|
||||
output_path: 输出路径,如果为None则返回字符串
|
||||
|
||||
Returns:
|
||||
报告内容
|
||||
"""
|
||||
summary = self.get_summary()
|
||||
|
||||
report = f"""# 需求分析度量报告
|
||||
|
||||
生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||
|
||||
## 总体统计
|
||||
|
||||
- 总任务数: {summary['total_tasks']}
|
||||
- 澄清触发次数: {summary['clarification_triggered']}
|
||||
- 直接执行次数: {summary['direct_execution']}
|
||||
- 用户二次修改次数: {summary['user_modifications']}
|
||||
- 需求歧义导致失败次数: {summary['ambiguity_failures']}
|
||||
|
||||
## 比率分析
|
||||
|
||||
- 澄清触发率: {summary['clarification_rate']:.1%}
|
||||
- 直接执行率: {summary['direct_execution_rate']:.1%}
|
||||
- 用户二次修改率: {summary['modification_rate']:.1%}
|
||||
- 需求歧义失败率: {summary['failure_rate']:.1%}
|
||||
|
||||
## 建议
|
||||
|
||||
"""
|
||||
|
||||
# 根据指标给出建议
|
||||
if summary['failure_rate'] > 0.2:
|
||||
report += "- ⚠️ 需求歧义失败率较高,建议提高澄清触发阈值\n"
|
||||
|
||||
if summary['clarification_rate'] < 0.1:
|
||||
report += "- ⚠️ 澄清触发率较低,可能存在模糊需求被直接执行的风险\n"
|
||||
|
||||
if summary['modification_rate'] > 0.3:
|
||||
report += "- ⚠️ 用户二次修改率较高,说明初次生成的代码质量需要改进\n"
|
||||
|
||||
if summary['direct_execution_rate'] > 0.8 and summary['failure_rate'] < 0.1:
|
||||
report += "- ✅ 直接执行率高且失败率低,需求分析效果良好\n"
|
||||
|
||||
if output_path:
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(report)
|
||||
|
||||
return report
|
||||
|
||||
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