Conflicts: docs.go (manual, OpenAPI and suites all kept), modelserve Job (trace and eval both kept). The eval routes are added to the OpenAPI route table, update and promote documented with their optional body, and the document regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
93 lines
2.6 KiB
Go
93 lines
2.6 KiB
Go
package adminapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/catalog"
|
|
)
|
|
|
|
// CanaryPercentRequest changes the share of a model's requests its canary
|
|
// takes.
|
|
type CanaryPercentRequest struct {
|
|
Percent int `json:"percent"`
|
|
}
|
|
|
|
func (a *API) registerCanary(mux Mux) {
|
|
need := func(w http.ResponseWriter) bool {
|
|
if a.serve == nil {
|
|
writeError(w, http.StatusNotImplemented, "a canary needs a mirror and a store, and this control plane lacks one")
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
mux.HandleFunc("GET /api/v1/canaries", func(w http.ResponseWriter, r *http.Request) {
|
|
if !need(w) {
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, a.serve.Canaries())
|
|
})
|
|
// Start one: the newer weights fetched and run beside, then given
|
|
// their share.
|
|
mux.HandleFunc("POST /api/v1/models/{name}/canary", func(w http.ResponseWriter, r *http.Request) {
|
|
if !need(w) {
|
|
return
|
|
}
|
|
var cn catalog.Canary
|
|
if err := json.NewDecoder(io.LimitReader(r.Body, 1<<12)).Decode(&cn); err != nil {
|
|
writeError(w, http.StatusBadRequest, "the request is not valid JSON: "+err.Error())
|
|
return
|
|
}
|
|
job, err := a.serve.StartCanary(r.PathValue("name"), cn, actorOf(r))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusAccepted, job)
|
|
})
|
|
// Change its share.
|
|
mux.HandleFunc("PUT /api/v1/models/{name}/canary", func(w http.ResponseWriter, r *http.Request) {
|
|
if !need(w) {
|
|
return
|
|
}
|
|
var req CanaryPercentRequest
|
|
if err := json.NewDecoder(io.LimitReader(r.Body, 1<<12)).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "the request is not valid JSON: "+err.Error())
|
|
return
|
|
}
|
|
msg, err := a.serve.SetCanaryPercent(r.Context(), r.PathValue("name"), req.Percent, actorOf(r))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, Result{Result: msg})
|
|
})
|
|
mux.HandleFunc("DELETE /api/v1/models/{name}/canary", func(w http.ResponseWriter, r *http.Request) {
|
|
if !need(w) {
|
|
return
|
|
}
|
|
msg, err := a.serve.AbortCanary(r.Context(), r.PathValue("name"), actorOf(r))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, Result{Result: msg})
|
|
})
|
|
mux.HandleFunc("POST /api/v1/models/{name}/promote", func(w http.ResponseWriter, r *http.Request) {
|
|
if !need(w) {
|
|
return
|
|
}
|
|
ch, ok := readChange(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
job, err := a.serve.StartPromote(r.PathValue("name"), actorOf(r), ch)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusAccepted, job)
|
|
})
|
|
}
|