Skip to content

Commit 3e4f13a

Browse files
Merge pull request #122 from openstack-lightspeed/lpiwowar/providers-cleanup
Cleanup LLMEndpointType
2 parents 4a45e7a + 75c1e1b commit 3e4f13a

10 files changed

Lines changed: 215 additions & 22 deletions

.github/workflows/unit-tests.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Unit Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
unit-tests:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v7
16+
17+
- name: Set up Go
18+
uses: actions/setup-go@v6
19+
with:
20+
go-version-file: "go.mod"
21+
22+
- name: Run tests
23+
run: make test

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ KUTTL ?= $(LOCALBIN)/kubectl-kuttl
246246
## Tool Versions
247247
KUSTOMIZE_VERSION ?= v5.4.2
248248
CONTROLLER_TOOLS_VERSION ?= v0.16.5
249-
ENVTEST_VERSION ?= release-0.18
249+
ENVTEST_VERSION ?= release-0.22
250250
GOLANGCI_LINT_VERSION ?= v2.6.0
251251
KUTTL_VERSION ?= 0.22.0
252252

api/v1beta1/openstacklightspeed_types.go

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

159159
// +kubebuilder:validation:Required
160-
// +kubebuilder:validation:Enum=azure_openai;bam;openai;watsonx;rhoai_vllm;rhelai_vllm;fake_provider;gemini
160+
// +kubebuilder:validation:Enum=azure_openai;openai;watsonx;rhoai_vllm;rhelai_vllm;gemini
161161
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Provider Type"
162162
// Type of the provider serving the LLM
163163
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
@@ -117,8 +117,14 @@ const (
117117
ConsoleProxyAlias = "ols"
118118
ConsoleUINetworkPolicyName = "lightspeed-console-plugin"
119119

120-
// Azure
121-
AzureOpenAIType = "azure_openai"
120+
// Provider name constants representing valid values for
121+
// OpenStackLightpseed.Spec.LLMEndpointType (providers available to users)
122+
RHELAIVLLMProviderName = "rhelai_vllm"
123+
RHOAIVLLMProviderName = "rhoai_vllm"
124+
GeminiProviderName = "gemini"
125+
AzureOpenAIProviderName = "azure_openai"
126+
OpenAIProviderName = "openai"
127+
WatsonXProviderName = "watsonx"
122128

123129
// EnvVarSuffixAPIKey is the environment variable suffix for API key credentials
124130
EnvVarSuffixAPIKey = "_API_KEY"

internal/controller/lcore_deployment.go

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

489489
envVarName := providerNameToEnvVarName(provider.Name)
490490

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

566566
// For vLLM providers, also set the URL environment variable
567567
// The vLLM adapter checks for VLLM_URL as a fallback if URL is not in config
568-
if provider.Type == "rhoai_vllm" || provider.Type == "rhelai_vllm" {
568+
if provider.Type == RHOAIVLLMProviderName || provider.Type == RHELAIVLLMProviderName {
569569
if provider.URL != "" {
570570
envVars = append(envVars, corev1.EnvVar{
571571
Name: "VLLM_URL",

internal/controller/llama_stack_config.go

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

2324
common_helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
2425
apiv1beta1 "github.com/openstack-lightspeed/operator/api/v1beta1"
@@ -122,17 +123,17 @@ func buildLlamaStackInferenceProviders(_ *common_helper.Helper, _ context.Contex
122123

123124
// Map provider types to Llama Stack provider types
124125
switch provider.Type {
125-
case "openai", "gemini", "rhoai_vllm", "rhelai_vllm":
126+
case OpenAIProviderName, GeminiProviderName, RHOAIVLLMProviderName, RHELAIVLLMProviderName:
126127
config := map[string]interface{}{}
127128
// Determine the appropriate Llama Stack provider type:
128129
// - OpenAI uses remote::openai
129130
// - vLLM uses remote::vllm
130131
var apiKeyField string
131132
switch provider.Type {
132-
case "openai":
133+
case OpenAIProviderName:
133134
providerConfig["provider_type"] = "remote::openai"
134135
apiKeyField = "api_key"
135-
case "gemini":
136+
case GeminiProviderName:
136137
providerConfig["provider_type"] = "remote::gemini"
137138
apiKeyField = "api_key"
138139
default:
@@ -149,7 +150,7 @@ func buildLlamaStackInferenceProviders(_ *common_helper.Helper, _ context.Contex
149150

150151
providerConfig["config"] = config
151152

152-
case "azure_openai":
153+
case AzureOpenAIProviderName:
153154
providerConfig["provider_type"] = "remote::azure"
154155
config := map[string]interface{}{}
155156

@@ -170,18 +171,34 @@ func buildLlamaStackInferenceProviders(_ *common_helper.Helper, _ context.Contex
170171
config["api_version"] = provider.APIVersion
171172
}
172173
if provider.URL != "" {
173-
config["api_base"] = provider.URL
174+
config["base_url"] = provider.URL
174175
}
175176
providerConfig["config"] = config
176177

177-
case "watsonx", "bam":
178-
// These providers are not supported by Llama Stack
179-
// They are handled directly by lightspeed-stack (LCS), not Llama Stack
180-
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)
178+
case WatsonXProviderName:
179+
providerConfig["provider_type"] = "remote::watsonx"
180+
181+
config := map[string]interface{}{}
182+
config["base_url"] = provider.URL
183+
config["api_key"] = fmt.Sprintf("${env.%s_API_KEY}", envVarName)
184+
185+
if provider.WatsonProjectID != "" {
186+
config["project_id"] = provider.WatsonProjectID
187+
}
188+
189+
providerConfig["config"] = config
181190

182191
default:
183-
// Unknown provider type
184-
return nil, fmt.Errorf("unknown provider type '%s' (provider '%s'). Supported types: openai, gemini, azure_openai, rhoai_vllm, rhelai_vllm", provider.Type, provider.Name)
192+
supportedProviders := []string{
193+
OpenAIProviderName, GeminiProviderName, RHOAIVLLMProviderName, RHELAIVLLMProviderName,
194+
AzureOpenAIProviderName, WatsonXProviderName,
195+
}
196+
return nil, fmt.Errorf(
197+
"unknown provider type '%s' (provider '%s'). Supported types: %s",
198+
provider.Type,
199+
provider.Name,
200+
strings.Join(supportedProviders, ","),
201+
)
185202
}
186203

187204
providers = append(providers, providerConfig)
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["base_url"]).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)