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

252
history/reuse_metrics.py Normal file
View File

@@ -0,0 +1,252 @@
"""
任务复用度量指标收集模块
"""
import json
from datetime import datetime
from pathlib import Path
from typing import Optional, Dict, List
from dataclasses import dataclass, asdict
@dataclass
class ReuseEvent:
"""复用事件记录"""
timestamp: str
original_task_id: str # 被复用的任务 ID
new_task_id: Optional[str] # 新任务 ID如果执行了
similarity_score: float # 相似度分数
user_action: str # 用户操作accepted/rejected/rollback/failed
differences_count: int # 差异数量
critical_differences: int # 关键差异数量
execution_success: Optional[bool] # 执行是否成功(如果执行了)
class ReuseMetrics:
"""复用指标管理器"""
def __init__(self, workspace_path: Path):
self.workspace = workspace_path
self.metrics_file = workspace_path / "reuse_metrics.json"
self._events: List[ReuseEvent] = []
self._load()
def _load(self):
"""加载指标数据"""
if self.metrics_file.exists():
try:
with open(self.metrics_file, 'r', encoding='utf-8') as f:
data = json.load(f)
self._events = [ReuseEvent(**event) for event in data]
except Exception as e:
print(f"[警告] 加载复用指标失败: {e}")
self._events = []
def _save(self):
"""保存指标数据"""
try:
self.metrics_file.parent.mkdir(parents=True, exist_ok=True)
with open(self.metrics_file, 'w', encoding='utf-8') as f:
data = [asdict(event) for event in self._events]
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"[警告] 保存复用指标失败: {e}")
def record_reuse_offered(
self,
original_task_id: str,
similarity_score: float,
differences_count: int,
critical_differences: int
):
"""记录复用建议被提供"""
event = ReuseEvent(
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
original_task_id=original_task_id,
new_task_id=None,
similarity_score=similarity_score,
user_action='offered',
differences_count=differences_count,
critical_differences=critical_differences,
execution_success=None
)
self._events.append(event)
self._save()
return event
def record_reuse_accepted(
self,
original_task_id: str,
similarity_score: float,
differences_count: int,
critical_differences: int
):
"""记录用户接受复用"""
event = ReuseEvent(
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
original_task_id=original_task_id,
new_task_id=None,
similarity_score=similarity_score,
user_action='accepted',
differences_count=differences_count,
critical_differences=critical_differences,
execution_success=None
)
self._events.append(event)
self._save()
return event
def record_reuse_rejected(
self,
original_task_id: str,
similarity_score: float,
differences_count: int,
critical_differences: int
):
"""记录用户拒绝复用"""
event = ReuseEvent(
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
original_task_id=original_task_id,
new_task_id=None,
similarity_score=similarity_score,
user_action='rejected',
differences_count=differences_count,
critical_differences=critical_differences,
execution_success=None
)
self._events.append(event)
self._save()
return event
def record_reuse_execution(
self,
original_task_id: str,
new_task_id: str,
success: bool
):
"""记录复用后的执行结果"""
# 查找最近的 accepted 事件并更新
for event in reversed(self._events):
if (event.original_task_id == original_task_id and
event.user_action == 'accepted' and
event.new_task_id is None):
event.new_task_id = new_task_id
event.execution_success = success
self._save()
return event
# 如果没找到,创建新记录
event = ReuseEvent(
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
original_task_id=original_task_id,
new_task_id=new_task_id,
similarity_score=0.0,
user_action='executed',
differences_count=0,
critical_differences=0,
execution_success=success
)
self._events.append(event)
self._save()
return event
def record_reuse_rollback(
self,
original_task_id: str,
new_task_id: str
):
"""记录复用后回滚(用户撤销/重做)"""
event = ReuseEvent(
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
original_task_id=original_task_id,
new_task_id=new_task_id,
similarity_score=0.0,
user_action='rollback',
differences_count=0,
critical_differences=0,
execution_success=False
)
self._events.append(event)
self._save()
return event
def get_statistics(self) -> Dict:
"""获取统计数据"""
if not self._events:
return {
'total_offered': 0,
'total_accepted': 0,
'total_rejected': 0,
'total_executed': 0,
'total_rollback': 0,
'acceptance_rate': 0.0,
'rejection_rate': 0.0,
'success_rate': 0.0,
'failure_rate': 0.0,
'rollback_rate': 0.0,
'avg_similarity': 0.0,
'avg_differences': 0.0,
'avg_critical_differences': 0.0
}
offered = [e for e in self._events if e.user_action == 'offered']
accepted = [e for e in self._events if e.user_action == 'accepted']
rejected = [e for e in self._events if e.user_action == 'rejected']
executed = [e for e in self._events if e.execution_success is not None]
rollback = [e for e in self._events if e.user_action == 'rollback']
total_offered = len(offered)
total_accepted = len(accepted)
total_rejected = len(rejected)
total_executed = len(executed)
total_rollback = len(rollback)
# 计算成功和失败
successful = [e for e in executed if e.execution_success]
failed = [e for e in executed if not e.execution_success]
# 计算率
acceptance_rate = total_accepted / total_offered if total_offered > 0 else 0.0
rejection_rate = total_rejected / total_offered if total_offered > 0 else 0.0
success_rate = len(successful) / total_executed if total_executed > 0 else 0.0
failure_rate = len(failed) / total_executed if total_executed > 0 else 0.0
rollback_rate = total_rollback / total_executed if total_executed > 0 else 0.0
# 平均值
all_events = offered + accepted + rejected
avg_similarity = sum(e.similarity_score for e in all_events) / len(all_events) if all_events else 0.0
avg_differences = sum(e.differences_count for e in all_events) / len(all_events) if all_events else 0.0
avg_critical_differences = sum(e.critical_differences for e in all_events) / len(all_events) if all_events else 0.0
return {
'total_offered': total_offered,
'total_accepted': total_accepted,
'total_rejected': total_rejected,
'total_executed': total_executed,
'total_rollback': total_rollback,
'acceptance_rate': acceptance_rate,
'rejection_rate': rejection_rate,
'success_rate': success_rate,
'failure_rate': failure_rate,
'rollback_rate': rollback_rate,
'avg_similarity': avg_similarity,
'avg_differences': avg_differences,
'avg_critical_differences': avg_critical_differences
}
def get_recent_events(self, count: int = 20) -> List[ReuseEvent]:
"""获取最近的事件"""
return self._events[-count:] if self._events else []
# 全局单例
_metrics: Optional[ReuseMetrics] = None
def get_reuse_metrics(workspace_path: Path) -> ReuseMetrics:
"""获取复用指标管理器单例"""
global _metrics
if _metrics is None:
_metrics = ReuseMetrics(workspace_path)
return _metrics