Files
llmgw/internal/guard/metrics.go
T

75 lines
2.2 KiB
Go

package guard
import (
"context"
"errors"
"time"
"github.com/prometheus/client_golang/prometheus"
)
// gateMetrics are what only the gate sees: the judge, and redactions.
type gateMetrics struct {
judgeLatency *prometheus.HistogramVec
judgeErrors *prometheus.CounterVec
failOpen prometheus.Counter
redactions *prometheus.CounterVec
}
func newGateMetrics() *gateMetrics {
return &gateMetrics{
judgeLatency: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: "llmgw_guard_judge_latency_seconds",
Help: "Time a judge model took to answer the guardrails, cached answers excluded.",
Buckets: []float64{.01, .025, .05, .1, .25, .5, 1, 2.5, 5},
}, []string{"judge_model"}),
judgeErrors: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "llmgw_guard_judge_errors_total",
Help: "Judge calls that gave no verdict, by reason: timeout, unavailable, format, no_judge.",
}, []string{"reason"}),
failOpen: prometheus.NewCounter(prometheus.CounterOpts{
Name: "llmgw_guard_fail_open_total",
Help: "Requests and answers served unjudged because a judge did not answer and the policy fails open.",
}),
redactions: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "llmgw_guard_redactions_total",
Help: "Values the guardrails rewrote as placeholders, by kind.",
}, []string{"kind"}),
}
}
// Describe and Collect make the gate a Prometheus collector.
func (g *Gate) Describe(ch chan<- *prometheus.Desc) {
for _, c := range g.collectors() {
c.Describe(ch)
}
}
func (g *Gate) Collect(ch chan<- prometheus.Metric) {
for _, c := range g.collectors() {
c.Collect(ch)
}
}
func (g *Gate) collectors() []prometheus.Collector {
m := g.m
return []prometheus.Collector{m.judgeLatency, m.judgeErrors, m.failOpen, m.redactions}
}
func (m *gateMetrics) judged(model string, took time.Duration, err error) {
if err == nil {
m.judgeLatency.WithLabelValues(model).Observe(took.Seconds())
return
}
reason := "unavailable"
switch {
case errors.Is(err, context.DeadlineExceeded):
reason = "timeout"
case errors.Is(err, errNoJudge):
reason = "no_judge"
case errors.Is(err, errJudgeFormat):
reason = "format"
}
m.judgeErrors.WithLabelValues(reason).Inc()
}