wip: the presets API's shape, answering nothing yet
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d817558121
commit
92b588452e
4 files changed
+306
No files matched your search
@@ -86,6 +86,9 @@
|
||||
{
|
||||
"name": "plan"
|
||||
},
|
||||
{
|
||||
"name": "presets"
|
||||
},
|
||||
{
|
||||
"name": "projects"
|
||||
},
|
||||
@@ -3651,6 +3654,116 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/presets": {
|
||||
"get": {
|
||||
"operationId": "getPresets",
|
||||
"summary": "The models that come with llmgw: whether each fits this cluster, is in the catalog, was declined",
|
||||
"tags": [
|
||||
"presets"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PresetsView"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"default": {
|
||||
"$ref": "#/components/responses/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/presets/auto": {
|
||||
"put": {
|
||||
"operationId": "putPresetsAuto",
|
||||
"summary": "Turn on or off the adding of presets when a node fits them",
|
||||
"tags": [
|
||||
"presets"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/Actor"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PresetsAutoRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PresetsView"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"default": {
|
||||
"$ref": "#/components/responses/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/presets/{name}": {
|
||||
"post": {
|
||||
"operationId": "postPresetsName",
|
||||
"summary": "Add a preset to the catalog, and clear its decline",
|
||||
"tags": [
|
||||
"presets"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The preset's name, as `llmgw ctl model presets` lists it.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/Actor"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ModelAdded"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"default": {
|
||||
"$ref": "#/components/responses/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/projects": {
|
||||
"post": {
|
||||
"operationId": "postProjects",
|
||||
@@ -7481,6 +7594,77 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"PresetView": {
|
||||
"type": "object",
|
||||
"description": "PresetView is one preset as it stands in this cluster.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"makes": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"card": {
|
||||
"type": "string"
|
||||
},
|
||||
"licence": {
|
||||
"$ref": "#/components/schemas/catalog.License"
|
||||
},
|
||||
"fits": {
|
||||
"type": "boolean",
|
||||
"description": "Fits is whether some node has a card that meets the preset's need; FitsClasses names those classes."
|
||||
},
|
||||
"fits_classes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"in_catalog": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"added_by": {
|
||||
"type": "string",
|
||||
"description": "AddedBy is \"auto\" when the leader added it, who did otherwise, \"\" when it was not added as a preset; AddedAt is when."
|
||||
},
|
||||
"added_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"declined": {
|
||||
"type": "boolean",
|
||||
"description": "Declined is whether somebody removed it after it was added, so it is not added again by itself; adding it again clears that."
|
||||
}
|
||||
}
|
||||
},
|
||||
"PresetsAutoRequest": {
|
||||
"type": "object",
|
||||
"description": "PresetsAutoRequest turns the automatic adding of presets on or off.",
|
||||
"properties": {
|
||||
"on": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PresetsView": {
|
||||
"type": "object",
|
||||
"description": "PresetsView is the models that come with llmgw, and whether the leader adds them by itself.",
|
||||
"properties": {
|
||||
"auto": {
|
||||
"type": "boolean",
|
||||
"description": "Auto is whether a preset is added when a node fits it and the catalog has no model that makes what it makes (presets.auto)."
|
||||
},
|
||||
"presets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PresetView"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProjectView": {
|
||||
"type": "object",
|
||||
"description": "ProjectView is a project, with how many people and keys are in it.",
|
||||
|
||||
@@ -166,6 +166,7 @@ func (a *API) Register(mux Mux) {
|
||||
}
|
||||
writeJSON(w, http.StatusOK, t)
|
||||
})
|
||||
a.registerPresets(mux)
|
||||
mux.HandleFunc("GET /api/v1/models", a.listModels)
|
||||
mux.HandleFunc("GET /api/v1/replicas", a.listReplicas)
|
||||
// Listing is a read and deleting is a separate verb, so a mistyped
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package adminapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gitea.mynodes.xyz/ilker/llmgw/internal/control/catalog"
|
||||
)
|
||||
|
||||
// PresetsView is the models that come with llmgw, and whether the leader adds
|
||||
// them by itself.
|
||||
type PresetsView struct {
|
||||
// Auto is whether a preset is added when a node fits it and the catalog
|
||||
// has no model that makes what it makes (presets.auto).
|
||||
Auto bool `json:"auto"`
|
||||
Presets []PresetView `json:"presets"`
|
||||
}
|
||||
|
||||
// PresetView is one preset as it stands in this cluster.
|
||||
type PresetView struct {
|
||||
Name string `json:"name"`
|
||||
Makes string `json:"makes"`
|
||||
Description string `json:"description"`
|
||||
Card string `json:"card"`
|
||||
Licence catalog.License `json:"licence"`
|
||||
// Fits is whether some node has a card that meets the preset's need;
|
||||
// FitsClasses names those classes.
|
||||
Fits bool `json:"fits"`
|
||||
FitsClasses []string `json:"fits_classes,omitempty"`
|
||||
InCatalog bool `json:"in_catalog"`
|
||||
// AddedBy is "auto" when the leader added it, who did otherwise, ""
|
||||
// when it was not added as a preset; AddedAt is when.
|
||||
AddedBy string `json:"added_by,omitempty"`
|
||||
AddedAt time.Time `json:"added_at,omitzero"`
|
||||
// Declined is whether somebody removed it after it was added, so it is
|
||||
// not added again by itself; adding it again clears that.
|
||||
Declined bool `json:"declined"`
|
||||
}
|
||||
|
||||
// PresetsAutoRequest turns the automatic adding of presets on or off.
|
||||
type PresetsAutoRequest struct {
|
||||
On bool `json:"on"`
|
||||
}
|
||||
|
||||
// errPresetsUnavailable is a control plane without the store presets are
|
||||
// kept in.
|
||||
var errPresetsUnavailable = errors.New("this control plane has no store, so presets cannot be added here")
|
||||
|
||||
// Presets is the presets as they stand in this cluster.
|
||||
func (a *API) Presets(ctx context.Context) (PresetsView, error) {
|
||||
return PresetsView{}, errPresetsUnavailable
|
||||
}
|
||||
|
||||
// AddPreset adds the preset of that name, and clears its decline.
|
||||
func (a *API) AddPreset(ctx context.Context, name, by string) error {
|
||||
return errPresetsUnavailable
|
||||
}
|
||||
|
||||
// SetAutoPresets turns presets.auto on or off.
|
||||
func (a *API) SetAutoPresets(ctx context.Context, on bool, by string) error {
|
||||
return errPresetsUnavailable
|
||||
}
|
||||
|
||||
func (a *API) registerPresets(mux Mux) {
|
||||
mux.HandleFunc("GET /api/v1/presets", func(w http.ResponseWriter, r *http.Request) {
|
||||
v, err := a.Presets(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, presetStatus(err), err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, v)
|
||||
})
|
||||
mux.HandleFunc("POST /api/v1/presets/{name}", func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := a.AddPreset(r.Context(), r.PathValue("name"), actorOf(r)); err != nil {
|
||||
writeError(w, presetStatus(err), err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, ModelAdded{Added: r.PathValue("name")})
|
||||
})
|
||||
mux.HandleFunc("PUT /api/v1/presets/auto", func(w http.ResponseWriter, r *http.Request) {
|
||||
var req PresetsAutoRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if err := a.SetAutoPresets(r.Context(), req.On, actorOf(r)); err != nil {
|
||||
writeError(w, presetStatus(err), err.Error())
|
||||
return
|
||||
}
|
||||
v, err := a.Presets(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, presetStatus(err), err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, v)
|
||||
})
|
||||
}
|
||||
|
||||
// errNoSuchPreset is a name no preset has.
|
||||
var errNoSuchPreset = errors.New("no preset of that name")
|
||||
|
||||
func presetStatus(err error) int {
|
||||
switch {
|
||||
case errors.Is(err, errPresetsUnavailable):
|
||||
return http.StatusNotImplemented
|
||||
case errors.Is(err, errNoSuchPreset):
|
||||
return http.StatusNotFound
|
||||
default:
|
||||
return http.StatusBadRequest
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,7 @@ type Param struct {
|
||||
// before them and their name.
|
||||
var PathParams = map[string]string{
|
||||
"models/{name}": "The model's name in the catalog.",
|
||||
"presets/{name}": "The preset's name, as `llmgw ctl model presets` lists it.",
|
||||
"nodes/{id}": "The node's ID, as `llmgw ctl nodes` lists it.",
|
||||
"jobs/{id}": "The job's ID, as starting it answered.",
|
||||
"runs/{id}": "The quality run's ID, as starting it answered.",
|
||||
@@ -136,6 +137,12 @@ var Routes = []Route{
|
||||
Request: CollectRequest{}, OptionalBody: true, Response: []CollectResult{}},
|
||||
|
||||
// The catalog.
|
||||
{Method: "GET", Path: "/api/v1/presets", Summary: "The models that come with llmgw: whether each fits this cluster, is in the catalog, was declined",
|
||||
Response: PresetsView{}},
|
||||
{Method: "POST", Path: "/api/v1/presets/{name}", Summary: "Add a preset to the catalog, and clear its decline",
|
||||
Response: ModelAdded{}},
|
||||
{Method: "PUT", Path: "/api/v1/presets/auto", Summary: "Turn on or off the adding of presets when a node fits them",
|
||||
Request: PresetsAutoRequest{}, Response: PresetsView{}},
|
||||
{Method: "GET", Path: "/api/v1/models", Summary: "Every model, with its replicas and state",
|
||||
Response: []ModelView{}},
|
||||
{Method: "POST", Path: "/api/v1/models", Summary: "Add a model to the catalog",
|
||||
|
||||
Reference in new issue
Block a user