package llm import ( "context" "encoding/json" "fmt" "net/http" "strings" "time" ) // UnknownModel is the label to print when the server would not say what it has // loaded. Deliberately ugly: an honest "unknown" is fine, a plausible-looking // but wrong model name is the bug this whole file exists to prevent. const UnknownModel = "unknown-model" // llama-server is local, so never send this through a proxy: this box's // http_proxy answers 503 for loopback, which would look like "server won't say // which model it has" when the server is right there and fine. // A Transport with no Proxy set bypasses http_proxy entirely. var modelHTTP = &http.Client{Timeout: 10 * time.Second, Transport: &http.Transport{}} // ModelID asks llama-server which model it has loaded, so a scoring run can // label itself. Without this a bake-off between two models produces two tables // that look identical, and the operator has to remember which server was up. // // Read from the server rather than passed in on purpose: a hand-typed label // goes stale the moment someone restarts the server with a different -m. func ModelID(ctx context.Context, base string) (string, error) { req, err := http.NewRequestWithContext(ctx, "GET", strings.TrimSuffix(base, "/")+"/v1/models", nil) if err != nil { return "", err } resp, err := modelHTTP.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != 200 { return "", fmt.Errorf("models: status %d", resp.StatusCode) } var out struct { Data []struct { ID string `json:"id"` } `json:"data"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return "", err } if len(out.Data) == 0 { return "", fmt.Errorf("models: empty list") } short := shortModelID(out.Data[0].ID) if short == "" { // Server answered but the id field was missing or blank. Say so // instead of handing back an empty label that reads as a real name. return "", fmt.Errorf("models: no id in response") } return short, nil } // shortModelID trims the path and the .gguf suffix — llama-server reports the // file name it was started with, which is too long for a table header. func shortModelID(id string) string { id = strings.TrimSpace(id) if i := strings.LastIndexAny(id, "/\\"); i >= 0 { id = id[i+1:] } return strings.TrimSpace(strings.TrimSuffix(id, ".gguf")) }