Reported after watching a model appear in ctl models before it could answer anything. Checked: clients are not told early — /v1/models listed it only once a replica was ready, and the chat picker reads the same source. The operator view showing it is deliberate, because a model coming up is exactly what someone running the cluster wants to see. But the report pointed at something real. That view distinguished "a replica is coming up" from "nothing could be placed" by a parenthetical — "0/1 ready (1 total)" against "0/1 ready" — and those need opposite responses: one means wait, the other means look at explain-placement. On four models the difference is findable. On forty it is invisible, and a model that will never start reads exactly like one that is thirty seconds away. SERVING now answers the question that was being asked of the replica counts — can a client use this right now — and STATUS says which of ready, starting, not placed or stopped is the case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
331de9b495
commit
d07dddb1bb
2 files changed
+60
-6
No files matched your search
+34
-6
@@ -172,6 +172,33 @@ func newNodesCmd(client func() *ctlClient, asJSON *bool) *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
// modelStatus says what is happening to a model, in the words that decide what
|
||||
// to do about it.
|
||||
//
|
||||
// The previous rendering distinguished "a replica is coming up" from "nothing
|
||||
// could be placed" by a parenthetical — "0/1 ready (1 total)" against "0/1
|
||||
// ready" — and those need opposite responses: one means wait, the other means
|
||||
// look at explain-placement. On four models the difference is findable; on
|
||||
// forty it is invisible, and a model that will never start looks exactly like
|
||||
// one that is thirty seconds away.
|
||||
//
|
||||
// A model appearing here before it serves is deliberate: this is the operator's
|
||||
// view, and a model coming up is precisely what someone wants to see. Clients
|
||||
// are told separately and only when it is true — /v1/models lists what is
|
||||
// actually served, never what is merely catalogued.
|
||||
func modelStatus(m adminapi.ModelView) string {
|
||||
switch {
|
||||
case m.Min == 0 && m.Total == 0:
|
||||
return "stopped"
|
||||
case m.Ready >= m.Min && m.Min > 0:
|
||||
return "ready"
|
||||
case m.Total > m.Ready:
|
||||
return "starting"
|
||||
default:
|
||||
return "not placed"
|
||||
}
|
||||
}
|
||||
|
||||
// idleColumn shows how long a replica has gone unused.
|
||||
//
|
||||
// "-" for a replica nothing has reported use of, which is emphatically not the
|
||||
@@ -212,14 +239,15 @@ func newModelsCmd(client func() *ctlClient, asJSON *bool) *cobra.Command {
|
||||
}
|
||||
|
||||
w := tab()
|
||||
fmt.Fprintln(w, "MODEL\tREPLICAS\tALIASES\tPROFILES\tFALLBACKS")
|
||||
fmt.Fprintln(w, "MODEL\tSERVING\tREPLICAS\tSTATUS\tALIASES\tPROFILES\tFALLBACKS")
|
||||
for _, m := range models {
|
||||
replicas := fmt.Sprintf("%d/%d ready", m.Ready, m.Min)
|
||||
if m.Total > m.Ready {
|
||||
replicas += fmt.Sprintf(" (%d total)", m.Total)
|
||||
serving := "no"
|
||||
if m.Ready > 0 {
|
||||
serving = "yes"
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
|
||||
m.Name, replicas, join(m.Aliases), join(m.Profiles), join(m.Fallbacks))
|
||||
fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\t%s\t%s\t%s\n",
|
||||
m.Name, serving, m.Ready, m.Min, modelStatus(m),
|
||||
join(m.Aliases), join(m.Profiles), join(m.Fallbacks))
|
||||
}
|
||||
return w.Flush()
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@ package cli
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
||||
)
|
||||
|
||||
func TestBarStaysWithinItsWidth(t *testing.T) {
|
||||
@@ -33,3 +35,27 @@ func TestIdleColumnDistinguishesUnknownFromIdle(t *testing.T) {
|
||||
t.Errorf("idleColumn(90) = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelStatusSeparatesWaitingFromBroken(t *testing.T) {
|
||||
// These used to differ by a parenthetical — "0/1 ready (1 total)" against
|
||||
// "0/1 ready" — and they need opposite responses: one means wait, the other
|
||||
// means look at explain-placement. On forty models that difference is
|
||||
// invisible.
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
view adminapi.ModelView
|
||||
want string
|
||||
}{
|
||||
{"serving what it asked for", adminapi.ModelView{Min: 2, Ready: 2, Total: 2}, "ready"},
|
||||
{"a replica is coming up", adminapi.ModelView{Min: 1, Ready: 0, Total: 1}, "starting"},
|
||||
{"nothing could be placed", adminapi.ModelView{Min: 1, Ready: 0, Total: 0}, "not placed"},
|
||||
{"deliberately not running", adminapi.ModelView{Min: 0, Ready: 0, Total: 0}, "stopped"},
|
||||
{"short but rebuilding", adminapi.ModelView{Min: 2, Ready: 1, Total: 2}, "starting"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := modelStatus(tc.view); got != tc.want {
|
||||
t.Errorf("modelStatus(%+v) = %q, want %q", tc.view, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user