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:
Mimikko-zeus
2026-02-27 14:32:30 +08:00
parent ab5bbff6f7
commit 8a538bb950
58 changed files with 13457 additions and 350 deletions

View File

@@ -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):
"""运行应用"""