Skip to content

Commit 33aaa68

Browse files
authored
feat: add ownedBy and createdAt for OpenModel (#438)
Signed-off-by: googs1025 <[email protected]>
1 parent d8fc1c4 commit 33aaa68

File tree

9 files changed

+101
-2
lines changed

9 files changed

+101
-2
lines changed

api/core/v1alpha1/model_types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const (
3737

3838
HUGGING_FACE = "Huggingface"
3939
MODEL_SCOPE = "ModelScope"
40+
41+
DefaultOwnedBy = "llmaz"
4042
)
4143

4244
// ModelHub represents the model registry for model downloads.
@@ -195,6 +197,18 @@ type ModelSpec struct {
195197
Source ModelSource `json:"source"`
196198
// InferenceConfig represents the inference configurations for the model.
197199
InferenceConfig *InferenceConfig `json:"inferenceConfig,omitempty"`
200+
// OwnedBy represents the owner of the running models serving by the backends,
201+
// which will be exported as the field of "OwnedBy" in openai-compatible API "/models".
202+
// Default to "llmaz" if not set.
203+
// +optional
204+
// +kubebuilder:default="llmaz"
205+
OwnedBy *string `json:"ownedBy,omitempty"`
206+
// CreatedAt represents the creation timestamp of the running models serving by the backends,
207+
// which will be exported as the field of "Created" in openai-compatible API "/models".
208+
// It follows the format of RFC 3339, for example "2024-05-21T10:00:00Z".
209+
// +optional
210+
// +kubebuilder:validation:Format=date-time
211+
CreatedAt *metav1.Time `json:"createdAt,omitempty"`
198212
}
199213

200214
const (

api/core/v1alpha1/zz_generated.deepcopy.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client-go/applyconfiguration/core/v1alpha1/modelspec.go

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/llmaz.io_openmodels.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ spec:
4141
spec:
4242
description: ModelSpec defines the desired state of Model
4343
properties:
44+
createdAt:
45+
description: |-
46+
CreatedAt represents the creation timestamp of the running models serving by the backends,
47+
which will be exported as the field of "Created" in openai-compatible API "/models".
48+
It follows the format of RFC 3339, for example "2024-05-21T10:00:00Z".
49+
format: date-time
50+
type: string
4451
familyName:
4552
description: |-
4653
FamilyName represents the model type, like llama2, which will be auto injected
@@ -106,6 +113,13 @@ spec:
106113
maxItems: 8
107114
type: array
108115
type: object
116+
ownedBy:
117+
default: llmaz
118+
description: |-
119+
OwnedBy represents the owner of the running models serving by the backends,
120+
which will be exported as the field of "OwnedBy" in openai-compatible API "/models".
121+
Default to "llmaz" if not set.
122+
type: string
109123
source:
110124
description: |-
111125
Source represents the source of the model, there're several ways to load

pkg/webhook/openmodel_webhook.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ package webhook
1919
import (
2020
"context"
2121

22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2223
"k8s.io/apimachinery/pkg/runtime"
2324
"k8s.io/apimachinery/pkg/util/validation"
2425
"k8s.io/apimachinery/pkg/util/validation/field"
26+
"k8s.io/utils/ptr"
2527
ctrl "sigs.k8s.io/controller-runtime"
2628
"sigs.k8s.io/controller-runtime/pkg/webhook"
2729
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
@@ -61,6 +63,8 @@ func (w *OpenModelWebhook) Default(ctx context.Context, obj runtime.Object) erro
6163
model.Labels = map[string]string{}
6264
}
6365
model.Labels[coreapi.ModelFamilyNameLabelKey] = string(model.Spec.FamilyName)
66+
createdAt := ptr.Deref[metav1.Time](model.Spec.CreatedAt, metav1.Now().Rfc3339Copy())
67+
model.Spec.CreatedAt = &createdAt
6468
return nil
6569
}
6670

site/content/en/docs/reference/core.v1alpha1.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,24 @@ the model such as loading from huggingface, OCI registry, s3, host path and so o
361361
<p>InferenceConfig represents the inference configurations for the model.</p>
362362
</td>
363363
</tr>
364+
<tr><td><code>ownedBy</code><br/>
365+
<code>string</code>
366+
</td>
367+
<td>
368+
<p>OwnedBy represents the owner of the running models serving by the backends,
369+
which will be exported as the field of &quot;OwnedBy&quot; in openai-compatible API &quot;/models&quot;.
370+
Default to &quot;llmaz&quot; if not set.</p>
371+
</td>
372+
</tr>
373+
<tr><td><code>createdAt</code><br/>
374+
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#time-v1-meta"><code>k8s.io/apimachinery/pkg/apis/meta/v1.Time</code></a>
375+
</td>
376+
<td>
377+
<p>CreatedAt represents the creation timestamp of the running models serving by the backends,
378+
which will be exported as the field of &quot;Created&quot; in openai-compatible API &quot;/models&quot;.
379+
It follows the format of RFC 3339, for example &quot;2024-05-21T10:00:00Z&quot;.</p>
380+
</td>
381+
</tr>
364382
</tbody>
365383
</table>
366384

test/integration/webhook/model_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,31 @@ var _ = ginkgo.Describe("model default and validation", func() {
4848
gomega.Expect(k8sClient.Create(ctx, model)).To(gomega.Succeed())
4949
gomega.Expect(model).To(gomega.BeComparableTo(tc.wantModel(),
5050
cmpopts.IgnoreTypes(coreapi.ModelStatus{}),
51+
cmpopts.IgnoreFields(coreapi.ModelSpec{}, "CreatedAt"),
5152
cmpopts.IgnoreFields(metav1.ObjectMeta{}, "UID", "ResourceVersion", "Generation", "CreationTimestamp", "ManagedFields")))
5253
},
5354
ginkgo.Entry("apply model family name", &testDefaultingCase{
5455
model: func() *coreapi.OpenModel {
5556
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("meta-llama/Meta-Llama-3-8B", "", "", nil, nil).FamilyName("llama3").Obj()
5657
},
5758
wantModel: func() *coreapi.OpenModel {
58-
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("meta-llama/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("Huggingface").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").Obj()
59+
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("meta-llama/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("Huggingface").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").OwnedBy(coreapi.DefaultOwnedBy).Obj()
5960
},
6061
}),
6162
ginkgo.Entry("apply modelscope model hub name", &testDefaultingCase{
6263
model: func() *coreapi.OpenModel {
6364
return wrapper.MakeModel("llama3-8b").FamilyName("llama3").ModelSourceWithModelHub("ModelScope").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "", nil, nil).Obj()
6465
},
6566
wantModel: func() *coreapi.OpenModel {
66-
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("ModelScope").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").Obj()
67+
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("ModelScope").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").OwnedBy(coreapi.DefaultOwnedBy).Obj()
68+
},
69+
}),
70+
ginkgo.Entry("custom ownedBy should not be overwritten", &testDefaultingCase{
71+
model: func() *coreapi.OpenModel {
72+
return wrapper.MakeModel("llama3-8b").FamilyName("llama3").ModelSourceWithModelHub("ModelScope").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "", nil, nil).OwnedBy("custom-owner").Obj()
73+
},
74+
wantModel: func() *coreapi.OpenModel {
75+
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("ModelScope").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").OwnedBy("custom-owner").Obj()
6776
},
6877
}),
6978
)

