feat(metrics): C3 — Prometheus counters for L0/L1/format-loss/retry
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m30s

This commit is contained in:
2026-07-14 16:53:16 +02:00
parent 13d2f83081
commit 8d0fc818ef
4 changed files with 574 additions and 0 deletions

View File

@@ -42,6 +42,50 @@ file_size_bytes = Histogram(
buckets=(100_000, 500_000, 1_000_000, 5_000_000, 10_000_000, 25_000_000, 50_000_000),
)
# ---- Quality pipeline metrics (L0 + L1) ----
quality_l0_checks_total = Counter(
"quality_l0_checks_total",
"Total L0 (script+length+pattern) quality checks",
["result", "file_type"], # result: pass | fail | error
)
quality_l1_judge_total = Counter(
"quality_l1_judge_total",
"Total L1 (LLM judge) verdicts",
["verdict", "model"], # verdict: pass | fail | skip | error
)
quality_l1_judge_duration_seconds = Histogram(
"quality_l1_judge_duration_seconds",
"L1 LLM judge call duration in seconds",
["model"],
buckets=(0.1, 0.25, 0.5, 1, 2, 5, 10, 30),
)
quality_l1_judge_cost_usd = Histogram(
"quality_l1_judge_cost_usd",
"L1 LLM judge estimated cost in USD",
["model"],
buckets=(0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1),
)
# ---- Retry metrics ----
translation_retry_total = Counter(
"translation_retry_total",
"Total translation retries triggered by quality issues",
["reason", "tier"], # reason: l0_fail | l1_fail | format_loss
)
# ---- Format preservation metrics ----
format_elements_lost_total = Counter(
"format_elements_lost_total",
"Format elements (hyperlinks, footnotes, charts, ...) lost during translation",
["format", "element_type"], # format: docx | xlsx | pptx | pdf
)
# Paths to skip from metrics (noisy health checks)
_SKIP_PATHS = {"/health", "/ready", "/metrics", "/favicon.ico"}
@@ -55,6 +99,65 @@ def record_file_size(file_type: str, size_bytes: int):
file_size_bytes.labels(file_type=file_type).observe(size_bytes)
# ---- Quality helpers ----
def record_l0_result(passed: bool, file_type: str = "unknown"):
"""Record an L0 quality check outcome.
Args:
passed: True if the L0 check passed.
file_type: docx | xlsx | pptx | pdf | unknown
"""
result = "pass" if passed else "fail"
quality_l0_checks_total.labels(result=result, file_type=file_type).inc()
def record_l0_error(file_type: str = "unknown"):
"""Record an L0 internal error (so we can spot broken checks)."""
quality_l0_checks_total.labels(result="error", file_type=file_type).inc()
def record_l1_verdict(
verdict: str,
model: str = "unknown",
duration_seconds: float = None,
cost_usd: float = None,
):
"""Record an L1 judge verdict.
Args:
verdict: pass | fail | skip | error
model: model name (e.g. deepseek-chat)
duration_seconds: optional, observed in histogram
cost_usd: optional, observed in histogram
"""
quality_l1_judge_total.labels(verdict=verdict, model=model).inc()
if duration_seconds is not None:
quality_l1_judge_duration_seconds.labels(model=model).observe(duration_seconds)
if cost_usd is not None:
quality_l1_judge_cost_usd.labels(model=model).observe(cost_usd)
def record_translation_retry(reason: str, tier: str = "free"):
"""Record a translation retry triggered by a quality issue.
Args:
reason: l0_fail | l1_fail | format_loss | user_request
tier: free | pro | enterprise
"""
translation_retry_total.labels(reason=reason, tier=tier).inc()
def record_format_loss(format: str, element_type: str):
"""Record a format element that was lost during translation.
Args:
format: docx | xlsx | pptx | pdf
element_type: hyperlink | footnote | chart | diagram | comment | image
"""
format_elements_lost_total.labels(format=format, element_type=element_type).inc()
class PrometheusMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
if request.url.path in _SKIP_PATHS: