"""
Audit-Logging für LLM-Enrichment.
Trackt welches Modell wann verwendet wurde, Erfolg/Fehler, Kosten.
"""
import logging
from datetime import datetime
from typing import Optional
class EnrichmentLogger:
"""Speichert Enrichment-Attempts in strukturierter Form."""
def __init__(self, log_name: str = 'enrichment'):
self.logger = logging.getLogger(log_name)
self.stats = {}
def log_attempt(self,
contact_id: int,
model_used: str,
level: int,
success: bool,
validation_result: str,
tokens_used: Optional[int] = None,
error_msg: Optional[str] = None,
estimated_fields: Optional[list] = None):
"""
Loggt einen Enrichment-Versuch.
Args:
contact_id: Kontakt-ID
model_used: z.B. "gpt-5-nano"
level: 1 (billig), 2 (mittel), 3 (stark)
success: True/False
validation_result: z.B. "OK" oder "MISSING_CRITICAL: pain_point"
tokens_used: Input+Output Tokens (falls verfügbar)
error_msg: Optionale Error-Message
"""
timestamp = datetime.now().isoformat()
estimated_fields = estimated_fields or []
log_entry = {
'timestamp': timestamp,
'contact_id': contact_id,
'model': model_used,
'level': level,
'success': success,
'validation': validation_result,
'tokens': tokens_used or 0,
'error': error_msg or '',
'estimated_fields_count': len(estimated_fields),
'estimated_fields_list': estimated_fields,
}
# Logging
status = "✓ OK" if success else f"✗ FAIL({validation_result})"
est_info = f" est={len(estimated_fields)}" if estimated_fields else ""
self.logger.info(
f"[{contact_id}] {model_used} (L{level}): {status} "
f"tokens={tokens_used or '?'}{est_info}"
)
# Stats sammeln
model_key = model_used
if model_key not in self.stats:
self.stats[model_key] = {
'attempts': 0,
'successes': 0,
'failures': 0,
'tokens_total': 0
}
self.stats[model_key]['attempts'] += 1
if success:
self.stats[model_key]['successes'] += 1
else:
self.stats[model_key]['failures'] += 1
if tokens_used:
self.stats[model_key]['tokens_total'] += tokens_used
return log_entry
def log_contact_budget(self,
contact_id: int,
budget_used: int,
max_budget: int,
tokens_per_level: dict,
exceeded: bool):
"""
Loggt Token-Budget-Zusammenfassung pro Kontakt.
Args:
contact_id: Kontakt-ID
budget_used: Gesamte verbrauchte Tokens
max_budget: Maximales Budget
tokens_per_level: Dict {"L1": 1200, "L2": 800, ...}
exceeded: True wenn Budget überschritten wurde
"""
pct = (budget_used / max_budget * 100) if max_budget > 0 else 0
status = "EXCEEDED" if exceeded else "OK"
level_str = ', '.join(f"{k}={v}" for k, v in tokens_per_level.items())
self.logger.info(
f"[{contact_id}] Budget {status}: {budget_used}/{max_budget} "
f"({pct:.1f}%) | per Level: {level_str or 'keine'}"
)
# Globale Budget-Statistik
if 'budget' not in self.stats:
self.stats['budget'] = {
'total_tokens': 0,
'total_contacts': 0,
'exceeded_count': 0,
}
self.stats['budget']['total_tokens'] += budget_used
self.stats['budget']['total_contacts'] += 1
if exceeded:
self.stats['budget']['exceeded_count'] += 1
def get_success_rate(self, model: str) -> Optional[float]:
"""Berechnet Success-Rate für ein Modell (z.B. nach 50 Requests)."""
if model not in self.stats:
return None
if self.stats[model]['attempts'] == 0:
return None
return self.stats[model]['successes'] / self.stats[model]['attempts']
def print_stats(self):
"""Gibt Statistiken aus."""
print("\n=== Enrichment Statistics ===")
for model, stats in self.stats.items():
if stats['attempts'] > 0:
rate = (stats['successes'] / stats['attempts']) * 100
print(f" {model}: {stats['successes']}/{stats['attempts']} "
f"({rate:.1f}%) | {stats['tokens_total']} tokens")
# Global Logger
enrichment_logger = EnrichmentLogger('enrichment')