Skip to content

Commit dada251

Browse files
committed
fix(mcp): narrow scheduling dto
Assisted-by: Hephaestus:openai/gpt-5.5 [opencode] Signed-off-by: Owen Adirah <owenadira@gmail.com>
1 parent 99547e6 commit dada251

4 files changed

Lines changed: 58 additions & 30 deletions

File tree

pkg/mcp/localaitools/dto.go

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package localaitools
22

3-
import (
4-
"time"
5-
6-
"github.com/mudler/LocalAI/core/services/voiceprofile"
7-
)
3+
import "github.com/mudler/LocalAI/core/services/voiceprofile"
84

95
// DTOs for the LocalAIClient interface. Where the same shape already exists
106
// elsewhere (config.Gallery, gallery.Metadata, schema.KnownBackend,
@@ -119,25 +115,19 @@ type SetNodeVRAMBudgetRequest struct {
119115
Budget string `json:"budget,omitempty" jsonschema:"VRAM allocation cap as a percentage (e.g. 80%) or absolute amount (e.g. 12GB). Empty string clears the override."`
120116
}
121117

122-
// ModelSchedulingConfig is the REST/MCP wire shape for one per-model
123-
// distributed scheduling rule. Keep this DTO explicit instead of aliasing the
124-
// node-registry model so MCP contracts remain owned by localaitools while still
125-
// matching /api/nodes/scheduling JSON.
118+
// ModelSchedulingConfig is the MCP wire shape for one per-model distributed
119+
// scheduling rule. Keep this DTO explicit instead of aliasing the node-registry
120+
// model so the MCP contract only exposes operator-facing scheduling fields.
126121
type ModelSchedulingConfig struct {
127-
ID string `json:"id"`
128-
ModelName string `json:"model_name"`
129-
NodeSelector string `json:"node_selector,omitempty"`
130-
MinReplicas int `json:"min_replicas"`
131-
MaxReplicas int `json:"max_replicas"`
132-
SpreadAll bool `json:"spread_all,omitempty"`
133-
RoutePolicy string `json:"route_policy,omitempty"`
134-
BalanceAbsThreshold int `json:"balance_abs_threshold,omitempty"`
135-
BalanceRelThreshold float64 `json:"balance_rel_threshold,omitempty"`
136-
MinPrefixMatch float64 `json:"min_prefix_match,omitempty"`
137-
UnsatisfiableUntil *time.Time `json:"unsatisfiable_until,omitempty"`
138-
UnsatisfiableTicks int `json:"unsatisfiable_ticks"`
139-
CreatedAt time.Time `json:"created_at"`
140-
UpdatedAt time.Time `json:"updated_at"`
122+
ModelName string `json:"model_name"`
123+
NodeSelector string `json:"node_selector,omitempty"`
124+
MinReplicas int `json:"min_replicas"`
125+
MaxReplicas int `json:"max_replicas"`
126+
SpreadAll bool `json:"spread_all,omitempty"`
127+
RoutePolicy string `json:"route_policy,omitempty"`
128+
BalanceAbsThreshold int `json:"balance_abs_threshold,omitempty"`
129+
BalanceRelThreshold float64 `json:"balance_rel_threshold,omitempty"`
130+
MinPrefixMatch float64 `json:"min_prefix_match,omitempty"`
141131
}
142132

143133
// SetSchedulingRequest is the input for set_scheduling. It mirrors

