Files
llmgw/internal/router/chat_load_test.go
Ilker ManapandClaude Opus 5.5 5aa5675a9c Show a model loading in the chat, and send once it is up
A message to a model that was not running waited 45 seconds and came
back as "the model is loading; retry shortly". The page now asks for
the model with /chat/wake, shows where it stands from
/chat/model-status - waiting for a node, downloading with bytes and a
percentage, starting the engine - and sends the message when it answers.

Also passes the nodes' llama.cpp build to the pages, which the mirror's
checks for loadable weights needed and were never given.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 20:52:31 +02:00

120 lines
3.9 KiB
Go

package router
import (
"encoding/json"
"net/http/httptest"
"strings"
"testing"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/catalog"
)
// replicaSource is the control plane's view with only replicas in it.
type replicaSource struct {
replicas map[string][]adminapi.ReplicaView
}
func (s replicaSource) Nodes() []adminapi.NodeView { return nil }
func (s replicaSource) Models() []adminapi.ModelView { return nil }
func (s replicaSource) Replicas(model string) []adminapi.ReplicaView {
return s.replicas[model]
}
func (s replicaSource) Explain(string) adminapi.ExplainView { return adminapi.ExplainView{} }
func (s replicaSource) Summary() adminapi.Summary { return adminapi.Summary{} }
func onDemandRouter(t *testing.T) (*Router, *fakeActivator) {
t.Helper()
e := newFakeEngine(t, "qwen2.5-coder-7b-instruct")
rt, _ := chatRouter(t, e.srv.URL)
cpu := []catalog.Profile{{Name: "cpu", Engine: "llamacpp"}}
c, err := catalog.New([]catalog.Model{
{Name: "qwen2.5-coder-7b-instruct", Source: "http://m/a.gguf", Profiles: cpu},
{Name: "nemo", Source: "http://m/b.gguf", Profiles: cpu, Scaling: catalog.Scaling{Min: 0, Max: 1}},
})
if err != nil {
t.Fatal(err)
}
rt.SetCatalog(c)
act := &fakeActivator{onDemand: map[string]bool{"nemo": true}}
rt.SetActivator(act)
waitForEndpoint(t, rt)
return rt, act
}
func TestALoadingModelReportsHowMuchOfItHasArrived(t *testing.T) {
// The chat page draws its bar from this. Two shards of one replica are one
// download as far as the person waiting is concerned.
rt, _ := onDemandRouter(t)
rt.SetClusterView(replicaSource{replicas: map[string][]adminapi.ReplicaView{
"nemo": {{State: "starting", AgeSec: 12, Shards: []adminapi.ShardView{
{Fetch: &adminapi.FetchView{BytesDone: 100, BytesTotal: 400}},
{Fetch: &adminapi.FetchView{BytesDone: 100, BytesTotal: 400}},
}}},
}}, nil)
st := rt.loadStatus("nemo")
if st.State != "downloading" || st.Percent == nil || *st.Percent != 25 {
t.Fatalf("got %+v, expected downloading at 25%%", st)
}
if st.Done != 200 || st.Total != 800 {
t.Errorf("bytes %d of %d, expected 200 of 800", st.Done, st.Total)
}
}
func TestAModelWithNothingPlacedYetIsWaitingNotStopped(t *testing.T) {
// Between the wake and the placement there is no replica at all. Calling
// that "stopped" would end the bar the moment it appeared.
rt, _ := onDemandRouter(t)
rt.SetClusterView(replicaSource{}, nil)
if st := rt.loadStatus("nemo"); st.State != "waiting" {
t.Errorf("got %+v, expected waiting", st)
}
}
func TestARunningModelIsReady(t *testing.T) {
rt, _ := onDemandRouter(t)
if st := rt.loadStatus("qwen2.5-coder-7b-instruct"); st.State != "ready" {
t.Errorf("got %+v, expected ready", st)
}
}
func TestWakingAModelAsksForItAndAnswersAtOnce(t *testing.T) {
rt, act := onDemandRouter(t)
rt.SetClusterView(replicaSource{}, nil)
_, user := chatRouter(t, "http://unused")
user.ProjectID = ""
req := httptest.NewRequest("POST", "/chat/wake", strings.NewReader(`{"model":"nemo"}`))
rec := httptest.NewRecorder()
rt.handleChatWake(rec, req, user)
if rec.Code != 200 {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
var st modelStatus
if err := json.Unmarshal(rec.Body.Bytes(), &st); err != nil {
t.Fatal(err)
}
if st.State != "waiting" {
t.Errorf("got %+v, expected waiting", st)
}
if len(act.asked) != 1 || act.asked[0] != "nemo" {
t.Errorf("the activator was asked for %v", act.asked)
}
}
func TestWakingAnUnknownModelIsNotFound(t *testing.T) {
rt, act := onDemandRouter(t)
_, user := chatRouter(t, "http://unused")
user.ProjectID = ""
req := httptest.NewRequest("POST", "/chat/wake", strings.NewReader(`{"model":"nope"}`))
rec := httptest.NewRecorder()
rt.handleChatWake(rec, req, user)
if rec.Code != 404 {
t.Errorf("status %d, expected 404", rec.Code)
}
if len(act.asked) != 0 {
t.Errorf("an unknown model was asked for: %v", act.asked)
}
}