Repository: kaito-project/kaito
Pull request #1009: feat: add vllm adapter strength validation
Author: login: zhuangqh
Created: 2025-04-14T08:05:38Z | Merged: 2025-04-15T05:28:53Z

**Reason for Change**:

- add vllm adapter strength valication
- fix strength nil panic error
- skip config validation for hf runtime

**Requirements**

- [x] added unit tests and e2e tests (if applicable).

**Issue Fixed**:

Fixes #998 

**Notes for Reviewers**:



Linked issue #998: vLLM adapter strength validation check
Add validation check strength is not currently supported for vLLM adapter loading

Diff:
diff --git a/api/v1alpha1/workspace_validation.go b/api/v1alpha1/workspace_validation.go
index 1855acb876..f6f79e3da5 100644
--- a/api/v1alpha1/workspace_validation.go
+++ b/api/v1alpha1/workspace_validation.go
@@ -441,9 +441,19 @@ func (i *InferenceSpec) validateCreate(ctx context.Context, namespace string, in
 			i.Preset.AccessMode != ModelImageAccessModePrivate {
 			errs = errs.Also(apis.ErrGeneric("This preset only supports private AccessMode, AccessMode must be private to continue"))
 		}
+		useAdapterStrength := false
+		for _, adapter := range i.Adapters {
+			if adapter.Strength != nil {
+				useAdapterStrength = true
+				break
+			}
+		}
 		err := modelPreset.GetInferenceParameters().Validate(model.RuntimeContext{
 			RuntimeName: runtime,
-			UseAdapters: len(i.Adapters) > 0,
+			RuntimeContextExtraArguments: model.RuntimeContextExtraArguments{
+				AdaptersEnabled:        len(i.Adapters) > 0,
+				AdapterStrengthEnabled: useAdapterStrength,
+			},
 		})
 		if err != nil {
 			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Runtime validation: %v", err)))
@@ -464,19 +474,28 @@ func (i *InferenceSpec) validateCreate(ctx context.Context, namespace string, in
 		errs = errs.Also(validateDuplicateName(i.Adapters, nameMap))
 	}
 
-	if i.Config == "" {
-		klog.InfoS("Inference config not specified. Using default:", DefaultInferenceConfigTemplate)
-		releaseNamespace, err := utils.GetReleaseNamespace()
-		if err != nil {
-			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to determine release namespace: %v", err), "namespace"))
-		}
-		if err := i.validateConfigMap(ctx, releaseNamespace, DefaultInferenceConfigTemplate, instanceType); err != nil {
-			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
-		}
-	} else {
-		if err := i.validateConfigMap(ctx, namespace, i.Config, instanceType); err != nil {
-			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
-		}
+	// check if required fields are set
+	// this check only applies to vllm runtime
+	if runtime == model.RuntimeNameVLLM {
+		func() {
+			var (
+				cmName = i.Config
+				cmNS   = namespace
+				err    error
+			)
+			if cmName == "" {
+				klog.Infof("Inference config not specified. Using default: %q", DefaultInferenceConfigTemplate)
+				cmName = DefaultInferenceConfigTemplate
+				cmNS, err = utils.GetReleaseNamespace()
+				if err != nil {
+					errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to determine release namespace: %v", err), "namespace"))
+					return
+				}
+			}
+			if err := i.validateConfigMap(ctx, cmNS, cmName, instanceType); err != nil {
+				errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
+			}
+		}()
 	}
 
 	return errs
diff --git a/api/v1alpha1/workspace_validation_test.go b/api/v1alpha1/workspace_validation_test.go
index 7b46aad374..8e35795a25 100644
--- a/api/v1alpha1/workspace_validation_test.go
+++ b/api/v1alpha1/workspace_validation_test.go
@@ -578,6 +578,7 @@ func TestInferenceSpecValidateCreate(t *testing.T) {
 	tests := []struct {
 		name          string
 		inferenceSpec *InferenceSpec
+		runtimeName   model.RuntimeName
 		errContent    string // Content expected error to include, if any
 		expectErrs    bool
 	}{
@@ -672,6 +673,77 @@ func TestInferenceSpecValidateCreate(t *testing.T) {
 			errContent: "Number of Adapters exceeds the maximum limit, maximum of 10 allowed",
 			expectErrs: true,
 		},
+		{
+			name: "Valid with adapters/vllm",
+			inferenceSpec: func() *InferenceSpec {
+				spec := &InferenceSpec{
+					Preset: &PresetSpec{
+						PresetMeta: PresetMeta{
+							Name:       ModelName("test-validation"),
+							AccessMode: ModelImageAccessModePublic,
+						},
+					},
+				}
+				for i := 1; i <= 2; i++ {
+					spec.Adapters = append(spec.Adapters, AdapterSpec{
+						Source: &DataSource{
+							Name:  fmt.Sprintf("Adapter-%d", i),
+							Image: fmt.Sprintf("fake.kaito.com/kaito-image:0.0.%d", i),
+						},
+					})
+				}
+				return spec
+			}(),
+		},
+		{
+			name: "Valid with adapters/transformers",
+			inferenceSpec: func() *InferenceSpec {
+				spec := &InferenceSpec{
+					Preset: &PresetSpec{
+						PresetMeta: PresetMeta{
+							Name:       ModelName("test-validation"),
+							AccessMode: ModelImageAccessModePublic,
+						},
+					},
+				}
+				for i := 1; i <= 2; i++ {
+					spec.Adapters = append(spec.Adapters, AdapterSpec{
+						Source: &DataSource{
+							Name:  fmt.Sprintf("Adapter-%d", i),
+							Image: fmt.Sprintf("fake.kaito.com/kaito-image:0.0.%d", i),
+						},
+						Strength: &ValidStrength,
+					})
+				}
+				return spec
+			}(),
+			runtimeName: model.RuntimeNameHuggingfaceTransformers,
+		},
+		{
+			name: "Adapters with strength/vllm",
+			inferenceSpec: func() *InferenceSpec {
+				spec := &InferenceSpec{
+					Preset: &PresetSpec{
+						PresetMeta: PresetMeta{
+							Name:       ModelName("test-validation"),
+							AccessMode: ModelImageAccessModePublic,
+						},
+					},
+				}
+				for i := 1; i <= 2; i++ {
+					spec.Adapters = append(spec.Adapters, AdapterSpec{
+						Source: &DataSource{
+							Name:  fmt.Sprintf("Adapter-%d", i),
+							Image: fmt.Sprintf("fake.kaito.com/kaito-image:0.0.%d", i),
+						},
+						Strength: &ValidStrength,
+					})
+				}
+				return spec
+			}(),
+			errContent: "vLLM does not support adapter strength",
+			expectErrs: true,
+		},
 		{
 			name: "Adapters names are duplicated",
 			inferenceSpec: func() *InferenceSpec {
@@ -752,6 +824,19 @@ func TestInferenceSpecValidateCreate(t *testing.T) {
 			errContent: "missing field(s): inference_config.yaml in ConfigMap",
 			expectErrs: true,
 		},
+		{
+			name: "ConfigMap missing required inference_config.yaml/transformers",
+			inferenceSpec: &InferenceSpec{
+				Preset: &PresetSpec{
+					PresetMeta: PresetMeta{
+						Name:       ModelName("test-validation"),
+						AccessMode: ModelImageAccessModePublic,
+					},
+				},
+				Config: "missing-key-config",
+			},
+			runtimeName: model.RuntimeNameHuggingfaceTransformers,
+		},
 	}
 
 	for _, tc := range tests {
@@ -770,7 +855,11 @@ func TestInferenceSpecValidateCreate(t *testing.T) {
 					}
 				}()
 			}
-			errs := tc.inferenceSpec.validateCreate(ctx, DefaultReleaseNamespace, "Standard_NC24ads_A100_v4", model.RuntimeNameHuggingfaceTransformers)
+			runtime := model.RuntimeNameVLLM
+			if tc.runtimeName != "" {
+				runtime = tc.runtimeName
+			}
+			errs := tc.inferenceSpec.validateCreate(ctx, DefaultReleaseNamespace, "Standard_NC24ads_A100_v4", runtime)
 			hasErrs := errs != nil
 			if hasErrs != tc.expectErrs {
 				t.Errorf("validateCreate() errors = %v, expectErrs %v", errs, tc.expectErrs)
@@ -1759,6 +1848,7 @@ other_field: value
 		name          string
 		inferenceSpec *InferenceSpec
 		resourceSpec  *ResourceSpec
+		runtimeName   model.RuntimeName
 		errContent    string // Content expected error to include, if any
 		expectErrs    bool
 	}{
@@ -1871,7 +1961,11 @@ other_field: value
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			// Validate the inference spec
-			errs := tc.inferenceSpec.validateCreate(ctx, DefaultReleaseNamespace, tc.resourceSpec.InstanceType, model.RuntimeNameHuggingfaceTransformers)
+			runtimeName := model.RuntimeNameVLLM
+			if tc.runtimeName != "" {
+				runtimeName = tc.runtimeName
+			}
+			errs := tc.inferenceSpec.validateCreate(ctx, DefaultReleaseNamespace, tc.resourceSpec.InstanceType, runtimeName)
 			hasErrs := errs != nil
 
 			if hasErrs != tc.expectErrs {
diff --git a/api/v1beta1/workspace_validation.go b/api/v1beta1/workspace_validation.go
index 05c478f268..7ceed645f0 100644
--- a/api/v1beta1/workspace_validation.go
+++ b/api/v1beta1/workspace_validation.go
@@ -441,9 +441,19 @@ func (i *InferenceSpec) validateCreate(ctx context.Context, namespace string, in
 			i.Preset.AccessMode != ModelImageAccessModePrivate {
 			errs = errs.Also(apis.ErrGeneric("This preset only supports private AccessMode, AccessMode must be private to continue"))
 		}
+		useAdapterStrength := false
+		for _, adapter := range i.Adapters {
+			if adapter.Strength != nil {
+				useAdapterStrength = true
+				break
+			}
+		}
 		err := modelPreset.GetInferenceParameters().Validate(model.RuntimeContext{
 			RuntimeName: runtime,
-			UseAdapters: len(i.Adapters) > 0,
+			RuntimeContextExtraArguments: model.RuntimeContextExtraArguments{
+				AdaptersEnabled:        len(i.Adapters) > 0,
+				AdapterStrengthEnabled: useAdapterStrength,
+			},
 		})
 		if err != nil {
 			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Runtime validation: %v", err)))
@@ -464,19 +474,28 @@ func (i *InferenceSpec) validateCreate(ctx context.Context, namespace string, in
 		errs = errs.Also(validateDuplicateName(i.Adapters, nameMap))
 	}
 
-	if i.Config == "" {
-		klog.InfoS("Inference config not specified. Using default:", DefaultInferenceConfigTemplate)
-		releaseNamespace, err := utils.GetReleaseNamespace()
-		if err != nil {
-			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to determine release namespace: %v", err), "namespace"))
-		}
-		if err := i.validateConfigMap(ctx, releaseNamespace, DefaultInferenceConfigTemplate, instanceType); err != nil {
-			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
-		}
-	} else {
-		if err := i.validateConfigMap(ctx, namespace, i.Config, instanceType); err != nil {
-			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
-		}
+	// check if required fields are set
+	// this check only applies to vllm runtime
+	if runtime == model.RuntimeNameVLLM {
+		func() {
+			var (
+				cmName = i.Config
+				cmNS   = namespace
+				err    error
+			)
+			if cmName == "" {
+				klog.Infof("Inference config not specified. Using default: %q", DefaultInferenceConfigTemplate)
+				cmName = DefaultInferenceConfigTemplate
+				cmNS, err = utils.GetReleaseNamespace()
+				if err != nil {
+					errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to determine release namespace: %v", err), "namespace"))
+					return
+				}
+			}
+			if err := i.validateConfigMap(ctx, cmNS, cmName, instanceType); err != nil {
+				errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
+			}
+		}()
 	}
 
 	return errs
diff --git a/api/v1beta1/workspace_validation_test.go b/api/v1beta1/workspace_validation_test.go
index eba135f911..40ab201610 100644
--- a/api/v1beta1/workspace_validation_test.go
+++ b/api/v1beta1/workspace_validation_test.go
@@ -578,6 +578,7 @@ func TestInferenceSpecValidateCreate(t *testing.T) {
 	tests := []struct {
 		name          string
 		inferenceSpec *InferenceSpec
+		runtimeName   model.RuntimeName
 		errContent    string // Content expected error to include, if any
 		expectErrs    bool
 	}{
@@ -672,6 +673,77 @@ func TestInferenceSpecValidateCreate(t *testing.T) {
 			errContent: "Number of Adapters exceeds the maximum limit, maximum of 10 allowed",
 			expectErrs: true,
 		},
+		{
+			name: "Valid with adapters/vllm",
+			inferenceSpec: func() *InferenceSpec {
+				spec := &InferenceSpec{
+					Preset: &PresetSpec{
+						PresetMeta: PresetMeta{
+							Name:       ModelName("test-validation"),
+							AccessMode: ModelImageAccessModePublic,
+						},
+					},
+				}
+				for i := 1; i <= 2; i++ {
+					spec.Adapters = append(spec.Adapters, AdapterSpec{
+						Source: &DataSource{
+							Name:  fmt.Sprintf("Adapter-%d", i),
+							Image: fmt.Sprintf("fake.kaito.com/kaito-image:0.0.%d", i),
+						},
+					})
+				}
+				return spec
+			}(),
+		},
+		{
+			name: "Valid with adapters/transformers",
+			inferenceSpec: func() *InferenceSpec {
+				spec := &InferenceSpec{
+					Preset: &PresetSpec{
+						PresetMeta: PresetMeta{
+							Name:       ModelName("test-validation"),
+							AccessMode: ModelImageAccessModePublic,
+						},
+					},
+				}
+				for i := 1; i <= 2; i++ {
+					spec.Adapters = append(spec.Adapters, AdapterSpec{
+						Source: &DataSource{
+							Name:  fmt.Sprintf("Adapter-%d", i),
+							Image: fmt.Sprintf("fake.kaito.com/kaito-image:0.0.%d", i),
+						},
+						Strength: &ValidStrength,
+					})
+				}
+				return spec
+			}(),
+			runtimeName: model.RuntimeNameHuggingfaceTransformers,
+		},
+		{
+			name: "Adapters with strength/vllm",
+			inferenceSpec: func() *InferenceSpec {
+				spec := &InferenceSpec{
+					Preset: &PresetSpec{
+						PresetMeta: PresetMeta{
+							Name:       ModelName("test-validation"),
+							AccessMode: ModelImageAccessModePublic,
+						},
+					},
+				}
+				for i := 1; i <= 2; i++ {
+					spec.Adapters = append(spec.Adapters, AdapterSpec{
+						Source: &DataSource{
+							Name:  fmt.Sprintf("Adapter-%d", i),
+							Image: fmt.Sprintf("fake.kaito.com/kaito-image:0.0.%d", i),
+						},
+						Strength: &ValidStrength,
+					})
+				}
+				return spec
+			}(),
+			errContent: "vLLM does not support adapter strength",
+			expectErrs: true,
+		},
 		{
 			name: "Adapters names are duplicated",
 			inferenceSpec: func() *InferenceSpec {
@@ -752,6 +824,19 @@ func TestInferenceSpecValidateCreate(t *testing.T) {
 			errContent: "missing field(s): inference_config.yaml in ConfigMap",
 			expectErrs: true,
 		},
+		{
+			name: "ConfigMap missing required inference_config.yaml/transformers",
+			inferenceSpec: &InferenceSpec{
+				Preset: &PresetSpec{
+					PresetMeta: PresetMeta{
+						Name:       ModelName("test-validation"),
+						AccessMode: ModelImageAccessModePublic,
+					},
+				},
+				Config: "missing-key-config",
+			},
+			runtimeName: model.RuntimeNameHuggingfaceTransformers,
+		},
 	}
 
 	for _, tc := range tests {
@@ -770,7 +855,11 @@ func TestInferenceSpecValidateCreate(t *testing.T) {
 					}
 				}()
 			}
-			errs := tc.inferenceSpec.validateCreate(ctx, DefaultReleaseNamespace, "Standard_NC24ads_A100_v4", model.RuntimeNameHuggingfaceTransformers)
+			runtime := model.RuntimeNameVLLM
+			if tc.runtimeName != "" {
+				runtime = tc.runtimeName
+			}
+			errs := tc.inferenceSpec.validateCreate(ctx, DefaultReleaseNamespace, "Standard_NC24ads_A100_v4", runtime)
 			hasErrs := errs != nil
 			if hasErrs != tc.expectErrs {
 				t.Errorf("validateCreate() errors = %v, expectErrs %v", errs, tc.expectErrs)
@@ -1759,6 +1848,7 @@ other_field: value
 		name          string
 		inferenceSpec *InferenceSpec
 		resourceSpec  *ResourceSpec
+		runtimeName   model.RuntimeName
 		errContent    string // Content expected error to include, if any
 		expectErrs    bool
 	}{
@@ -1871,7 +1961,11 @@ other_field: value
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			// Validate the inference spec
-			errs := tc.inferenceSpec.validateCreate(ctx, DefaultReleaseNamespace, tc.resourceSpec.InstanceType, model.RuntimeNameHuggingfaceTransformers)
+			runtimeName := model.RuntimeNameVLLM
+			if tc.runtimeName != "" {
+				runtimeName = tc.runtimeName
+			}
+			errs := tc.inferenceSpec.validateCreate(ctx, DefaultReleaseNamespace, tc.resourceSpec.InstanceType, runtimeName)
 			hasErrs := errs != nil
 
 			if hasErrs != tc.expectErrs {
diff --git a/pkg/model/interface.go b/pkg/model/interface.go
index c90e0131d7..4dc27fa71f 100644
--- a/pkg/model/interface.go
+++ b/pkg/model/interface.go
@@ -8,6 +8,7 @@ import (
 	"math"
 	"path"
 	"strconv"
+	"strings"
 	"time"
 
 	corev1 "k8s.io/api/core/v1"
@@ -142,7 +143,12 @@ type RuntimeContext struct {
 	GPUConfig    *sku.GPUConfig
 	ConfigVolume *corev1.VolumeMount
 	SKUNumGPUs   int
-	UseAdapters  bool
+	RuntimeContextExtraArguments
+}
+
+type RuntimeContextExtraArguments struct {
+	AdaptersEnabled        bool
+	AdapterStrengthEnabled bool
 }
 
 func (p *PresetParam) GetInferenceCommand(rc RuntimeContext) []string {
@@ -176,7 +182,7 @@ func (p *PresetParam) buildVLLMInferenceCommand(rc RuntimeContext) []string {
 	if !p.DisableTensorParallelism {
 		p.VLLM.ModelRunParams["tensor-parallel-size"] = strconv.Itoa(rc.SKUNumGPUs)
 	}
-	if !p.VLLM.DisallowLoRA && rc.UseAdapters {
+	if !p.VLLM.DisallowLoRA && rc.AdaptersEnabled {
 		p.VLLM.ModelRunParams["enable-lora"] = ""
 	}
 	gpuMemUtil := getGPUMemoryUtilForVLLM(rc.GPUConfig)
@@ -189,11 +195,18 @@ func (p *PresetParam) buildVLLMInferenceCommand(rc RuntimeContext) []string {
 }
 
 func (p *PresetParam) Validate(rc RuntimeContext) error {
+	var errs []string
 	switch rc.RuntimeName {
 	case RuntimeNameVLLM:
-		if rc.UseAdapters && p.VLLM.DisallowLoRA {
-			return fmt.Errorf("vLLM does not support LoRA adapters for this model: %s", p.VLLM.ModelName)
+		if rc.AdaptersEnabled && p.VLLM.DisallowLoRA {
+			errs = append(errs, fmt.Sprintf("vLLM does not support LoRA adapters for this model: %s", p.VLLM.ModelName))
 		}
+		if rc.AdapterStrengthEnabled {
+			errs = append(errs, "vLLM does not support adapter strength")
+		}
+	}
+	if len(errs) > 0 {
+		return fmt.Errorf("%s", strings.Join(errs, "; "))
 	}
 	return nil
 }
diff --git a/pkg/workspace/inference/preset-inferences.go b/pkg/workspace/inference/preset-inferences.go
index 82c0cf3f9f..00a10e7a95 100644
--- a/pkg/workspace/inference/preset-inferences.go
+++ b/pkg/workspace/inference/preset-inferences.go
@@ -205,7 +205,9 @@ func CreatePresetInference(ctx context.Context, workspaceObj *v1beta1.Workspace,
 		GPUConfig:    gpuConfig,
 		ConfigVolume: &cmVolumeMount,
 		SKUNumGPUs:   skuNumGPUs,
-		UseAdapters:  len(workspaceObj.Inference.Adapters) > 0,
+		RuntimeContextExtraArguments: pkgmodel.RuntimeContextExtraArguments{
+			AdaptersEnabled: len(workspaceObj.Inference.Adapters) > 0,
+		},
 	})
 
 	image, imagePullSecrets := GetInferenceImageInfo(ctx, workspaceObj, inferenceParam)
diff --git a/pkg/workspace/manifests/manifests.go b/pkg/workspace/manifests/manifests.go
index 3df57848fc..8df4e5b3bf 100644
--- a/pkg/workspace/manifests/manifests.go
+++ b/pkg/workspace/manifests/manifests.go
@@ -378,7 +378,7 @@ func GeneratePullerContainers(wObj *kaitov1beta1.Workspace, volumeMounts []corev
 	size := len(wObj.Inference.Adapters)
 
 	initContainers := make([]corev1.Container, 0, size)
-	envVars := make([]corev1.EnvVar, 0, size)
+	var envVars []corev1.EnvVar
 	volumes := make([]corev1.Volume, 0, size)
 
 	for _, adapter := range wObj.Inference.Adapters {
@@ -388,11 +388,13 @@ func GeneratePullerContainers(wObj *kaitov1beta1.Workspace, volumeMounts []corev
 		volume, volumeMount := utils.ConfigImagePullSecretVolume(sourceName+"-inference-adapter", source.ImagePullSecrets)
 		volumes = append(volumes, volume)
 
-		envVar := corev1.EnvVar{
-			Name:  sourceName,
-			Value: *adapter.Strength,
+		if adapter.Strength != nil {
+			envVar := corev1.EnvVar{
+				Name:  sourceName,
+				Value: *adapter.Strength,
+			}
+			envVars = append(envVars, envVar)
 		}
-		envVars = append(envVars, envVar)
 
 		outputDirectory := path.Join("/mnt/adapter", sourceName)
 		pullerContainer := image.NewPullerContainer(source.Image, outputDirectory)
diff --git a/test/e2e/inference_with_adapters_test.go b/test/e2e/inference_with_adapters_test.go
index 7fd33f4be5..ac74ce74b3 100644
--- a/test/e2e/inference_with_adapters_test.go
+++ b/test/e2e/inference_with_adapters_test.go
@@ -28,7 +28,7 @@ var fullImageName1 = utils.GetEnv("E2E_ACR_REGISTRY") + "/" + imageName1 + ":0.0
 var imageName2 = "e2e-adapter2"
 var fullImageName2 = utils.GetEnv("E2E_ACR_REGISTRY") + "/" + imageName2 + ":0.0.1"
 
-var validAdapters1 = []kaitov1beta1.AdapterSpec{
+var testAdapters1 = []kaitov1beta1.AdapterSpec{
 	{
 		Source: &kaitov1beta1.DataSource{
 			Name:  imageName1,
@@ -41,7 +41,7 @@ var validAdapters1 = []kaitov1beta1.AdapterSpec{
 	},
 }
 
-var validAdapters2 = []kaitov1beta1.AdapterSpec{
+var testAdapters2 = []kaitov1beta1.AdapterSpec{
 	{
 		Source: &kaitov1beta1.DataSource{
 			Name:  imageName2,
@@ -54,21 +54,20 @@ var validAdapters2 = []kaitov1beta1.AdapterSpec{
 	},
 }
 
-var baseInitContainer = image.NewPullerContainer("", "")
-
-var expectedInitContainers1 = []corev1.Container{
+var phi4AdapterName = "adapter-phi-3-mini-pycoder"
+var phi4Adapter = []kaitov1beta1.AdapterSpec{
 	{
-		Name:  baseInitContainer.Name + "-" + imageName1,
-		Image: baseInitContainer.Image,
+		Source: &kaitov1beta1.DataSource{
+			Name:  phi4AdapterName,
+			Image: utils.GetEnv("E2E_ACR_REGISTRY") + "/" + phi4AdapterName + ":0.0.1",
+			ImagePullSecrets: []string{
+				utils.GetEnv("E2E_ACR_REGISTRY_SECRET"),
+			},
+		},
 	},
 }
 
-var expectedInitContainers2 = []corev1.Container{
-	{
-		Name:  baseInitContainer.Name + "-" + imageName2,
-		Image: baseInitContainer.Image,
-	},
-}
+var baseInitContainer = image.NewPullerContainer("", "")
 
 func validateInitContainers(workspaceObj *kaitov1beta1.Workspace, expectedInitContainers []corev1.Container) {
 	By("Checking the InitContainers", func() {
@@ -222,7 +221,7 @@ var _ = Describe("Workspace Preset", func() {
 
 	It("should create a falcon workspace with adapter, and update the workspace with another adapter", utils.GinkgoLabelFastCheck, func() {
 		numOfNode := 1
-		workspaceObj := createCustomWorkspaceWithAdapter(numOfNode, validAdapters1)
+		workspaceObj := createCustomWorkspaceWithAdapter(numOfNode, testAdapters1)
 
 		defer cleanupResources(workspaceObj)
 		time.Sleep(30 * time.Second)
@@ -240,11 +239,24 @@ var _ = Describe("Workspace Preset", func() {
 
 		validateRevision(workspaceObj, "1")
 
+		var expectedInitContainers1 = []corev1.Container{
+			{
+				Name:  baseInitContainer.Name + "-" + imageName1,
+				Image: baseInitContainer.Image,
+			},
+		}
+		var expectedInitContainers2 = []corev1.Container{
+			{
+				Name:  baseInitContainer.Name + "-" + imageName2,
+				Image: baseInitContainer.Image,
+			},
+		}
+
 		validateInitContainers(workspaceObj, expectedInitContainers1)
-		validateImagePullSecrets(workspaceObj, validAdapters1[0].Source.ImagePullSecrets)
+		validateImagePullSecrets(workspaceObj, testAdapters1[0].Source.ImagePullSecrets)
 		validateAdapterAdded(workspaceObj, workspaceObj.Name, imageName1)
 
-		workspaceObj = updateCustomWorkspaceWithAdapter(workspaceObj, validAdapters2)
+		workspaceObj = updateCustomWorkspaceWithAdapter(workspaceObj, testAdapters2)
 		validateResourceStatus(workspaceObj)
 
 		time.Sleep(30 * time.Second)
@@ -257,7 +269,7 @@ var _ = Describe("Workspace Preset", func() {
 
 		validateRevision(workspaceObj, "2")
 		validateInitContainers(workspaceObj, expectedInitContainers2)
-		validateImagePullSecrets(workspaceObj, validAdapters2[0].Source.ImagePullSecrets)
+		validateImagePullSecrets(workspaceObj, testAdapters2[0].Source.ImagePullSecrets)
 		validateAdapterAdded(workspaceObj, workspaceObj.Name, imageName2)
 	})
 })
diff --git a/test/e2e/preset_vllm_test.go b/test/e2e/preset_vllm_test.go
index 89cf270136..c28d0505e5 100644
--- a/test/e2e/preset_vllm_test.go
+++ b/test/e2e/preset_vllm_test.go
@@ -190,19 +190,6 @@ var _ = Describe("Workspace Preset on vllm runtime", func() {
 
 	It("should create a phi4 workspace with adapter successfully", utils.GinkgoLabelFastCheck, func() {
 		numOfNode := 1
-		phi4AdapterName := "adapter-phi-3-mini-pycoder"
-		var phi4Adapter = []kaitov1beta1.AdapterSpec{
-			{
-				Source: &kaitov1beta1.DataSource{
-					Name:  phi4AdapterName,
-					Image: utils.GetEnv("E2E_ACR_REGISTRY") + "/" + phi4AdapterName + ":0.0.1",
-					ImagePullSecrets: []string{
-						utils.GetEnv("E2E_ACR_REGISTRY_SECRET"),
-					},
-				},
-				Strength: &DefaultStrength,
-			},
-		}
 		workspaceObj := createPhi4WorkspaceWithAdapterAndVLLM(numOfNode, phi4Adapter)
 
 		defer cleanupResources(workspaceObj)
diff --git a/test/e2e/webhook_test.go b/test/e2e/webhook_test.go
index 976fdb8e8a..72128b7062 100644
--- a/test/e2e/webhook_test.go
+++ b/test/e2e/webhook_test.go
@@ -61,13 +61,62 @@ var _ = Describe("Workspace Validation Webhook", utils.GinkgoLabelFastCheck, fun
 		})
 	})
 
+	It("should validate the workspace inference adapters at creation", func() {
+		By("Creating a vllm workspace with adapter strength", func() {
+			workspaceObj := utils.GenerateInferenceWorkspaceManifestWithVLLM(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC6s_v3",
+				&metav1.LabelSelector{
+					MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
+				}, nil, PresetFalcon7BModel, kaitov1beta1.ModelImageAccessModePublic, nil, nil, testAdapters1)
+
+			// Create workspace
+			Eventually(func() error {
+				return utils.TestingCluster.KubeClient.Create(ctx, workspaceObj, &client.CreateOptions{})
+			}, utils.PollTimeout, utils.PollInterval).
+				Should(HaveOccurred(), "Failed to create workspace %s", workspaceObj.Name)
+		})
+
+		By("Creating a vllm workspace without adapter strength", func() {
+			workspaceObj := utils.GenerateInferenceWorkspaceManifestWithVLLM(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC6s_v3",
+				&metav1.LabelSelector{
+					MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
+				}, nil, PresetPhi4MiniModel, kaitov1beta1.ModelImageAccessModePublic, nil, nil, phi4Adapter)
+
+			// Create workspace
+			Eventually(func() error {
+				return utils.TestingCluster.KubeClient.Create(ctx, workspaceObj, &client.CreateOptions{})
+			}, utils.PollTimeout, utils.PollInterval).
+				Should(Succeed(), "Failed to create workspace %s", workspaceObj.Name)
+			// delete workspace
+			Eventually(func() error {
+				return utils.TestingCluster.KubeClient.Delete(ctx, workspaceObj, &client.DeleteOptions{})
+			}, utils.PollTimeout, utils.PollInterval).Should(Succeed(), "Failed to delete workspace")
+		})
+
+		By("Creating a transformers workspace with adapter strength", func() {
+			workspaceObj := utils.GenerateInferenceWorkspaceManifest(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC6s_v3",
+				&metav1.LabelSelector{
+					MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
+				}, nil, PresetFalcon7BModel, kaitov1beta1.ModelImageAccessModePublic, nil, nil, testAdapters1)
+			// Create workspace
+			Eventually(func() error {
+				return utils.TestingCluster.KubeClient.Create(ctx, workspaceObj, &client.CreateOptions{})
+			}, utils.PollTimeout, utils.PollInterval).
+				Should(Succeed(), "Failed to create workspace %s", workspaceObj.Name)
+
+			// delete workspace
+			Eventually(func() error {
+				return utils.TestingCluster.KubeClient.Delete(ctx, workspaceObj, &client.DeleteOptions{})
+			}, utils.PollTimeout, utils.PollInterval).Should(Succeed(), "Failed to delete workspace")
+		})
+	})
+
 	It("should validate the workspace inference configfile", func() {
-		workspaceObj := utils.GenerateInferenceWorkspaceManifest(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC12s_v3",
-			&metav1.LabelSelector{
-				MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
-			}, nil, PresetFalcon7BModel, kaitov1beta1.ModelImageAccessModePublic, nil, nil, nil)
+		By("Creating a 2gpu vllm workspace without configfile", func() {
+			workspaceObj := utils.GenerateInferenceWorkspaceManifestWithVLLM(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC12s_v3",
+				&metav1.LabelSelector{
+					MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
+				}, nil, PresetFalcon7BModel, kaitov1beta1.ModelImageAccessModePublic, nil, nil, nil)
 
-		By("Creating a 2gpu workspace without configfile", func() {
 			// Create workspace
 			Eventually(func() error {
 				return utils.TestingCluster.KubeClient.Create(ctx, workspaceObj, &client.CreateOptions{})
@@ -75,7 +124,30 @@ var _ = Describe("Workspace Validation Webhook", utils.GinkgoLabelFastCheck, fun
 				Should(HaveOccurred(), "Failed to create workspace %s", workspaceObj.Name)
 		})
 
+		By("Creating a 2gpu transformers workspace without configfile", func() {
+			workspaceObj := utils.GenerateInferenceWorkspaceManifest(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC12s_v3",
+				&metav1.LabelSelector{
+					MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
+				}, nil, PresetFalcon7BModel, kaitov1beta1.ModelImageAccessModePublic, nil, nil, nil)
+
+			// Create workspace
+			Eventually(func() error {
+				return utils.TestingCluster.KubeClient.Create(ctx, workspaceObj, &client.CreateOptions{})
+			}, utils.PollTimeout, utils.PollInterval).
+				Should(Succeed(), "Failed to create workspace %s", workspaceObj.Name)
+
+			// delete workspace
+			Eventually(func() error {
+				return utils.TestingCluster.KubeClient.Delete(ctx, workspaceObj, &client.DeleteOptions{})
+			}, utils.PollTimeout, utils.PollInterval).Should(Succeed(), "Failed to delete workspace")
+		})
+
 		By("Creating a 2gpu workspace with correct configfile", func() {
+			workspaceObj := utils.GenerateInferenceWorkspaceManifestWithVLLM(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC12s_v3",
+				&metav1.LabelSelector{
+					MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
+				}, nil, PresetFalcon7BModel, kaitov1beta1.ModelImageAccessModePublic, nil, nil, nil)
+
 			cm := corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "inference-config-webhook",
@@ -100,12 +172,11 @@ vllm:
 				return utils.TestingCluster.KubeClient.Create(ctx, workspaceObj, &client.CreateOptions{})
 			}, utils.PollTimeout, utils.PollInterval).
 				Should(Succeed(), "Failed to create workspace %s", workspaceObj.Name)
+			// delete workspace
+			Eventually(func() error {
+				return utils.TestingCluster.KubeClient.Delete(ctx, workspaceObj, &client.DeleteOptions{})
+			}, utils.PollTimeout, utils.PollInterval).Should(Succeed(), "Failed to delete workspace")
 		})
-
-		// delete	workspace
-		Eventually(func() error {
-			return utils.TestingCluster.KubeClient.Delete(ctx, workspaceObj, &client.DeleteOptions{})
-		}, utils.PollTimeout, utils.PollInterval).Should(Succeed(), "Failed to delete workspace")
 	})
 
 	It("should validate the workspace tuning spec at creation ", func() {


Reviews:
user: login: Fei-Guo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:40:57Z
user: login: ishaansehgal99
body: I was thinking we just have a small check: if user specifies an adapter strength verify the annotation being used is transformers. Otherwise reject - what am I missing
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T20:07:58Z
user: login: ishaansehgal99
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T01:00:11Z
user: login: Fei-Guo
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-15T05:28:37Z

Review comments:
diff_hunk: @@ -142,7 +143,12 @@ type RuntimeContext struct {
 	GPUConfig    *sku.GPUConfig
 	ConfigVolume *corev1.VolumeMount
 	SKUNumGPUs   int
-	UseAdapters  bool
+	RuntimeCapabilityRequest
path: pkg/model/interface.go
user: login: Fei-Guo
body: Rename "RuntimeCapabilityRequest" -> "ExtraArguments"

InferenceAdapter->AdaptersEnabled
AdapterStrength->AdapterStrengthEnabled
created_at: 2025-04-14T18:32:28Z
author_association: COLLABORATOR
diff_hunk: @@ -464,19 +474,27 @@ func (i *InferenceSpec) validateCreate(ctx context.Context, namespace string, in
 		errs = errs.Also(validateDuplicateName(i.Adapters, nameMap))
 	}
 
-	if i.Config == "" {
-		klog.InfoS("Inference config not specified. Using default:", DefaultInferenceConfigTemplate)
-		releaseNamespace, err := utils.GetReleaseNamespace()
-		if err != nil {
-			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to determine release namespace: %v", err), "namespace"))
-		}
-		if err := i.validateConfigMap(ctx, releaseNamespace, DefaultInferenceConfigTemplate, instanceType); err != nil {
-			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
-		}
-	} else {
-		if err := i.validateConfigMap(ctx, namespace, i.Config, instanceType); err != nil {
-			errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
-		}
+	// check if required fields are set
+	if runtime == model.RuntimeNameVLLM {
path: api/v1alpha1/workspace_validation.go
user: login: Fei-Guo
body: Is the configure file only used by vllm, not hf? if so, add a comment please.
created_at: 2025-04-14T18:40:35Z
author_association: COLLABORATOR

PR comments:
user: login: kaito-pr-agent[bot]
created_at: 2025-04-14T08:06:05Z
body: ## Title

#### (Describe updated until commit https://github.com/kaito-project/kaito/commit/b35921ce290df94c47904fd8c468eb4e2ef57f90)


Enhance vLLM adapter strength validation


___
### **Changes title**
- Add vLLM adapter strength validation
- Update tests for adapter strength validation
- Modify model interface for adapter strength validation
- Adjust workspace inference preset for adapter strength validation



___

### **Description**
- Added validation check for adapter strength in `api/v1alpha1/workspace_validation.go` and `api/v1beta1/workspace_validation.go`.

- Updated test cases in `api/v1alpha1/workspace_validation_test.go`, `api/v1beta1/workspace_validation_test.go`, `test/e2e/inference_with_adapters_test.go`, `test/e2e/preset_vllm_test.go`, and `test/e2e/webhook_test.go` to handle adapter strength validation.

- Modified `pkg/model/interface.go` to include `RuntimeContextExtraArguments` and updated `Validate` method to handle adapter strength validation.

- Adjusted `pkg/workspace/inference/preset-inferences.go` and `pkg/workspace/manifests/manifests.go` to pass `RuntimeContextExtraArguments` to validation methods.


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>5 files</summary><table>
<tr>
  <td><strong>workspace_validation.go</strong><dd><code>Add vLLM adapter strength validation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-5aa8d95205b91001e903b72a7a9dfb992d1acbeae62204197db42d7c684d811e">+33/-14</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>workspace_validation.go</strong><dd><code>Add vLLM adapter strength validation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-ff810030efa090347e63f5a61e4344f94c6f3167ace34bfcc8f602249acd1959">+33/-14</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>interface.go</strong><dd><code>Modify model interface for adapter strength validation</code>&nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-04a7e55519ccf0b2fdd00eb3ef35c6d8435e39c7e75331a2995fee3936b0c8a0">+17/-4</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>preset-inferences.go</strong><dd><code>Adjust workspace inference preset for adapter strength validation</code></dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-70fba8abbb995994620f8b0660cefb14ab620deca0713acb7000082c002da0aa">+3/-1</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>manifests.go</strong><dd><code>Adjust workspace inference preset for adapter strength validation</code></dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-5857e0b2f1941c47dd88c752fab691c28690d8ca2c3a497dbc78a7ed4b23d4b4">+7/-5</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>5 files</summary><table>
<tr>
  <td><strong>workspace_validation_test.go</strong><dd><code>Update tests for adapter strength validation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-40361cf72c705b9ed5e31c558e6ef9fa52e32b75118ae5d14d875513614fd686">+96/-2</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>workspace_validation_test.go</strong><dd><code>Update tests for adapter strength validation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-f7f7c3213c1c9eace61d77a0dcd6d091f3ea17c0b81ba8cb32ad416bdf09f888">+96/-2</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>inference_with_adapters_test.go</strong><dd><code>Update tests for adapter strength validation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-f0da6759781436985c4933dca4fc9820e2355b2cc446b08b8dfc71db6663c575">+29/-17</a>&nbsp; </td>

</tr>

<tr>
  <td><strong>preset_vllm_test.go</strong><dd><code>Update tests for adapter strength validation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-f8a6c00571ab04b7eba0e8d4c081b4d1e0cb3a669df36c0f46e1cf4c626940a1">+0/-13</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>webhook_test.go</strong><dd><code>Update tests for adapter strength validation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/kaito-project/kaito/pull/1009/files#diff-d1f37c1f53f266ed09116f139e0e92c59c03f92665260a4a300eb1e0f10f3c71">+81/-10</a>&nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

___

> <details> <summary>  Need help?</summary><li>Type <code>/help how to ...</code> in the comments thread for any questions about PR-Agent usage.</li><li>Check out the <a href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a> for more information.</li></details>
author_association: CONTRIBUTOR
user: login: kaito-pr-agent[bot]
created_at: 2025-04-14T08:06:20Z
body: ## PR Reviewer Guide 🔍

#### (Review updated until commit https://github.com/kaito-project/kaito/commit/b35921ce290df94c47904fd8c468eb4e2ef57f90)


Here are some key observations to aid the review process:

<table>
<tr><td>

**🎫 Ticket compliance analysis 🔶**



**[998](https://github.com/kaito-project/kaito/issues/998) - Partially compliant**

Compliant requirements:

- Add validation check strength is not currently supported for vLLM adapter loading

Non-compliant requirements:

- None



</td></tr>
<tr><td>&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr>
<tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr>
<tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr>
<tr><td>⚡&nbsp;<strong>No major issues detected</strong></td></tr>
</table>

author_association: CONTRIBUTOR
user: login: kaito-pr-agent[bot]
created_at: 2025-04-14T08:07:19Z
body: ## PR Code Suggestions ✨

<!-- 1f18716 -->

Explore these optional code suggestions:

<table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=4>General</td>
<td>



<details><summary>Reset <code>useAdapterStrength</code> before validation</summary>

___


**Ensure that `useAdapterStrength` is reset before each validation call.**

[api/v1alpha1/workspace_validation.go [444-448]](https://github.com/kaito-project/kaito/pull/1009/files#diff-5aa8d95205b91001e903b72a7a9dfb992d1acbeae62204197db42d7c684d811eR444-R448)

```diff
 useAdapterStrength := false
 for _, adapter := range i.Adapters {
     if adapter.Strength != nil {
         useAdapterStrength = true
         break
     }
 }
+err := modelPreset.GetInferenceParameters().Validate(model.RuntimeContext{
+    RuntimeName: runtime,
+    RuntimeCapabilityRequest: model.RuntimeCapabilityRequest{
+        InferenceAdapter: len(i.Adapters) > 0,
+        AdapterStrength:  useAdapterStrength,
+    },
+})
```
<details><summary>Suggestion importance[1-10]: 6</summary>

__

Why: Ensuring `useAdapterStrength` is reset before each validation call improves the clarity and correctness of the code.


</details></details></td><td align=center>Low

</td></tr><tr><td>



<details><summary>Refactor config handling logic</summary>

___


**Simplify the conditional logic for handling the inference configuration.**

[api/v1alpha1/workspace_validation.go [478-496]](https://github.com/kaito-project/kaito/pull/1009/files#diff-5aa8d95205b91001e903b72a7a9dfb992d1acbeae62204197db42d7c684d811eR478-R496)

```diff
+var cmName, cmNS string
+var err error
 if i.Config == "" {
-    klog.InfoS("Inference config not specified. Using default:", DefaultInferenceConfigTemplate)
-    releaseNamespace, err := utils.GetReleaseNamespace()
+    klog.Infof("Inference config not specified. Using default: %q", DefaultInferenceConfigTemplate)
+    cmName = DefaultInferenceConfigTemplate
+    cmNS, err = utils.GetReleaseNamespace()
     if err != nil {
         errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to determine release namespace: %v", err), "namespace"))
-    }
-    if err := i.validateConfigMap(ctx, releaseNamespace, DefaultInferenceConfigTemplate, instanceType); err != nil {
-        errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
+        return
     }
 } else {
-    if err := i.validateConfigMap(ctx, namespace, i.Config, instanceType); err != nil {
-        errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
-    }
+    cmName = i.Config
+    cmNS = namespace
+}
+if err := i.validateConfigMap(ctx, cmNS, cmName, instanceType); err != nil {
+    errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("Failed to evaluate validateConfigMap: %v", err), "Config"))
 }
```
<details><summary>Suggestion importance[1-10]: 6</summary>

__

Why: Simplifying the conditional logic for handling the inference configuration makes the code easier to read and maintain.


</details></details></td><td align=center>Low

</td></tr><tr><td>



<details><summary>Add vLLM-specific test cases</summary>

___


**Add a test case for validating the presence of required fields when using vLLM.**

[api/v1alpha1/workspace_validation_test.go [670]](https://github.com/kaito-project/kaito/pull/1009/files#diff-40361cf72c705b9ed5e31c558e6ef9fa52e32b75118ae5d14d875513614fd686R670-R670)

```diff
 {
     name: "Valid with adapters/vllm",
     inferenceSpec: func() *InferenceSpec {
         spec := &InferenceSpec{
             Preset: &PresetSpec{
                 PresetMeta: PresetMeta{
                     Name:       ModelName("test-validation"),
                     AccessMode: ModelImageAccessModePublic,
                 },
             },
         }
         for i := 1; i <= 2; i++ {
             spec.Adapters = append(spec.Adapters, AdapterSpec{
                 Source: &DataSource{
                     Name:  fmt.Sprintf("Adapter-%d", i),
                     Image: fmt.Sprintf("fake.kaito.com/kaito-image:0.0.%d", i),
                 },
             })
         }
         return spec
     }(),
+    runtimeName: model.RuntimeNameVLLM,
 },
```
<details><summary>Suggestion importance[1-10]: 5</summary>

__

Why: Adding specific test cases for vLLM can help catch regressions and ensure the implementation works as expected.


</details></details></td><td align=center>Low

</td></tr><tr><td>



<details><summary>Remove unnecessary error message</summary>

___


**Remove redundant error message for unsupported feature.**

[api/v1alpha1/workspace_validation_test.go [676]](https://github.com/kaito-project/kaito/pull/1009/files#diff-40361cf72c705b9ed5e31c558e6ef9fa52e32b75118ae5d14d875513614fd686R676-R676)

```diff
 {
     name: "Adapters with strength/vllm",
     inferenceSpec: func() *InferenceSpec {
         spec := &InferenceSpec{
             Preset: &PresetSpec{
                 PresetMeta: PresetMeta{
                     Name:       ModelName("test-validation"),
                     AccessMode: ModelImageAccessModePublic,
                 },
             },
         }
         for i := 1; i <= 2; i++ {
             spec.Adapters = append(spec.Adapters, AdapterSpec{
                 Source: &DataSource{
                     Name:  fmt.Sprintf("Adapter-%d", i),
                     Image: fmt.Sprintf("fake.kaito.com/kaito-image:0.0.%d", i),
                 },
                 Strength: &ValidStrength,
             })
         }
         return spec
     }(),
-    errContent: "vLLM does not support adapter strength",
     expectErrs: true,
 },
```
<details><summary>Suggestion importance[1-10]: 3</summary>

__

Why: Removing redundant error messages for unsupported features improves the overall readability and reduces noise in the test suite.


</details></details></td><td align=center>Low

</td></tr></tr></tbody></table>


author_association: CONTRIBUTOR
user: login: zhuangqh
created_at: 2025-04-14T08:18:31Z
body: > ## PR Reviewer Guide 🔍
> Here are some key observations to aid the review process:
> 
> **🎫 Ticket compliance analysis 🔶**
> 
> **[998](https://github.com/kaito-project/kaito/issues/998) - Partially compliant**
> 
> Compliant requirements:
> 
> * Add validation check strength is not currently supported for vLLM adapter loading
> 
> Non-compliant requirements:
> 
> * None
> 
> Requires further human verification:
> 
> * None
> 
>  **Estimated effort to review**: 3 🔵🔵🔵⚪⚪
> 🧪 **PR contains tests**
> 🔒 **No security concerns identified**
> ⚡ **Recommended focus areas for review**
> 
> [**Potential Bug**](https://github.com/kaito-project/kaito/pull/1009/files#diff-5aa8d95205b91001e903b72a7a9dfb992d1acbeae62204197db42d7c684d811eR185-R186)
> The new code adds a check for `AdapterStrength` but does not handle it properly in the `buildVLLMInferenceCommand` function. This could lead to incorrect behavior when `AdapterStrength` is provided.
> 
> [**Potential Bug**](https://github.com/kaito-project/kaito/pull/1009/files#diff-04a7e55519ccf0b2fdd00eb3ef35c6d8435e39c7e75331a2995fee3936b0c8a0R205-R206)
> The new code adds a check for `AdapterStrength` but does not handle it properly in the `Validate` function. This could lead to incorrect behavior when `AdapterStrength` is provided.

Thank you for pointing this out. I confirm that for now, `AdapterStrength` won't be used in `buildVLLMInferenceCommand`.
author_association: COLLABORATOR
user: login: zhuangqh
created_at: 2025-04-15T00:17:44Z
body: > I was thinking we just have a small check: if user specifies an adapter strength verify the annotation being used is transformers. Otherwise reject - what am I missing

It's the same logic I think. I want to decouple this logic from the apis package by my best effort.
author_association: COLLABORATOR
user: login: ishaansehgal99
created_at: 2025-04-15T01:02:35Z
body: /ask improve this code - if it can be
author_association: CONTRIBUTOR
user: login: kaito-pr-agent[bot]
created_at: 2025-04-15T01:04:43Z
body: ### **Ask**❓
improve this code - if it can be

### **Answer:**
Sure, I'd be happy to help improve the code! Let's go through the changes and suggest some improvements.

### `api/v1alpha1/workspace_validation.go`

#### Changes:
1. **Adding `useAdapterStrength` flag**:
   ```go
   useAdapterStrength := false
   for _, adapter := range i.Adapters {
       if adapter.Strength != nil {
           useAdapterStrength = true
           break
       }
   }
   ```

2. **Updating `RuntimeContext`**:
   ```go
   RuntimeContextExtraArguments: model.RuntimeContextExtraArguments{
       AdaptersEnabled:        len(i.Adapters) > 0,
       AdapterStrengthEnabled: useAdapterStrength,
   },
   ```

3. **Skipping config validation for HF runtime**:
   ```go
   if runtime == model.RuntimeNameHuggingfaceTransformers {
       // Skip config validation
   }
   ```

#### Improvements:
1. **Use `any` instead of `bool` for `AdapterStrengthEnabled`**:
   Instead of using a boolean to indicate whether adapter strength is enabled, we can use an `any` type to allow for more flexibility. This way, we can store additional information if needed in the future.

   ```go
   type RuntimeContextExtraArguments struct {
       AdaptersEnabled        bool
       AdapterStrengthEnabled any
   }
   ```

2. **Refactor `validateCreate` method**:
   The `validateCreate` method is quite long and complex. We can refactor it into smaller methods to improve readability and maintainability.

   ```go
   func (i *InferenceSpec) validateCreate(ctx context.Context, namespace string, instanceType string, runtime model.RuntimeName) (*validationErrorList, error) {
       var errs validationErrorList
       if err := i.validateAdapters(ctx, namespace, instanceType, runtime); err != nil {
           errs = errs.Also(err)
       }
       if err := i.validateConfig(ctx, namespace, instanceType, runtime); err != nil {
           errs = errs.Also(err)
       }
       return errs, nil
   }

   func (i *InferenceSpec) validateAdapters(ctx context.Context, namespace string, instanceType string, runtime model.RuntimeName) error {
       useAdapterStrength := false
       for _, adapter := range i.Adapters {
           if adapter.Strength != nil {
               useAdapterStrength = true
               break
           }
       }
       return modelPreset.GetInferenceParameters().Validate(model.RuntimeContext{
           RuntimeName: runtime,
           RuntimeContextExtraArguments: model.RuntimeContextExtraArguments{
               AdaptersEnabled:        len(i.Adapters) > 0,
               AdapterStrengthEnabled: useAdapterStrength,
           },
       })
   }

   func (i *InferenceSpec) validateConfig(ctx context.Context, namespace string, instanceType string, runtime model.RuntimeName) error {
       if runtime == model.RuntimeNameHuggingfaceTransformers {
           return nil
       }
       // Existing validation logic
   }
   ```

### `api/v1alpha1/workspace_validation_test.go`

#### Changes:
1. **Adding new test cases**:
   ```go
   {
       name: "Valid with adapters/vllm",
       inferenceSpec: func() *InferenceSpec {
           // ...
       }(),
   },
   {
       name: "Valid with adapters/transformers",
       inferenceSpec: func() *InferenceSpec {
           // ...
       }(),
       runtimeName: model.RuntimeNameHuggingfaceTransformers,
   },
   {
       name: "Adapters with strength/vllm",
       inferenceSpec: func() *InferenceSpec {
           // ...
       }(),
       errContent: "vLLM does not support adapter strength",
       expectErrs: true,
   },
   ```

#### Improvements:
1. **Add comments for each test case**:
   Adding comments to explain what each test case is testing will make it easier for others to understand the purpose of each test.

   ```go
   {
       name: "Valid with adapters/vllm",
       inferenceSpec: func() *InferenceSpec {
           // ...
       }(),
       // Explanation of the test case
   },
   ```

2. **Refactor test setup**:
   Extract common setup logic into helper functions to reduce code duplication.

   ```go
   func createTestInferenceSpec(runtimeName model.RuntimeName) *InferenceSpec {
       // Common setup logic
   }

   func TestInferenceSpecValidateCreate(t *testing.T) {
       tests := []struct {
           name          string
           inferenceSpec *InferenceSpec
           runtimeName   model.RuntimeName
           errContent    string
           expectErrs    bool
       }{
           {
               name: "Valid with adapters/vllm",
               inferenceSpec: createTestInferenceSpec(model.RuntimeNameVLLM),
           },
           {
               name: "Valid with adapters/transformers",
               inferenceSpec: createTestInferenceSpec(model.RuntimeNameHuggingfaceTransformers),
               runtimeName: model.RuntimeNameHuggingfaceTransformers,
           },
           {
               name: "Adapters with strength/vllm",
               inferenceSpec: createTestInferenceSpec(model.RuntimeNameVLLM),
               errContent: "vLLM does not support adapter strength",
               expectErrs: true,
           },
           // Other test cases
       }

       for _, tc := range tests {
           t.Run(tc.name, func(t *testing.T) {
               // Test implementation
           })
       }
   }
   ```

### `pkg/model/interface.go`

#### Changes:
1. **Adding `RuntimeContextExtraArguments`**:
   ```go
   type RuntimeContextExtraArguments struct {
       AdaptersEnabled        bool
       AdapterStrengthEnabled any
   }
   ```

2. **Updating `Validate` method**:
   ```go
   func (p *PresetParam) Validate(rc RuntimeContext) error {
       var errs []string
       switch rc.RuntimeName {
       case RuntimeNameVLLM:
           if rc.AdaptersEnabled && p.VLLM.DisallowLoRA {
               errs = append(errs, fmt.Sprintf("vLLM does not support LoRA adapters for this model: %s", p.VLLM.ModelName))
           }
           if rc.AdapterStrengthEnabled != nil {
               errs = append(errs, "vLLM does not support adapter strength")
           }
       }
       if len(errs) > 0 {
           return fmt.Errorf("%s", strings.Join(errs, "; "))
       }
       return nil
   }
   ```

#### Improvements:
1. **Use `error` instead of `[]string` for `errs`**:
   Instead of storing errors in a slice of strings, we can directly return them as an error. This makes the error handling more straightforward.

   ```go
   func (p *PresetParam) Validate(rc RuntimeContext) error {
       switch rc.RuntimeName {
       case RuntimeNameVLLM:
           if rc.AdaptersEnabled && p.VLLM.DisallowLoRA {
               return fmt.Errorf("vLLM does not support LoRA adapters for this model: %s", p.VLLM.ModelName)
           }
           if rc.AdapterStrengthEnabled != nil {
               return fmt.Errorf("vLLM does not support adapter strength")
           }
       }
       return nil
   }
   ```

2. **Extract common validation logic**:
   Extract common validation logic into separate functions to improve readability and maintainability.

   ```go
   func validateAdapters(rc RuntimeContext, p *PresetParam) error {
       if rc.AdaptersEnabled && p.VLLM.DisallowLoRA {
           return fmt.Errorf("vLLM does not support LoRA adapters for this model: %s", p.VLLM.ModelName)
       }
       if rc.AdapterStrengthEnabled != nil {
           return fmt.Errorf("vLLM does not support adapter strength")
       }
       return nil
   }

   func (p *PresetParam) Validate(rc RuntimeContext) error {
       switch rc.RuntimeName {
       case RuntimeNameVLLM:
           return validateAdapters(rc, p)
       }
       return nil
   }
   ```

### `pkg/workspace/inference/preset-inferences.go`

#### Changes:
1. **Updating `CreatePresetInference` method**:
   ```go
   func CreatePresetInference(ctx context.Context, workspaceObj *v1beta1.Workspace, volumeMounts []corev1.VolumeMount) ([]corev1.Container, []corev1.EnvVar, []corev1.Volume) {
       commands := inferenceParam.GetInferenceCommand(pkgmodel.RuntimeContext{
           RuntimeName:  v1beta1.GetWorkspaceRuntimeName(workspaceObj),
           GPUConfig:    gpuConfig,
           ConfigVolume: &cmVolumeMount,
           SKUNumGPUs:   skuNumGPUs,
           RuntimeContextExtraArguments: pkgmodel.RuntimeContextExtraArguments{
               AdaptersEnabled: len(workspaceObj.Inference.Adapters) > 0,
           },
       })
       // Existing logic
   }
   ```

#### Improvements:
1. **Extract common logic**:
   Extract common logic into separate functions to improve readability and maintainability.

   ```go
   func getRuntimeContext(workspaceObj *v1beta1.Workspace, gpuConfig *sku.GPUConfig, cmVolumeMount *corev1.VolumeMount, skuNumGPUs int) pkgmodel.RuntimeContext {
       return pkgmodel.RuntimeContext{
           RuntimeName:  v1beta1.GetWorkspaceRuntimeName(workspaceObj),
           GPUConfig:    gpuConfig,
           ConfigVolume: cmVolumeMount,
           SKUNumGPUs:   skuNumGPUs,
           RuntimeContextExtraArguments: pkgmodel.RuntimeContextExtraArguments{
               AdaptersEnabled: len(workspaceObj.Inference.Adapters) > 0,
           },
       }
   }

   func CreatePresetInference(ctx context.Context, workspaceObj *v1beta1.Workspace, volumeMounts []corev1.VolumeMount) ([]corev1.Container, []corev1.EnvVar, []corev1.Volume) {
       rc := getRuntimeContext(workspaceObj, gpuConfig, cmVolumeMount, skuNumGPUs)
       commands := inferenceParam.GetInferenceCommand(rc)
       // Existing logic
   }
   ```

### `pkg/workspace/manifests/manifests.go`

#### Changes:
1. **Updating `GeneratePullerContainers` method**:
   ```go
   func GeneratePullerContainers(wObj *kaitov1beta1.Workspace, volumeMounts []corev1.VolumeMount) ([]corev1.Container, []corev1.EnvVar, []corev1.Volume) {
       initContainers := make([]corev1.Container, 0, len(wObj.Inference.Adapters))
       var envVars []corev1.EnvVar
       volumes := make([]corev1.Volume, 0, len(wObj.Inference.Adapters))

       for _, adapter := range wObj.Inference.Adapters {
           if adapter.Strength != nil {
               envVar := corev1.EnvVar{
                   Name:  adapter.Source.Name,
                   Value: *adapter.Strength,
               }
               envVars = append(envVars, envVar)
           }
           // Existing logic
       }
       // Existing logic
   }
   ```

#### Improvements:
1. **Extract common logic**:
   Extract common logic into separate functions to improve readability and maintainability.

   ```go
   func getAdapterEnvVars(adapters []kaitov1beta1.AdapterSpec) []corev1.EnvVar {
       var envVars []corev1.EnvVar
       for _, adapter := range adapters {
           if adapter.Strength != nil {
               envVar := corev1.EnvVar{
                   Name:  adapter.Source.Name,
                   Value: *adapter.Strength,
               }
               envVars = append(envVars, envVar)
           }
       }
       return envVars
   }

   func GeneratePullerContainers(wObj *kaitov1beta1.Workspace, volumeMounts []corev1.VolumeMount) ([]corev1.Container, []corev1.EnvVar, []corev1.Volume) {
       initContainers := make([]corev1.Container, 0, len(wObj.Inference.Adapters))
       envVars := getAdapterEnvVars(wObj.Inference.Adapters)
       volumes := make([]corev1.Volume, 0, len(wObj.Inference.Adapters))

       for _, adapter := range wObj.Inference.Adapters {
           // Existing logic
       }
       // Existing logic
   }
   ```

### `test/e2e/inference_with_adapters_test.go`

#### Changes:
1. **Updating `validateInitContainers` function**:
   ```go
   func validateInitContainers(workspaceObj *kaitov1beta1.Workspace, expectedInitContainers []corev1.Container) {
       By("Checking the InitContainers", func() {
           Eventually(func() bool {
               // Existing logic
           }).Should(BeTrue())
       })
   }
   ```

#### Improvements:
1. **Extract common logic**:
   Extract common logic into separate functions to improve readability and maintainability.

   ```go
   func getExpectedInitContainers(validAdapters []kaitov1beta1.AdapterSpec) []corev1.Container {
       var expectedInitContainers []corev1.Container
       for _, adapter := range validAdapters {
           expectedInitContainers = append(expectedInitContainers, corev1.Container{
               Name:  baseInitContainer.Name + "-" + adapter.Source.Name,
               Image: baseInitContainer.Image,
           })
       }
       return expectedInitContainers
   }

   func validateInitContainers(workspaceObj *kaitov1beta1.Workspace, expectedInitContainers []corev1.Container) {
       By("Checking the InitContainers", func() {
           Eventually(func() bool {
               // Existing logic
           }).Should(BeTrue())
       })
   }

   func TestInferenceSpecValidateCreate(t *testing.T) {
       tests := []struct {
           name          string
           inferenceSpec *InferenceSpec
           resourceSpec  *ResourceSpec
           runtimeName   model.RuntimeName
           errContent    string
           expectErrs    bool
       }{
           {
               name: "Valid with adapters/vllm",
               inferenceSpec: func() *InferenceSpec {
                   // ...
               }(),
               runtimeName: model.RuntimeNameVLLM,
           },
           {
               name: "Valid with adapters/transformers",
               inferenceSpec: func() *InferenceSpec {
                   // ...
               }(),
               runtimeName: model.RuntimeNameHuggingfaceTransformers,
           },
           {
               name: "Adapters with strength/vllm",
               inferenceSpec: func() *InferenceSpec {
                   // ...
               }(),
               errContent: "vLLM does not support adapter strength",
               expectErrs: true,
           },
           // Other test cases
       }

       for _, tc := range tests {
           t.Run(tc.name, func(t *testing.T) {
               // Test implementation
           })
       }
   }
   ```

### `test/e2e/preset_vllm_test.go`

#### Changes:
1. **Updating `It` block**:
   ```go
   It("should create a phi4 workspace with adapter successfully", utils.GinkgoLabelFastCheck, func() {
       numOfNode := 1
       workspaceObj := createPhi4WorkspaceWithAdapterAndVLLM(numOfNode, phi4Adapter)

       defer cleanupResources(workspaceObj)
       time.Sleep(30 * time.Second)
   })
   ```

#### Improvements:
1. **Extract common logic**:
   Extract common logic into separate functions to improve readability and maintainability.

   ```go
   func createPhi4WorkspaceWithAdapterAndVLLM(numOfNode int, phi4Adapter []kaitov1beta1.AdapterSpec) *kaitov1beta1.Workspace {
       // Existing logic
   }

   It("should create a phi4 workspace with adapter successfully", utils.GinkgoLabelFastCheck, func() {
       numOfNode := 1
       workspaceObj := createPhi4WorkspaceWithAdapterAndVLLM(numOfNode, phi4Adapter)

       defer cleanupResources(workspaceObj)
       time.Sleep(30 * time.Second)
   }
   ```

### `test/e2e/webhook_test.go`

#### Changes:
1. **Updating `It` blocks**:
   ```go
   It("should validate the workspace inference adapters at creation", func() {
       By("Creating a vllm workspace with adapter strength", func() {
           // Existing logic
       })

       By("Creating a vllm workspace without adapter strength", func() {
           // Existing logic
       })

       By("Creating a transformers workspace with adapter strength", func() {
           // Existing logic
       })
   })
   ```

#### Improvements:
1. **Extract common logic**:
   Extract common logic into separate functions to improve readability and maintainability.

   ```go
   func generateInferenceWorkspaceManifestWithVLLM(name, namespace, config, numOfNode, instanceType string, labelSelector *metav1.LabelSelector, annotations map[string]string, preset Preset, accessMode kaitov1beta1.ModelImageAccessMode, adapters []kaitov1beta1.AdapterSpec) *kaitov1beta1.Workspace {
       // Existing logic
   }

   It("should validate the workspace inference adapters at creation", func() {
       By("Creating a vllm workspace with adapter strength", func() {
           workspaceObj := generateInferenceWorkspaceManifestWithVLLM(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC6s_v3",
               &metav1.LabelSelector{
                   MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
               }, nil, PresetFalcon7BModel, kaitov1beta1.ModelImageAccessModePublic, testAdapters1)

           // Create workspace
           Eventually(func() error {
               return utils.TestingCluster.KubeClient.Create(ctx, workspaceObj, &client.CreateOptions{})
           }, utils.PollTimeout, utils.PollInterval).
               Should(HaveOccurred(), "Failed to create workspace %s", workspaceObj.Name)
       })

       By("Creating a vllm workspace without adapter strength", func() {
           workspaceObj := generateInferenceWorkspaceManifestWithVLLM(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC6s_v3",
               &metav1.LabelSelector{
                   MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
               }, nil, PresetPhi4MiniModel, kaitov1beta1.ModelImageAccessModePublic, phi4Adapter)

           // Create workspace
           Eventually(func() error {
               return utils.TestingCluster.KubeClient.Create(ctx, workspaceObj, &client.CreateOptions{})
           }, utils.PollTimeout, utils.PollInterval).
               Should(Succeed(), "Failed to create workspace %s", workspaceObj.Name)
           // delete workspace
           Eventually(func() error {
               return utils.TestingCluster.KubeClient.Delete(ctx, workspaceObj, &client.DeleteOptions{})
           }, utils.PollTimeout, utils.PollInterval).Should(Succeed(), "Failed to delete workspace")
       })

       By("Creating a transformers workspace with adapter strength", func() {
           workspaceObj := generateInferenceWorkspaceManifest(fmt.Sprint("webhook-", rand.Intn(1000)), namespaceName, "", 1, "Standard_NC6s_v3",
               &metav1.LabelSelector{
                   MatchLabels: map[string]string{"kaito-workspace": "webhook-e2e-test"},
               }, nil, PresetFalcon7BModel, kaitov1beta1.ModelImageAccessModePublic, nil, nil, testAdapters1)

           // Create workspace
           Eventually(func() error {
               return utils.TestingCluster.KubeClient.Create(ctx, workspaceObj, &client.CreateOptions{})
           }, utils.PollTimeout, utils.PollInterval).
               Should(Succeed(), "Failed to create workspace %s", workspaceObj.Name)

           // delete workspace
           Eventually(func() error {
               return utils.TestingCluster.KubeClient.Delete(ctx, workspaceObj, &client.DeleteOptions{})
           }, utils.PollTimeout, utils.PollInterval).Should(Succeed(), "Failed to delete workspace")
       })
   })
   ```

These improvements should make the code more readable, maintainable, and efficient. If you have any further questions or need additional assistance, feel free to ask!


author_association: CONTRIBUTOR