@@ -29,6 +29,7 @@ import (
2929
3030 "github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/datalayer"
3131 dlsrc "github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/datalayer/datasource"
32+ "github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/datalayer/modelgroups"
3233 "github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/datalayer/pricing"
3334 "github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/plugin"
3435)
@@ -54,9 +55,17 @@ type ModelConfiguration struct {
5455 Pricing pricing.ModelPriceShape `json:"pricing"`
5556}
5657
58+ // ModelGroupConfig is a named group of model names in the config file. It is
59+ // used to populate each listed model's modelgroups.GroupsAttributeKey attribute.
60+ type ModelGroupConfig struct {
61+ Name string `json:"name"`
62+ Models []string `json:"models"`
63+ }
64+
5765// ModelsConfig is the schema of the JSON config file.
5866type ModelsConfig struct {
5967 Models []ModelConfiguration `json:"models"`
68+ Groups []ModelGroupConfig `json:"groups,omitempty"`
6069}
6170
6271// ModelConfigDataSource watches a JSON file listing model names and keeps the
@@ -176,7 +185,8 @@ func (c *ModelConfigDataSource) Stop() {
176185}
177186
178187// syncModels reads the config file, registers every valid listed model in the datastore,
179- // and removes any datastore model that no longer appears in the file.
188+ // removes any datastore model that no longer appears in the file, and (re)populates each
189+ // model's group membership from the config's group-centric "groups" list.
180190func (c * ModelConfigDataSource ) syncModels (ctx context.Context ) error {
181191 logger := log .FromContext (ctx ).WithName ("model-config-datasource" )
182192
@@ -191,7 +201,32 @@ func (c *ModelConfigDataSource) syncModels(ctx context.Context) error {
191201 return err
192202 }
193203
204+ // membership maps a model name to the group names it belongs to, inverting the
205+ // config's group-centric "groups" list ({name, models[]}) so it can be looked up
206+ // per model below. Membership is only resolved against the "models" list once
207+ // that loop below has run, so a group can reference a model before its validity
208+ // as a model entry is known.
209+ // A group with an empty name or an empty model list is invalid as the filter's
210+ // "auto/<group-name>" syntax requires non-empty group-name.
211+ // An individual empty model name within an otherwise valid group is
212+ // skipped on its own.
213+ membership := make (map [string ]modelgroups.Groups )
214+ for _ , g := range cfg .Groups {
215+ if g .Name == "" || len (g .Models ) == 0 {
216+ logger .Info ("skipping invalid group configuration" , "group" , g .Name , "models" , g .Models )
217+ continue
218+ }
219+ for _ , modelName := range g .Models {
220+ if modelName == "" {
221+ logger .Info ("skipping empty model name in group configuration" , "group" , g .Name )
222+ continue
223+ }
224+ membership [modelName ] = append (membership [modelName ], g .Name )
225+ }
226+ }
227+
194228 desired := make (map [string ]struct {}, len (cfg .Models ))
229+ invalid := make (map [string ]struct {}, len (cfg .Models ))
195230 for _ , m := range cfg .Models {
196231 if m .Name == "" {
197232 logger .Info ("skipping model entry with empty name" )
@@ -202,11 +237,35 @@ func (c *ModelConfigDataSource) syncModels(ctx context.Context) error {
202237 "model" , m .Name ,
203238 "input_per_million" , m .Pricing .InputPerMillion ,
204239 "output_per_million" , m .Pricing .OutputPerMillion )
240+ invalid [m .Name ] = struct {}{}
205241 continue
206242 }
207243 desired [m .Name ] = struct {}{}
208244 mdl := c .ds .GetOrCreateModel (m .Name )
209245 mdl .GetAttributes ().Put (pricing .TokenPricesAttributeKey , pricing .ToTokenPrices (m .Pricing ))
246+ // Refresh group membership alongside pricing so a model that lost all its
247+ // group memberships in this reload doesn't keep a stale attribute.
248+ if groups , ok := membership [m .Name ]; ok {
249+ mdl .GetAttributes ().Put (modelgroups .GroupsAttributeKey , groups )
250+ } else {
251+ mdl .GetAttributes ().Delete (modelgroups .GroupsAttributeKey )
252+ }
253+ }
254+
255+ // A group may reference a model name that isn't a valid "models" entry: either
256+ // missing from "models" entirely, or present but itself skipped (empty name,
257+ // negative price). Group membership never auto-creates a model, so just warn,
258+ // distinguishing the two cases so the "invalid model" warning already logged
259+ // above isn't mistaken for an unrelated "unknown model" one.
260+ for modelName := range membership {
261+ if _ , ok := desired [modelName ]; ok {
262+ continue
263+ }
264+ if _ , ok := invalid [modelName ]; ok {
265+ logger .Info ("skipping invalid model referenced in group configuration" , "model" , modelName )
266+ } else {
267+ logger .Info ("skipping unknown model in group configuration" , "model" , modelName )
268+ }
210269 }
211270
212271 for _ , model := range c .ds .GetModels (datalayer .AllModelsPredicate ) {
0 commit comments