test/util/validation/validate_model.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package validation
1919
import (
2020
"context"
2121
"errors"
22+
"fmt"
2223

2324
"github.com/onsi/gomega"
2425
"k8s.io/apimachinery/pkg/types"
@@ -37,6 +38,12 @@ func ValidateModel(ctx context.Context, k8sClient client.Client, model *coreapi.
3738
if model.Labels[coreapi.ModelFamilyNameLabelKey] != string(model.Spec.FamilyName) {
3839
return errors.New("family name not right")
3940
}
41+
if model.Spec.OwnedBy == nil {
42+
return fmt.Errorf("ownedBy is nil")
43+
}
44+
if model.Spec.CreatedAt == nil {
45+
return fmt.Errorf("createdAt is nil")
46+
}
4047

4148
return nil
4249
}, util.IntegrationTimeout, util.Interval).Should(gomega.Succeed())

test/util/wrapper/model.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ func (w *ModelWrapper) Label(k, v string) *ModelWrapper {
107107
return w
108108
}
109109

110+
func (w *ModelWrapper) OwnedBy(ownedBy string) *ModelWrapper {
111+
w.Spec.OwnedBy = &ownedBy
112+
return w
113+
}
114+
110115
func MakeFlavor(name string) *FlavorWrapper {
111116
return &FlavorWrapper{
112117
coreapi.Flavor{

0 commit comments

Comments
 (0)