Files
LocalAgent/history/reuse_metrics.py
Mimikko-zeus 8a538bb950 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.
2026-02-27 14:32:30 +08:00

253 lines
8.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
任务复用度量指标收集模块
"""
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