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
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m30s
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user