Skip to content

Commit bd5158d

Browse files
committed
Clean up LLMEndpointType
Clean up LLMEndpointType providers by dropping providers unsupported by OGX and lightspeed-core/lightspeed-stack: 1) BAM is no longer supported by IBM and was replaced by watsonx.ai [1]. 2) fake_providers is an artifact that persisted through the migration from openshift/lightspeed-operator. This provider is supported only by openshift/lightspeed-service and not lightspeed-core/lightspeed-stack [2]. Additionally, add unit tests for buildLlamaStackModels and buildLlamaStackInferenceProviders to ground the configuration. Note: this commit aims to align the providers configuration as closely as possible with the recommended configuration in lightspeed-core/lightspeed-stack [3]. It was not tested with the actual providers. Additional testing is likely required and may lead to minor tweaks in the providers code. Also, address lint issues discovered by the pre-commit checks (replace repeating strings with constants). [1] openshift/lightspeed-service#2827 [2] https://github.com/openshift/lightspeed-service/blob/b8919bccabf3b4e6ab8877e73c7b505974a6a723/docs/ai/providers.md [3] https://github.com/lightspeed-core/lightspeed-stack/tree/main/examples
1 parent 694d996 commit bd5158d

8 files changed

Lines changed: 178 additions & 15 deletions

