| """ |
| Nightly Auto-Evaluator: samples audit logs, re-runs queries, computes metrics. |
| |
| Fixes: |
| - Removed unused imports: time, CONFIDENCE_THRESHOLD |
| """ |
| import json |
| import random |
| import logging |
| import argparse |
| import os |
| from datetime import datetime, timezone, timedelta |
| from typing import List, Dict |
|
|
| from audit.logger import read_audit_logs |
| from config import AUDIT_LOG_DIR |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| def _load_samples(n: int, days_back: int = 7) -> List[Dict]: |
| records = [] |
| for i in range(days_back): |
| date = (datetime.now(timezone.utc) - timedelta(days=i)).strftime("%Y-%m-%d") |
| records.extend(read_audit_logs(date_str=date, limit=500)) |
| random.shuffle(records) |
| return records[:n] |
|
|
|
|
| def compute_metrics(records: List[Dict]) -> Dict: |
| if not records: |
| return {} |
|
|
| total = len(records) |
| cache_hits = sum(1 for r in records if r.get("cache_hit")) |
| low_conf = sum(1 for r in records if r.get("low_confidence_flag")) |
| latencies = [r.get("response_latency_ms", 0) for r in records] |
| confidences = [r.get("final_confidence", 0) for r in records] |
|
|
| sorted_lat = sorted(latencies) |
| p95_idx = max(0, int(0.95 * len(sorted_lat)) - 1) |
|
|
| routing_counts: Dict[str, int] = {} |
| for r in records: |
| agent = r.get("routed_to", "unknown") |
| routing_counts[agent] = routing_counts.get(agent, 0) + 1 |
|
|
| return { |
| "total_queries": total, |
| "cache_hit_rate": round(cache_hits / total, 4), |
| "low_confidence_rate": round(low_conf / total, 4), |
| "avg_latency_ms": round(sum(latencies) / total, 1) if latencies else 0, |
| "p95_latency_ms": sorted_lat[p95_idx] if sorted_lat else 0, |
| "avg_confidence": round(sum(confidences) / total, 4) if confidences else 0, |
| "routing_distribution": routing_counts, |
| "evaluated_at": datetime.now(timezone.utc).isoformat(), |
| } |
|
|
|
|
| def run_benchmark(n: int = 50) -> None: |
| logger.info(f"Running benchmark on {n} samples...") |
| records = _load_samples(n) |
| if not records: |
| logger.warning("No audit records found. Run some queries first.") |
| return |
|
|
| metrics = compute_metrics(records) |
|
|
| targets = { |
| "cache_hit_rate": (0.40, "≥ 40%", False), |
| "low_confidence_rate": (0.15, "≤ 15%", True), |
| "avg_latency_ms": (1500, "< 1500ms", True), |
| "p95_latency_ms": (3500, "< 3500ms", True), |
| "avg_confidence": (0.80, "≥ 0.80", False), |
| } |
|
|
| print("\n" + "=" * 60) |
| print("CX BOT BENCHMARK RESULTS") |
| print("=" * 60) |
| for key, value in metrics.items(): |
| if key in targets: |
| target_val, label, lower_is_better = targets[key] |
| status = "✓ PASS" if (value <= target_val if lower_is_better else value >= target_val) else "✗ FAIL" |
| print(f" {key:<28} {str(value):<12} Target: {label:<12} {status}") |
| else: |
| print(f" {key:<28} {value}") |
| print("=" * 60 + "\n") |
|
|
| os.makedirs(AUDIT_LOG_DIR, exist_ok=True) |
| report_path = os.path.join( |
| AUDIT_LOG_DIR, |
| f"benchmark_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.json" |
| ) |
| with open(report_path, "w") as f: |
| json.dump(metrics, f, indent=2) |
| logger.info(f"Benchmark report saved to {report_path}") |
|
|
|
|
| if __name__ == "__main__": |
| logging.basicConfig(level=logging.INFO) |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--mode", choices=["benchmark"], default="benchmark") |
| parser.add_argument("--samples", type=int, default=50) |
| args = parser.parse_args() |
| run_benchmark(args.samples) |
|
|