Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/adminapi/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ func buildModelSections(models []uiModel, presetMode bool) []uiModelSection {
order := []uiModelSection{
{Key: "recommended", Title: "Recommended", Description: "Stable, role-fit models backed by benchmark data."},
{Key: "interesting", Title: "Interesting", Description: "Plausible alternatives and non-obvious trade-offs worth reviewing."},
{Key: "watchlist", Title: "Needs review", Description: "Capability-compatible models outside the proven family allowlist. Inspect or test before allowing."},
{Key: "untested", Title: "Untested", Description: "Capability-compatible models with missing quality data or local evidence."},
{Key: "blocked", Title: "Blocked", Description: "Filtered or manually denied models, shown only when present."},
}
Expand Down Expand Up @@ -732,6 +733,8 @@ func modelSectionKey(m uiModel) string {
return "recommended"
case m.Source == "untested" || m.Policy == "free_unverified":
return "untested"
case m.Source == "watchlist" || m.Section == "watchlist":
return "watchlist"
case m.Source == "near_frontier" || m.Source == "manual" || m.Policy == "manual_allow":
return "interesting"
default:
Expand Down
4 changes: 4 additions & 0 deletions internal/adminapi/model_display.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ func modelStatusLabel(m uiModel, current bool) string {
return "Recommended"
case m.Section == "untested" || m.Source == "untested" || m.Policy == "free_unverified":
return "Untested"
case m.Section == "watchlist" || m.Source == "watchlist":
return "Needs review"
case m.Section == "interesting" || m.Source == "near_frontier" || m.Source == "manual" || m.Policy == "manual_allow":
return "Interesting"
}
Expand Down Expand Up @@ -99,6 +101,8 @@ func modelPrimaryReason(m uiModel, role, status string) string {
return "Near frontier for " + role
}
return "Near recommendation frontier"
case m.Source == "watchlist":
return "Outside proven model family allowlist"
case status == "Untested":
return "Compatible but missing role evidence"
}
Expand Down
110 changes: 109 additions & 1 deletion internal/adminapi/recommend.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,113 @@ func appendUntestedAlternatives(models, candidates []uiModel, axes paretoAxes, r
return append(models, untested...)
}

func appendWatchlistCandidates(models []uiModel, all map[string]llm.Capabilities, aaModels map[string]llm.AAModelInfo, role string, visionFallbackPrompt float64, limit int) []uiModel {
if limit <= 0 {
return models
}
seen := make(map[string]bool, len(models))
for _, m := range models {
seen[m.ID] = true
}
candidates := make([]uiModel, 0, limit)
for id, c := range all {
if seen[id] || !watchlistEligible(c, id, role) {
continue
}
m := uiModel{
ID: id,
Name: c.Name,
Description: c.Description,
PromptPrice: c.PromptPrice,
CompletionPrice: c.CompletionPrice,
ContextLength: c.ContextLength,
Vision: c.Vision,
Tools: c.Tools,
Reasoning: c.Reasoning,
Free: c.Free(),
Score: c.Score,
Section: "watchlist",
}
if aaModels != nil {
if info := llm.LookupAAInfo(id, aaModels); info != nil {
enrichFromAA(&m, *info)
}
}
if usesVisionFallback(role) && visionFallbackPrompt > 0 && !c.Vision {
m.EffectivePrompt = (1-imageShare)*c.PromptPrice + imageShare*visionFallbackPrompt
}
axes := watchlistAxes(role)
if axes != nil {
if q, p := axes(m); q > 0 && p > 0 {
m.ValuePerDollar = q / p
}
}
annotateModelForRole(&m, role, "watchlist")
m.Reasons = uniqueStrings(append(m.Reasons, "watchlist candidate", "outside multilingual allowlist"))
candidates = append(candidates, m)
}
sort.Slice(candidates, func(i, j int) bool {
axes := watchlistAxes(role)
if axes != nil {
qi, pi := axes(candidates[i])
qj, pj := axes(candidates[j])
if qi != qj {
return qi > qj
}
if pi != pj {
return pi < pj
}
}
if candidates[i].ContextLength != candidates[j].ContextLength {
return candidates[i].ContextLength > candidates[j].ContextLength
}
return candidates[i].ID < candidates[j].ID
})
if len(candidates) > limit {
candidates = candidates[:limit]
}
return append(models, candidates...)
}

func watchlistEligible(c llm.Capabilities, id, role string) bool {
if c.Free() || multilingualRegex.MatchString(id) || excludedVendorsRegex.MatchString(id) || isUnstableVariant(id) {
return false
}
if specialisedCoderRegex.MatchString(id) {
return false
}
if role != "multimodal" && specialisedVisionRegex.MatchString(id) {
return false
}
switch role {
case "simple":
return c.Tools && c.ContextLength >= 32000 && c.PromptPrice > 0 && c.PromptPrice <= 0.5
case "default":
return c.Tools && !lightweightDefaultRegex.MatchString(id) && c.ContextLength >= 32000 && c.PromptPrice > 0 && c.PromptPrice <= 4.0
case "complex":
return c.Tools && c.Reasoning && c.ContextLength >= 64000 && c.PromptPrice > 0 && c.PromptPrice <= 10.0
case "multimodal":
return c.Tools && c.Vision && c.ContextLength >= 32000 && c.PromptPrice > 0 && c.PromptPrice <= 4.0
case "compaction":
return c.ContextLength >= 64000 && c.CompletionPrice > 0 && c.CompletionPrice <= 4.0
case "classifier":
return c.ContextLength >= 16000 && c.PromptPrice > 0 && c.PromptPrice <= 0.25
default:
return false
}
}

func watchlistAxes(role string) paretoAxes {
switch role {
case "compaction":
return func(m uiModel) (float64, float64) { return roleQuality(m, role), m.CompletionPrice }
case "classifier", "simple", "default", "complex", "multimodal":
return func(m uiModel) (float64, float64) { return roleQuality(m, role), effectiveBlendedPriceOf(m) }
Comment on lines +535 to +536

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use prompt price for classifier watchlist ranking

For the classifier preset, the existing axes rank by prompt price because the classifier emits only a tiny completion, but watchlist candidates now use blended input/output cost. When an outside-allowlist classifier has cheap input tokens but expensive output tokens, it can be ranked and labeled as worse value than a model with higher prompt cost, even though the route's actual cost is prompt-dominated and the regular classifier preset would prefer it.

Useful? React with 👍 / 👎.

default:
return nil
}
}

// applyPreset returns the Pareto-optimal models for the role, sorted by
// quality descending (best first). If the role has no preset, returns nil.
// Each returned model has ValuePerDollar populated using the role's axes.
Expand Down Expand Up @@ -501,7 +608,8 @@ func applyPreset(all map[string]llm.Capabilities, aaModels map[string]llm.AAMode
frontier[i].Section = "recommended"
annotateModelForRole(&frontier[i], role, "preset")
}
return appendNearFrontierAlternatives(frontier, candidates, axes, role, 4)
out := appendNearFrontierAlternatives(frontier, candidates, axes, role, 4)
return appendWatchlistCandidates(out, all, aaModels, role, visionFallbackPrompt, 4)
}

