Files
artex/db/llmhealth.go
T
dela 0335d572de
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
First Commit
2026-10-09 08:38:16 +08:00

58 lines
2.2 KiB
Go

package db
import "time"
// LLM failover health: one row per profile tracking its circuit-breaker state.
// The authoritative copy lives in memory (llmpool.Registry); this table only
// survives a restart so a cooling-off window isn't silently reset by one.
// LLMHealth is one profile's circuit-breaker state.
type LLMHealth struct {
ProfileID int64 `json:"profile_id"`
Fails int `json:"fails"` // consecutive failures; cleared on success
Trips int `json:"trips"` // total trips, drives the backoff ladder
OpenUntil *time.Time `json:"open_until"` // nil/past = closed (healthy)
LastError string `json:"last_error"`
LastAt time.Time `json:"last_at"`
}
// LoadLLMHealth returns the profiles still in an UNEXPIRED cooling-off window.
// Expired rows are deliberately skipped: after a restart a profile that has
// finished cooling should be treated as healthy again and re-probed on its next
// call, not resurrected as broken.
func (d *DB) LoadLLMHealth() ([]LLMHealth, error) {
rows, err := d.Query(`SELECT profile_id,fails,trips,open_until,COALESCE(last_error,''),last_at
FROM llm_profile_health WHERE open_until IS NOT NULL AND open_until > now()`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []LLMHealth
for rows.Next() {
var h LLMHealth
if err := rows.Scan(&h.ProfileID, &h.Fails, &h.Trips, &h.OpenUntil, &h.LastError, &h.LastAt); err != nil {
return nil, err
}
out = append(out, h)
}
return out, rows.Err()
}
// SaveLLMHealth upserts one profile's circuit-breaker state.
func (d *DB) SaveLLMHealth(h LLMHealth) error {
_, err := d.Exec(`
INSERT INTO llm_profile_health(profile_id,fails,trips,open_until,last_error,last_at)
VALUES ($1,$2,$3,$4,$5,now())
ON CONFLICT (profile_id) DO UPDATE SET
fails=EXCLUDED.fails, trips=EXCLUDED.trips, open_until=EXCLUDED.open_until,
last_error=EXCLUDED.last_error, last_at=now()`,
h.ProfileID, h.Fails, h.Trips, h.OpenUntil, h.LastError)
return err
}
// ClearLLMHealth drops one profile's state (manual "recover now" from the UI).
func (d *DB) ClearLLMHealth(profileID int64) error {
_, err := d.Exec(`DELETE FROM llm_profile_health WHERE profile_id=$1`, profileID)
return err
}