cx-bot / eval /auto_eval.py
rnyx's picture
Initial commit: domain-aware multi-agent CX bot
dcfd487
Raw
History Blame Contribute Delete
3.61 kB
"""
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)