Skip to content

Commit 09679ad

Browse files
authored
Prevent stale running model evals
1 parent ed3f6e5 commit 09679ad

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

internal/adminapi/adminapi_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,29 @@ func TestModelEvalLoadLegacyAndDetailedReports(t *testing.T) {
539539
}
540540
}
541541

542+
func TestModelEvalNormalizesStaleRunningStatus(t *testing.T) {
543+
s := newTestServer(t)
544+
stale := map[string]modelEvalStatus{
545+
modelCheckKey("openrouter", "stale/model:free"): {
546+
Status: "running",
547+
Provider: "openrouter",
548+
ModelID: "stale/model:free",
549+
StartedAt: time.Now().Add(-2 * time.Hour).Format(time.RFC3339),
550+
UpdatedAt: time.Now().Add(-2 * time.Hour).Format(time.RFC3339),
551+
},
552+
}
553+
data, err := json.Marshal(stale)
554+
if err != nil {
555+
t.Fatal(err)
556+
}
557+
s.settings = &fakeSettingsStore{values: map[string]string{settingKeyModelEvals: string(data)}}
558+
evals := s.loadModelEvals(context.Background())
559+
got := evals[modelCheckKey("openrouter", "stale/model:free")]
560+
if got.Status != "error" || got.Error == "" || got.Failed != 1 {
561+
t.Fatalf("stale running eval not normalized to error: %+v", got)
562+
}
563+
}
564+
542565
func TestWebModelCheckPersistsFreeModelStatus(t *testing.T) {
543566
s, _ := newTGModelTestServer(t)
544567
settings := &fakeSettingsStore{values: map[string]string{}}

internal/adminapi/model_evals.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const (
1717
settingKeyModelEvals = "recommendation.model_evals"
1818
modelEvalDatasetPath = "evals/workload.json"
1919
modelEvalTimeout = 45 * time.Second
20+
modelEvalStaleAfter = modelEvalTimeout + 15*time.Second
2021
)
2122

2223
type modelEvalStatus struct {
@@ -69,16 +70,20 @@ func (s *Server) handleModelEval(w http.ResponseWriter, r *http.Request) {
6970
return
7071
}
7172
defer s.modelEvalMu.Unlock()
72-
if err := s.saveModelEval(r.Context(), provider, modelID, runningModelEval(provider, modelID)); err != nil {
73+
saveCtx, saveCancel := context.WithTimeout(context.Background(), 5*time.Second)
74+
defer saveCancel()
75+
if err := s.saveModelEval(saveCtx, provider, modelID, runningModelEval(provider, modelID)); err != nil {
7376
http.Error(w, err.Error(), http.StatusServiceUnavailable)
7477
return
7578
}
7679

7780
caps := s.lookupCapsFor(r.Context(), provider, modelID)
78-
ctx, cancel := context.WithTimeout(r.Context(), modelEvalTimeout)
81+
ctx, cancel := context.WithTimeout(context.Background(), modelEvalTimeout)
7982
defer cancel()
8083
status := s.runModelEval(ctx, provider, modelID, caps)
81-
if err := s.saveModelEval(ctx, provider, modelID, status); err != nil {
84+
saveCtx, saveCancel = context.WithTimeout(context.Background(), 5*time.Second)
85+
defer saveCancel()
86+
if err := s.saveModelEval(saveCtx, provider, modelID, status); err != nil {
8287
http.Error(w, err.Error(), http.StatusServiceUnavailable)
8388
return
8489
}
@@ -258,9 +263,30 @@ func normalizeSingleModelEval(provider, modelID string, status modelEvalStatus)
258263
if status.CheckedAt == "" && status.FinishedAt != "" {
259264
status.CheckedAt = status.FinishedAt
260265
}
266+
if status.Status == "running" && isStaleModelEval(status, time.Now()) {
267+
now := time.Now().Format(time.RFC3339)
268+
status.Status = "error"
269+
status.FinishedAt = now
270+
status.UpdatedAt = now
271+
status.CheckedAt = now
272+
status.Failed = 1
273+
status.Error = "model eval timed out before completion"
274+
status.Failures = []string{status.Error}
275+
}
261276
return status
262277
}
263278

279+
func isStaleModelEval(status modelEvalStatus, now time.Time) bool {
280+
if status.StartedAt == "" {
281+
return false
282+
}
283+
started, err := time.Parse(time.RFC3339, status.StartedAt)
284+
if err != nil {
285+
return false
286+
}
287+
return now.Sub(started) > modelEvalStaleAfter
288+
}
289+
264290
type modelOpsData struct {
265291
ActiveTab string
266292
Checks []modelCheckStatus

0 commit comments

Comments
 (0)