func annotateModelForRole(m *uiModel, role, source string) {
Expand Down
78 changes: 75 additions & 3 deletions internal/adminapi/recommend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,74 @@ func TestPresetKeepsUntestedCandidatesBelowRecommendations(t *testing.T) {
}
}

func TestDefaultPresetAddsWatchlistForUnknownModelFamilies(t *testing.T) {
caps := map[string]llm.Capabilities{
"deepseek/deepseek-v3.2": {
Tools: true,
PromptPrice: 0.25,
CompletionPrice: 1.00,
ContextLength: 128000,
Score: 40,
},
"minimax/minimax-m2.5": {
Tools: true,
PromptPrice: 0.20,
CompletionPrice: 1.10,
ContextLength: 196000,
Score: 42,
},
}

got := applyPreset(caps, nil, "default", 0)
if !containsModel(got, "minimax/minimax-m2.5") {
t.Fatalf("unknown family should be visible as watchlist candidate: %+v", got)
}
for _, model := range got {
if model.ID != "minimax/minimax-m2.5" {
continue
}
if model.Recommended || model.Source != "watchlist" || model.Section != "watchlist" || model.Policy != "candidate" {
t.Fatalf("unknown family should be watchlist-only, got: %+v", model)
}
if !containsString(model.Reasons, "outside multilingual allowlist") {
t.Fatalf("watchlist reason missing: %+v", model.Reasons)
}
return
}
}

func TestWatchlistDoesNotIncludeFreeOrUnstableModels(t *testing.T) {
caps := map[string]llm.Capabilities{
"deepseek/deepseek-v3.2": {
Tools: true,
PromptPrice: 0.25,
CompletionPrice: 1.00,
ContextLength: 128000,
Score: 40,
},
"minimax/minimax-m2.5:free": {
Tools: true,
ContextLength: 196000,
Score: 42,
},
"minimax/minimax-m2.5-preview": {
Tools: true,
PromptPrice: 0.20,
CompletionPrice: 1.10,
ContextLength: 196000,
Score: 42,
},
}

got := applyPreset(caps, nil, "default", 0)
if containsModel(got, "minimax/minimax-m2.5:free") {
t.Fatalf("free variants should not enter watchlist by default: %+v", got)
}
if containsModel(got, "minimax/minimax-m2.5-preview") {
t.Fatalf("unstable variants should not enter watchlist: %+v", got)
}
}

func TestPresetUsesAAScoreWhenCapabilityScoreIsEmpty(t *testing.T) {
caps := map[string]llm.Capabilities{
"x-ai/grok-4.3": {
Expand Down Expand Up @@ -208,20 +276,24 @@ func TestModelSectionsSplitRecommendationBuckets(t *testing.T) {
models := []uiModel{
{ID: "deepseek/deepseek-v3.2", Recommended: true, Section: "recommended", Policy: "recommended"},
{ID: "x-ai/grok-4.3", Source: "near_frontier", Section: "interesting", Policy: "candidate"},
{ID: "minimax/minimax-m2.5", Source: "watchlist", Section: "watchlist", Policy: "candidate"},
{ID: "qwen/qwen-plus", Source: "untested", Section: "untested", Policy: "candidate"},
}

sections := buildModelSections(models, true)
if len(sections) != 3 {
t.Fatalf("sections len = %d, want 3: %+v", len(sections), sections)
if len(sections) != 4 {
t.Fatalf("sections len = %d, want 4: %+v", len(sections), sections)
}
if sections[0].Key != "recommended" || sections[0].Models[0].ID != "deepseek/deepseek-v3.2" {
t.Fatalf("unexpected recommended section: %+v", sections)
}
if sections[1].Key != "interesting" || sections[1].Models[0].ID != "x-ai/grok-4.3" {
t.Fatalf("unexpected interesting section: %+v", sections)
}
if sections[2].Key != "untested" || sections[2].Models[0].ID != "qwen/qwen-plus" {
if sections[2].Key != "watchlist" || sections[2].Models[0].ID != "minimax/minimax-m2.5" {
t.Fatalf("unexpected watchlist section: %+v", sections)
}
if sections[3].Key != "untested" || sections[3].Models[0].ID != "qwen/qwen-plus" {
t.Fatalf("unexpected untested section: %+v", sections)
}
}
Expand Down
Loading