pkg/mcp/localaitools/httpapi/client_test.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,16 @@ func fakeLocalAI() *httptest.Server {
8787
mux.HandleFunc("/api/nodes/scheduling", func(w http.ResponseWriter, r *http.Request) {
8888
switch r.Method {
8989
case http.MethodGet:
90-
_ = json.NewEncoder(w).Encode([]map[string]any{{"model_name": "qwen", "min_replicas": 1, "max_replicas": 2}})
90+
_ = json.NewEncoder(w).Encode([]map[string]any{{
91+
"id": "sched-1",
92+
"model_name": "qwen",
93+
"min_replicas": 1,
94+
"max_replicas": 2,
95+
"unsatisfiable_ticks": 1,
96+
"unsatisfiable_until": "2026-01-01T00:00:00Z",
97+
"created_at": "2026-01-01T00:00:00Z",
98+
"updated_at": "2026-01-01T00:00:00Z",
99+
}})
91100
case http.MethodPost:
92101
var body map[string]any
93102
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
@@ -233,6 +242,13 @@ var _ = Describe("httpapi.Client against the LocalAI admin REST surface", func()
233242
Expect(out).To(HaveLen(1))
234243
Expect(out[0].ModelName).To(Equal("qwen"))
235244
Expect(out[0].MinReplicas).To(Equal(1))
245+
Expect(schedulingJSONKeys(&out[0])).ToNot(Or(
246+
HaveKey("id"),
247+
HaveKey("unsatisfiable_until"),
248+
HaveKey("unsatisfiable_ticks"),
249+
HaveKey("created_at"),
250+
HaveKey("updated_at"),
251+
))
236252
})
237253

238254
It("gets one scheduling config", func() {
@@ -245,7 +261,6 @@ var _ = Describe("httpapi.Client against the LocalAI admin REST surface", func()
245261
It("sets a scheduling config", func() {
246262
out, err := c.SetScheduling(ctx, localaitools.SetSchedulingRequest{ModelName: "qwen", MinReplicas: 1, MaxReplicas: 2})
247263
Expect(err).ToNot(HaveOccurred())
248-
Expect(out.ID).To(Equal("sched-1"))
249264
Expect(out.ModelName).To(Equal("qwen"))
250265
Expect(out.MaxReplicas).To(Equal(2))
251266
})
@@ -256,6 +271,14 @@ var _ = Describe("httpapi.Client against the LocalAI admin REST surface", func()
256271
})
257272
})
258273

274+
func schedulingJSONKeys(config *localaitools.ModelSchedulingConfig) map[string]any {
275+
var out map[string]any
276+
b, err := json.Marshal(config)
277+
Expect(err).ToNot(HaveOccurred())
278+
Expect(json.Unmarshal(b, &out)).To(Succeed())
279+
return out
280+
}
281+
259282
var _ = Describe("Model aliases", func() {
260283
Describe("ListAliases", func() {
261284
It("passes the GET /api/aliases payload through unchanged", func() {

pkg/mcp/localaitools/inproc/client_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package inproc
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
67
"os"
78
"path/filepath"
@@ -165,6 +166,13 @@ var _ = Describe("inproc.Client model scheduling", func() {
165166
Expect(created.NodeSelector).To(Equal(`{"gpu":"nvidia"}`))
166167
Expect(created.RoutePolicy).To(Equal("prefix_cache"))
167168
Expect(created.MinPrefixMatch).To(Equal(0.4))
169+
Expect(schedulingJSONKeys(created)).ToNot(Or(
170+
HaveKey("id"),
171+
HaveKey("unsatisfiable_until"),
172+
HaveKey("unsatisfiable_ticks"),
173+
HaveKey("created_at"),
174+
HaveKey("updated_at"),
175+
))
168176

169177
listed, err := c.ListScheduling(ctx)
170178
Expect(err).ToNot(HaveOccurred())
@@ -193,3 +201,15 @@ var _ = Describe("inproc.Client model scheduling", func() {
193201
Expect(missing).To(BeNil())
194202
})
195203
})
204+
205+
func schedulingJSONKeys(config *localaitools.ModelSchedulingConfig) map[string]any {
206+
var out map[string]any
207+
Expect(json.Unmarshal([]byte(mustMarshal(config)), &out)).To(Succeed())
208+
return out
209+
}
210+
211+
func mustMarshal(v any) string {
212+
b, err := json.Marshal(v)
213+
Expect(err).ToNot(HaveOccurred())
214+
return string(b)
215+
}

pkg/mcp/localaitools/scheduling.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import "github.com/mudler/LocalAI/core/services/nodes"
44

55
func SchedulingConfigFromNode(config nodes.ModelSchedulingConfig) ModelSchedulingConfig {
66
return ModelSchedulingConfig{
7-
ID: config.ID,
87
ModelName: config.ModelName,
98
NodeSelector: config.NodeSelector,
109
MinReplicas: config.MinReplicas,
@@ -14,10 +13,6 @@ func SchedulingConfigFromNode(config nodes.ModelSchedulingConfig) ModelSchedulin
1413
BalanceAbsThreshold: config.BalanceAbsThreshold,
1514
BalanceRelThreshold: config.BalanceRelThreshold,
1615
MinPrefixMatch: config.MinPrefixMatch,
17-
UnsatisfiableUntil: config.UnsatisfiableUntil,
18-
UnsatisfiableTicks: config.UnsatisfiableTicks,
19-
CreatedAt: config.CreatedAt,
20-
UpdatedAt: config.UpdatedAt,
2116
}
2217
}
2318

0 commit comments

Comments
 (0)