api/v1beta1/openstacklightspeed_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ type OpenStackLightspeedCore struct {
155155
LLMEndpoint string `json:"llmEndpoint"`
156156

157157
// +kubebuilder:validation:Required
158-
// +kubebuilder:validation:Enum=azure_openai;bam;openai;watsonx;rhoai_vllm;rhelai_vllm;fake_provider;gemini
158+
// +kubebuilder:validation:Enum=azure_openai;openai;watsonx;rhoai_vllm;rhelai_vllm;gemini
159159
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Provider Type"
160160
// Type of the provider serving the LLM
161161
LLMEndpointType string `json:"llmEndpointType"`

bundle/manifests/lightspeed.openstack.org_openstacklightspeeds.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,10 @@ spec:
103103
description: Type of the provider serving the LLM
104104
enum:
105105
- azure_openai
106-
- bam
107106
- openai
108107
- watsonx
109108
- rhoai_vllm
110109
- rhelai_vllm
111-
- fake_provider
112110
- gemini
113111
type: string
114112
llmProjectID:

config/crd/bases/lightspeed.openstack.org_openstacklightspeeds.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,10 @@ spec:
103103
description: Type of the provider serving the LLM
104104
enum:
105105
- azure_openai
106-
- bam
107106
- openai
108107
- watsonx
109108
- rhoai_vllm
110109
- rhelai_vllm
111-
- fake_provider
112110
- gemini
113111
type: string
114112
llmProjectID:

internal/controller/constants.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,14 @@ const (
109109
ConsoleProxyAlias = "ols"
110110
ConsoleUINetworkPolicyName = "lightspeed-console-plugin"
111111

112-
// Azure
113-
AzureOpenAIType = "azure_openai"
112+
// Provider name constants representing valid values for
113+
// OpenStackLightpseed.Spec.LLMEndpointType (providers available to users)
114+
RHELAIVLLMProviderName = "rhelai_vllm"
115+
RHOAIVLLMProviderName = "rhoai_vllm"
116+
GeminiProviderName = "gemini"
117+
AzureOpenAIProviderName = "azure_openai"
118+
OpenAIProviderName = "openai"
119+
WatsonXProviderName = "watsonx"
114120

115121
// EnvVarSuffixAPIKey is the environment variable suffix for API key credentials
116122
EnvVarSuffixAPIKey = "_API_KEY"

internal/controller/lcore_deployment.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ func buildLlamaStackEnvVars(h *common_helper.Helper, ctx context.Context, instan
458458

459459
envVarName := providerNameToEnvVarName(provider.Name)
460460

461-
if provider.Type == AzureOpenAIType {
461+
if provider.Type == AzureOpenAIProviderName {
462462
// Azure supports both API key and client credentials authentication.
463463
// Read the secret to determine which fields are present.
464464
secret := &corev1.Secret{}
@@ -535,7 +535,7 @@ func buildLlamaStackEnvVars(h *common_helper.Helper, ctx context.Context, instan
535535

536536
// For vLLM providers, also set the URL environment variable
537537
// The vLLM adapter checks for VLLM_URL as a fallback if URL is not in config
538-
if provider.Type == "rhoai_vllm" || provider.Type == "rhelai_vllm" {
538+
if provider.Type == RHOAIVLLMProviderName || provider.Type == RHELAIVLLMProviderName {
539539
if provider.URL != "" {
540540
envVars = append(envVars, corev1.EnvVar{
541541
Name: "VLLM_URL",

internal/controller/llama_stack_config.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,17 @@ func buildLlamaStackInferenceProviders(_ *common_helper.Helper, _ context.Contex
122122

123123
// Map provider types to Llama Stack provider types
124124
switch provider.Type {
125-
case "openai", "gemini", "rhoai_vllm", "rhelai_vllm":
125+
case OpenAIProviderName, GeminiProviderName, RHOAIVLLMProviderName, RHELAIVLLMProviderName:
126126
config := map[string]interface{}{}
127127
// Determine the appropriate Llama Stack provider type:
128128
// - OpenAI uses remote::openai
129129
// - vLLM uses remote::vllm
130130
var apiKeyField string
131131
switch provider.Type {
132-
case "openai":
132+
case OpenAIProviderName:
133133
providerConfig["provider_type"] = "remote::openai"
134134
apiKeyField = "api_key"
135-
case "gemini":
135+
case GeminiProviderName:
136136
providerConfig["provider_type"] = "remote::gemini"
137137
apiKeyField = "api_key"
138138
default:
@@ -149,7 +149,7 @@ func buildLlamaStackInferenceProviders(_ *common_helper.Helper, _ context.Contex
149149

150150
providerConfig["config"] = config
151151

152-
case "azure_openai":
152+
case AzureOpenAIProviderName:
153153
providerConfig["provider_type"] = "remote::azure"
154154
config := map[string]interface{}{}
155155

@@ -174,7 +174,17 @@ func buildLlamaStackInferenceProviders(_ *common_helper.Helper, _ context.Contex
174174
}
175175
providerConfig["config"] = config
176176

177-
case "watsonx", "bam":
177+
case WatsonXProviderName:
178+
providerConfig["provider_type"] = "remote::watsonx"
179+
180+
config := map[string]interface{}{}
181+
config["base_url"] = provider.URL
182+
config["project_id"] = provider.WatsonProjectID
183+
config["api_key"] = fmt.Sprintf("${env.%s_API_KEY:=}", envVarName)
184+
185+
providerConfig["config"] = config
186+
187+
case "bam":
178188
// These providers are not supported by Llama Stack
179189
// They are handled directly by lightspeed-stack (LCS), not Llama Stack
180190
return nil, fmt.Errorf("provider type '%s' (provider '%s') is not currently supported by Llama Stack. Supported types: openai, gemini, azure_openai, rhoai_vllm, rhelai_vllm", provider.Type, provider.Name)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package controller
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
)
13+
14+
func expectSentenceTransformersProvider(providers []interface{}) {
15+
sentenceTransformers := providers[0].(map[string]interface{})
16+
Expect(sentenceTransformers["provider_id"]).To(Equal("sentence-transformers"))
17+
Expect(sentenceTransformers["provider_type"]).To(Equal("inline::sentence-transformers"))
18+
}
19+
20+
func getOpenStackLightspeedProvidersInstance(provider string) *apiv1beta1.OpenStackLightspeed {
21+
instance := &apiv1beta1.OpenStackLightspeed{
22+
ObjectMeta: metav1.ObjectMeta{
23+
Name: "openstack-lightspeed",
24+
Namespace: "openstack-lightspeed",
25+
},
26+
}
27+
28+
switch provider {
29+
case OpenAIProviderName:
30+
instance.Spec.LLMEndpointType = OpenAIProviderName
31+
instance.Spec.LLMEndpoint = "https://api.openai.com/v1"
32+
instance.Spec.ModelName = "gpt-4o"
33+
return instance
34+
case GeminiProviderName:
35+
instance.Spec.LLMEndpointType = GeminiProviderName
36+
instance.Spec.ModelName = "gemini-2.0-flash"
37+
return instance
38+
case RHOAIVLLMProviderName:
39+
instance.Spec.LLMEndpointType = RHOAIVLLMProviderName
40+
instance.Spec.LLMEndpoint = "https://vllm.example.com/v1"
41+
instance.Spec.ModelName = "meta-llama/Llama-3.1-70B-Instruct"
42+
return instance
43+
case RHELAIVLLMProviderName:
44+
instance.Spec.LLMEndpointType = RHELAIVLLMProviderName
45+
instance.Spec.LLMEndpoint = "https://rhelai-vllm.example.com/v1"
46+
instance.Spec.ModelName = "meta-llama/Llama-3.1-70B-Instruct"
47+
return instance
48+
case AzureOpenAIProviderName:
49+
instance.Spec.LLMEndpointType = AzureOpenAIProviderName
50+
instance.Spec.LLMEndpoint = "https://my-resource.openai.azure.com"
51+
instance.Spec.LLMDeploymentName = "gpt-4o-deployment"
52+
instance.Spec.LLMAPIVersion = "2024-02-01"
53+
instance.Spec.ModelName = "gpt-4o"
54+
return instance
55+
case WatsonXProviderName:
56+
instance.Spec.LLMEndpointType = WatsonXProviderName
57+
instance.Spec.LLMEndpoint = "https://watsonx.example.com"
58+
instance.Spec.LLMProjectID = "test-project-id"
59+
instance.Spec.ModelName = "ibm/granite-13b-chat-v2"
60+
return instance
61+
default:
62+
Fail(fmt.Sprintf("Unknown provider %s", provider))
63+
}
64+
65+
return nil
66+
}
67+
68+
func checkModelCommonConfig(modelConfig map[string]interface{}, instance *apiv1beta1.OpenStackLightspeed) {
69+
Expect(modelConfig["model_id"]).To(Equal(instance.Spec.ModelName))
70+
Expect(modelConfig["model_type"]).To(Equal("llm"))
71+
Expect(modelConfig["provider_id"]).To(Equal(OpenStackLightspeedDefaultProvider))
72+
Expect(modelConfig["provider_model_id"]).To(Equal(instance.Spec.ModelName))
73+
Expect(modelConfig).NotTo(HaveKey("metadata"))
74+
}
75+
76+
var _ = Describe("Llama Stack config", func() {
77+
Describe("buildLlamaStackInferenceProviders", func() {
78+
DescribeTable("should return correct inference providers config",
79+
func(provider, providerType string, checkConfig func(map[string]interface{}, *apiv1beta1.OpenStackLightspeed)) {
80+
instance := getOpenStackLightspeedProvidersInstance(provider)
81+
inferenceProvidersConfig, err := buildLlamaStackInferenceProviders(nil, context.Background(), instance)
82+
83+
Expect(err).NotTo(HaveOccurred())
84+
Expect(inferenceProvidersConfig).To(HaveLen(2))
85+
86+
expectSentenceTransformersProvider(inferenceProvidersConfig)
87+
88+
inferenceProvider := inferenceProvidersConfig[1].(map[string]interface{})
89+
Expect(inferenceProvider["provider_id"]).To(Equal(OpenStackLightspeedDefaultProvider))
90+
Expect(inferenceProvider["provider_type"]).To(Equal(providerType))
91+
92+
checkConfig(inferenceProvider["config"].(map[string]interface{}), instance)
93+
},
94+
Entry("for openai", OpenAIProviderName, "remote::openai",
95+
func(config map[string]interface{}, _ *apiv1beta1.OpenStackLightspeed) {
96+
Expect(config["api_key"]).To(Equal("${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY}"))
97+
}),
98+
Entry("for gemini", GeminiProviderName, "remote::gemini",
99+
func(config map[string]interface{}, _ *apiv1beta1.OpenStackLightspeed) {
100+
Expect(config["api_key"]).To(Equal("${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY}"))
101+
Expect(config).NotTo(HaveKey("base_url"))
102+
}),
103+
Entry("for rhoai_vllm", RHOAIVLLMProviderName, "remote::vllm",
104+
func(config map[string]interface{}, instance *apiv1beta1.OpenStackLightspeed) {
105+
Expect(config["api_token"]).To(Equal("${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY}"))
106+
Expect(config["base_url"]).To(Equal(instance.Spec.LLMEndpoint))
107+
}),
108+
Entry("for rhelai_vllm", RHELAIVLLMProviderName, "remote::vllm",
109+
func(config map[string]interface{}, instance *apiv1beta1.OpenStackLightspeed) {
110+
Expect(config["api_token"]).To(Equal("${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY}"))
111+
Expect(config["base_url"]).To(Equal(instance.Spec.LLMEndpoint))
112+
}),
113+
Entry("for azure_openai", AzureOpenAIProviderName, "remote::azure",
114+
func(config map[string]interface{}, instance *apiv1beta1.OpenStackLightspeed) {
115+
Expect(config["api_key"]).To(Equal("${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY}"))
116+
Expect(config["client_id"]).To(Equal("${env.OPENSTACK_LIGHTSPEED_PROVIDER_CLIENT_ID:=}"))
117+
Expect(config["tenant_id"]).To(Equal("${env.OPENSTACK_LIGHTSPEED_PROVIDER_TENANT_ID:=}"))
118+
Expect(config["client_secret"]).To(Equal("${env.OPENSTACK_LIGHTSPEED_PROVIDER_CLIENT_SECRET:=}"))
119+
Expect(config["api_base"]).To(Equal(instance.Spec.LLMEndpoint))
120+
Expect(config["deployment_name"]).To(Equal(instance.Spec.LLMDeploymentName))
121+
Expect(config["api_version"]).To(Equal(instance.Spec.LLMAPIVersion))
122+
}),
123+
Entry("for watsonx", WatsonXProviderName, "remote::watsonx",
124+
func(config map[string]interface{}, instance *apiv1beta1.OpenStackLightspeed) {
125+
Expect(config["base_url"]).To(Equal(instance.Spec.LLMEndpoint))
126+
Expect(config["project_id"]).To(Equal(instance.Spec.LLMProjectID))
127+
Expect(config["api_key"]).To(Equal("${env.OPENSTACK_LIGHTSPEED_PROVIDER_API_KEY:=}"))
128+
}),
129+
)
130+
})
131+
132+
Describe("buildLlamaStackModels", func() {
133+
DescribeTable("should return correct models config",
134+
func(provider string) {
135+
instance := getOpenStackLightspeedProvidersInstance(provider)
136+
modelsConfig := buildLlamaStackModels(nil, instance)
137+
138+
Expect(modelsConfig).To(HaveLen(1))
139+
140+
modelConfig := modelsConfig[0].(map[string]interface{})
141+
checkModelCommonConfig(modelConfig, instance)
142+
},
143+
Entry("for openai", OpenAIProviderName),
144+
Entry("for gemini", GeminiProviderName),
145+
Entry("for rhoai_vllm", RHOAIVLLMProviderName),
146+
Entry("for rhelai_vllm", RHELAIVLLMProviderName),
147+
Entry("for azure_openai", AzureOpenAIProviderName),
148+
Entry("for watsonx", WatsonXProviderName),
149+
)
150+
})
151+
})

internal/controller/openstacklightspeed_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ var _ = Describe("OpenStackLightspeed Controller", func() {
5454
Spec: apiv1beta1.OpenStackLightspeedSpec{
5555
OpenStackLightspeedCore: apiv1beta1.OpenStackLightspeedCore{
5656
LLMEndpoint: "https://example.com/llm",
57-
LLMEndpointType: "openai",
57+
LLMEndpointType: OpenAIProviderName,
5858
ModelName: "test-model",
5959
},
6060
},

0 commit comments

Comments
 (0)