From fd188d2330521dfe30ed82e1551c4855bcf92ac1 Mon Sep 17 00:00:00 2001 From: Tharanidk Date: Mon, 6 Jul 2026 10:13:20 +0530 Subject: [PATCH 1/2] Add groupId, managedBy, and version fields to LLM provider templates --- .../api/management-openapi.yaml | 20 + .../anthropic-template.yaml | 3 + .../awsbedrock-template.yaml | 3 + .../azureaifoundry-template.yaml | 3 + .../azureopenai-template.yaml | 3 + .../gemini-template.yaml | 3 + .../mistral-template.yaml | 3 + .../openai-template.yaml | 3 + .../pkg/api/management/generated.go | 562 +++++++++--------- .../pkg/models/llm_provider_template.go | 34 ++ .../pkg/models/llm_provider_template_test.go | 50 ++ .../pkg/utils/llm_deployment.go | 18 +- .../pkg/utils/llm_deployment_test.go | 98 +++ 13 files changed, 530 insertions(+), 273 deletions(-) diff --git a/gateway/gateway-controller/api/management-openapi.yaml b/gateway/gateway-controller/api/management-openapi.yaml index f3926b0181..a29a667e01 100644 --- a/gateway/gateway-controller/api/management-openapi.yaml +++ b/gateway/gateway-controller/api/management-openapi.yaml @@ -5566,6 +5566,26 @@ components: minLength: 1 maxLength: 253 example: OpenAI + groupId: + type: string + description: | + Stable family-grouping identifier shared by every version of this + template. Multiple versions with the same groupId but different + handles are surfaced as versions of one template. Defaults to + metadata.name when omitted. + example: openai + managedBy: + type: string + description: | + Origin of the template. Built-in templates use 'wso2'; custom + templates default to 'customer' and may be set to any value. + example: wso2 + version: + type: string + description: | + Template content version (e.g. v1.0). Multiple versions of the same + groupId can coexist; defaults to v1.0 when omitted. + example: v1.0 promptTokens: $ref: '#/components/schemas/ExtractionIdentifier' completionTokens: diff --git a/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml index 5a46cf08b7..6744eb9d83 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/anthropic-template.yaml @@ -22,6 +22,9 @@ metadata: name: anthropic spec: displayName: Anthropic + groupId: anthropic + managedBy: wso2 + version: v1.0 promptTokens: location: payload identifier: $.usage.input_tokens diff --git a/gateway/gateway-controller/default-llm-provider-templates/awsbedrock-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/awsbedrock-template.yaml index 71b8b3d82d..5b8968f9d5 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/awsbedrock-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/awsbedrock-template.yaml @@ -22,6 +22,9 @@ metadata: name: awsbedrock spec: displayName: AWS Bedrock + groupId: awsbedrock + managedBy: wso2 + version: v1.0 promptTokens: location: payload identifier: $.usage.inputTokens diff --git a/gateway/gateway-controller/default-llm-provider-templates/azureaifoundry-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/azureaifoundry-template.yaml index cf132b952c..a40afb1b28 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/azureaifoundry-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/azureaifoundry-template.yaml @@ -22,6 +22,9 @@ metadata: name: azureai-foundry spec: displayName: Azure AI Foundry + groupId: azureai-foundry + managedBy: wso2 + version: v1.0 promptTokens: location: payload identifier: $.usage.prompt_tokens diff --git a/gateway/gateway-controller/default-llm-provider-templates/azureopenai-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/azureopenai-template.yaml index 9784c3d7d6..a3da0b060b 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/azureopenai-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/azureopenai-template.yaml @@ -22,6 +22,9 @@ metadata: name: azure-openai spec: displayName: Azure OpenAI + groupId: azure-openai + managedBy: wso2 + version: v1.0 promptTokens: location: payload identifier: $.usage.prompt_tokens diff --git a/gateway/gateway-controller/default-llm-provider-templates/gemini-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/gemini-template.yaml index 78ba507aae..cafd577ddf 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/gemini-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/gemini-template.yaml @@ -22,6 +22,9 @@ metadata: name: gemini spec: displayName: Gemini + groupId: gemini + managedBy: wso2 + version: v1.0 promptTokens: location: payload identifier: $.usageMetadata.promptTokenCount diff --git a/gateway/gateway-controller/default-llm-provider-templates/mistral-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/mistral-template.yaml index 15703d138e..23f37fdced 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/mistral-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/mistral-template.yaml @@ -22,6 +22,9 @@ metadata: name: mistralai spec: displayName: MistralAI + groupId: mistralai + managedBy: wso2 + version: v1.0 promptTokens: location: payload identifier: $.usage.prompt_tokens diff --git a/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml b/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml index 410797da61..c19ae69749 100644 --- a/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml +++ b/gateway/gateway-controller/default-llm-provider-templates/openai-template.yaml @@ -22,6 +22,9 @@ metadata: name: openai spec: displayName: OpenAI + groupId: openai + managedBy: wso2 + version: v1.0 promptTokens: location: payload identifier: $.usage.prompt_tokens diff --git a/gateway/gateway-controller/pkg/api/management/generated.go b/gateway/gateway-controller/pkg/api/management/generated.go index 5dbc799012..943ac0629b 100644 --- a/gateway/gateway-controller/pkg/api/management/generated.go +++ b/gateway/gateway-controller/pkg/api/management/generated.go @@ -894,13 +894,27 @@ type LLMProviderTemplateData struct { CompletionTokens *ExtractionIdentifier `json:"completionTokens,omitempty" yaml:"completionTokens,omitempty"` // DisplayName Human-readable LLM Template name - DisplayName string `json:"displayName" yaml:"displayName"` + DisplayName string `json:"displayName" yaml:"displayName"` + + // GroupId Stable family-grouping identifier shared by every version of this + // template. Multiple versions with the same groupId but different + // handles are surfaced as versions of one template. Defaults to + // metadata.name when omitted. + GroupId *string `json:"groupId,omitempty" yaml:"groupId,omitempty"` + + // ManagedBy Origin of the template. Built-in templates use 'wso2'; custom + // templates default to 'customer' and may be set to any value. + ManagedBy *string `json:"managedBy,omitempty" yaml:"managedBy,omitempty"` PromptTokens *ExtractionIdentifier `json:"promptTokens,omitempty" yaml:"promptTokens,omitempty"` RemainingTokens *ExtractionIdentifier `json:"remainingTokens,omitempty" yaml:"remainingTokens,omitempty"` RequestModel *ExtractionIdentifier `json:"requestModel,omitempty" yaml:"requestModel,omitempty"` ResourceMappings *LLMProviderTemplateResourceMappings `json:"resourceMappings,omitempty" yaml:"resourceMappings,omitempty"` ResponseModel *ExtractionIdentifier `json:"responseModel,omitempty" yaml:"responseModel,omitempty"` TotalTokens *ExtractionIdentifier `json:"totalTokens,omitempty" yaml:"totalTokens,omitempty"` + + // Version Template content version (e.g. v1.0). Multiple versions of the same + // groupId can coexist; defaults to v1.0 when omitted. + Version *string `json:"version,omitempty" yaml:"version,omitempty"` } // LLMProviderTemplateRequest defines model for LLMProviderTemplateRequest. @@ -5748,281 +5762,285 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/gateway/gateway-controller/pkg/models/llm_provider_template.go b/gateway/gateway-controller/pkg/models/llm_provider_template.go index 9ead757304..642db4d9d6 100644 --- a/gateway/gateway-controller/pkg/models/llm_provider_template.go +++ b/gateway/gateway-controller/pkg/models/llm_provider_template.go @@ -19,11 +19,18 @@ package models import ( + "strings" "time" api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management" ) +// DefaultTemplateVersion is used when a template does not declare a version. +const DefaultTemplateVersion = "v1.0" + +// DefaultTemplateManagedBy is used when a template does not declare managedBy. +const DefaultTemplateManagedBy = "customer" + // StoredLLMProviderTemplate represents the LLM provider template stored in the database and in-memory type StoredLLMProviderTemplate struct { UUID string `json:"uuid"` @@ -36,3 +43,30 @@ type StoredLLMProviderTemplate struct { func (t *StoredLLMProviderTemplate) GetHandle() string { return t.Configuration.Metadata.Name } + +func (t *StoredLLMProviderTemplate) GetGroupID() string { + if t.Configuration.Spec.GroupId != nil { + if v := strings.TrimSpace(*t.Configuration.Spec.GroupId); v != "" { + return v + } + } + return t.Configuration.Metadata.Name +} + +func (t *StoredLLMProviderTemplate) GetVersion() string { + if t.Configuration.Spec.Version != nil { + if v := strings.TrimSpace(*t.Configuration.Spec.Version); v != "" { + return v + } + } + return DefaultTemplateVersion +} + +func (t *StoredLLMProviderTemplate) GetManagedBy() string { + if t.Configuration.Spec.ManagedBy != nil { + if p := strings.TrimSpace(*t.Configuration.Spec.ManagedBy); p != "" { + return p + } + } + return DefaultTemplateManagedBy +} diff --git a/gateway/gateway-controller/pkg/models/llm_provider_template_test.go b/gateway/gateway-controller/pkg/models/llm_provider_template_test.go index 6aada6cc69..ceeac6ef96 100644 --- a/gateway/gateway-controller/pkg/models/llm_provider_template_test.go +++ b/gateway/gateway-controller/pkg/models/llm_provider_template_test.go @@ -78,3 +78,53 @@ func TestStoredLLMProviderTemplate_GetHandle(t *testing.T) { }) } } + +func newTemplate(name string, groupID, version, managedBy *string) *StoredLLMProviderTemplate { + return &StoredLLMProviderTemplate{ + Configuration: api.LLMProviderTemplate{ + Metadata: api.Metadata{Name: name}, + Spec: api.LLMProviderTemplateData{ + GroupId: groupID, + Version: version, + ManagedBy: managedBy, + }, + }, + } +} + +func ptr(s string) *string { return &s } + +func TestStoredLLMProviderTemplate_GetGroupID(t *testing.T) { + // Explicit groupId is returned verbatim. + tmpl := newTemplate("openai-v2", ptr("openai"), nil, nil) + assert.Equal(t, "openai", tmpl.GetGroupID()) + + // Falls back to metadata.name (the handle) when groupId is unset or blank. + assert.Equal(t, "openai-v2", newTemplate("openai-v2", nil, nil, nil).GetGroupID()) + assert.Equal(t, "openai-v2", newTemplate("openai-v2", ptr(" "), nil, nil).GetGroupID()) +} + +func TestStoredLLMProviderTemplate_GetVersion(t *testing.T) { + assert.Equal(t, "v2.0", newTemplate("openai", nil, ptr("v2.0"), nil).GetVersion()) + // Defaults to v1.0 when unset or blank. + assert.Equal(t, DefaultTemplateVersion, newTemplate("openai", nil, nil, nil).GetVersion()) + assert.Equal(t, DefaultTemplateVersion, newTemplate("openai", nil, ptr(" "), nil).GetVersion()) +} + +func TestStoredLLMProviderTemplate_GetManagedBy(t *testing.T) { + assert.Equal(t, "wso2", newTemplate("openai", nil, nil, ptr("wso2")).GetManagedBy()) + // Defaults to customer when unset or blank. + assert.Equal(t, DefaultTemplateManagedBy, newTemplate("openai", nil, nil, nil).GetManagedBy()) + assert.Equal(t, DefaultTemplateManagedBy, newTemplate("openai", nil, nil, ptr("")).GetManagedBy()) +} + +// Two deployed versions of the same template carry distinct handles but share a +// groupId, so the AI workspace can present them as versions of one template. +func TestStoredLLMProviderTemplate_VersionsShareGroupID(t *testing.T) { + v1 := newTemplate("openai-v1", ptr("openai"), ptr("v1.0"), ptr("customer")) + v2 := newTemplate("openai-v2", ptr("openai"), ptr("v2.0"), ptr("customer")) + + assert.NotEqual(t, v1.GetHandle(), v2.GetHandle()) + assert.Equal(t, v1.GetGroupID(), v2.GetGroupID()) + assert.NotEqual(t, v1.GetVersion(), v2.GetVersion()) +} diff --git a/gateway/gateway-controller/pkg/utils/llm_deployment.go b/gateway/gateway-controller/pkg/utils/llm_deployment.go index 04d7115cb2..cd4cd2cd93 100644 --- a/gateway/gateway-controller/pkg/utils/llm_deployment.go +++ b/gateway/gateway-controller/pkg/utils/llm_deployment.go @@ -592,12 +592,28 @@ type LLMTemplateParams struct { Logger *slog.Logger } -// parseAndValidateLLMTemplate parses the raw spec and validates it, returning the typed template. +// normalizeTemplateDefaults fills in default values for GroupId, Version, and ManagedBy if they are missing or empty. +func normalizeTemplateDefaults(tmpl *api.LLMProviderTemplate) { + if tmpl.Spec.GroupId == nil || strings.TrimSpace(*tmpl.Spec.GroupId) == "" { + g := tmpl.Metadata.Name + tmpl.Spec.GroupId = &g + } + if tmpl.Spec.Version == nil || strings.TrimSpace(*tmpl.Spec.Version) == "" { + v := models.DefaultTemplateVersion + tmpl.Spec.Version = &v + } + if tmpl.Spec.ManagedBy == nil || strings.TrimSpace(*tmpl.Spec.ManagedBy) == "" { + p := models.DefaultTemplateManagedBy + tmpl.Spec.ManagedBy = &p + } +} + func (s *LLMDeploymentService) parseAndValidateLLMTemplate(params LLMTemplateParams) (*api.LLMProviderTemplate, error) { var tmpl api.LLMProviderTemplate if err := s.parser.Parse(params.Spec, params.ContentType, &tmpl); err != nil { return nil, fmt.Errorf("%w: failed to parse template configuration: %v", ErrLLMTemplateValidation, err) } + normalizeTemplateDefaults(&tmpl) // Render template expressions into a separate copy for validation only; tmpl stays unrendered for persistence. renderHolder := &models.StoredConfig{Configuration: tmpl} diff --git a/gateway/gateway-controller/pkg/utils/llm_deployment_test.go b/gateway/gateway-controller/pkg/utils/llm_deployment_test.go index 2033e61728..1505c04207 100644 --- a/gateway/gateway-controller/pkg/utils/llm_deployment_test.go +++ b/gateway/gateway-controller/pkg/utils/llm_deployment_test.go @@ -1354,3 +1354,101 @@ func TestLLMDeploymentService_InitializeOOBTemplates_UpdateExisting(t *testing.T assert.NoError(t, err) assert.Equal(t, "Updated Template", found.Configuration.Spec.DisplayName) } + +func TestNormalizeTemplateDefaults(t *testing.T) { + strPtr := func(s string) *string { return &s } + + t.Run("fills defaults when fields are absent", func(t *testing.T) { + tmpl := &api.LLMProviderTemplate{ + Metadata: api.Metadata{Name: "mistralai-v2"}, + Spec: api.LLMProviderTemplateData{DisplayName: "MistralAI"}, + } + normalizeTemplateDefaults(tmpl) + + require.NotNil(t, tmpl.Spec.GroupId) + // groupId falls back to the handle (metadata.name), never overwrites it. + assert.Equal(t, "mistralai-v2", *tmpl.Spec.GroupId) + require.NotNil(t, tmpl.Spec.Version) + assert.Equal(t, models.DefaultTemplateVersion, *tmpl.Spec.Version) + require.NotNil(t, tmpl.Spec.ManagedBy) + assert.Equal(t, models.DefaultTemplateManagedBy, *tmpl.Spec.ManagedBy) + // The handle itself is left untouched. + assert.Equal(t, "mistralai-v2", tmpl.Metadata.Name) + }) + + t.Run("preserves explicit values", func(t *testing.T) { + tmpl := &api.LLMProviderTemplate{ + Metadata: api.Metadata{Name: "mistralai-v2"}, + Spec: api.LLMProviderTemplateData{ + DisplayName: "MistralAI", + GroupId: strPtr("mistralai"), + Version: strPtr("v2.0"), + ManagedBy: strPtr("wso2"), + }, + } + normalizeTemplateDefaults(tmpl) + + assert.Equal(t, "mistralai", *tmpl.Spec.GroupId) + assert.Equal(t, "v2.0", *tmpl.Spec.Version) + assert.Equal(t, "wso2", *tmpl.Spec.ManagedBy) + }) +} + +func TestTemplateVersionsShareGroupID(t *testing.T) { + const v1YAML = `apiVersion: gateway.api-platform.wso2.com/v1 +kind: LlmProviderTemplate +metadata: + name: deep-seek-v1-0 +spec: + displayName: deep seek + groupId: deep-seek + managedBy: customer + version: v1.0 + promptTokens: + location: payload + identifier: $.usage.prompt_tokens + totalTokens: + location: payload + identifier: $.usage.total_tokens +` + const v2YAML = `apiVersion: gateway.api-platform.wso2.com/v1 +kind: LlmProviderTemplate +metadata: + name: deep-seek-v2-0 +spec: + displayName: deep seek + groupId: deep-seek + managedBy: customer + version: v2.0 + promptTokens: + location: payload + identifier: $.usage.prompt_tokens + totalTokens: + location: payload + identifier: $.usage.total_tokens +` + + parser := config.NewParser() + parse := func(raw string) *models.StoredLLMProviderTemplate { + var tmpl api.LLMProviderTemplate + require.NoError(t, parser.Parse([]byte(raw), "application/yaml", &tmpl)) + normalizeTemplateDefaults(&tmpl) + return &models.StoredLLMProviderTemplate{Configuration: tmpl} + } + + v1 := parse(v1YAML) + v2 := parse(v2YAML) + + // Two separate templates: handles are distinct and taken verbatim from metadata.name. + assert.Equal(t, "deep-seek-v1-0", v1.GetHandle()) + assert.Equal(t, "deep-seek-v2-0", v2.GetHandle()) + assert.NotEqual(t, v1.GetHandle(), v2.GetHandle()) + + // Same group, different versions — this is what lets the AI workspace group them. + assert.Equal(t, "deep-seek", v1.GetGroupID()) + assert.Equal(t, v1.GetGroupID(), v2.GetGroupID()) + assert.Equal(t, "v1.0", v1.GetVersion()) + assert.Equal(t, "v2.0", v2.GetVersion()) + assert.NotEqual(t, v1.GetVersion(), v2.GetVersion()) + assert.Equal(t, "customer", v1.GetManagedBy()) +} From 4d2febd3bea0dbbfcdaa67fd1959bd9e64c6a14a Mon Sep 17 00:00:00 2001 From: Tharanidk Date: Mon, 6 Jul 2026 15:26:24 +0530 Subject: [PATCH 2/2] fix review changes --- gateway/examples/llm-provider-template.yaml | 3 +++ gateway/gateway-controller/api/management-openapi.yaml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/gateway/examples/llm-provider-template.yaml b/gateway/examples/llm-provider-template.yaml index 34d5019f22..7e87e3ab9b 100644 --- a/gateway/examples/llm-provider-template.yaml +++ b/gateway/examples/llm-provider-template.yaml @@ -22,6 +22,9 @@ metadata: name: my-llm-provider-template spec: displayName: OpenAI + groupId: anthropic + managedBy: wso2 + version: v1.0 promptTokens: location: payload identifier: $.usage.inputTokens diff --git a/gateway/gateway-controller/api/management-openapi.yaml b/gateway/gateway-controller/api/management-openapi.yaml index a29a667e01..640796a9fc 100644 --- a/gateway/gateway-controller/api/management-openapi.yaml +++ b/gateway/gateway-controller/api/management-openapi.yaml @@ -5573,18 +5573,21 @@ components: template. Multiple versions with the same groupId but different handles are surfaced as versions of one template. Defaults to metadata.name when omitted. + maxLength: 40 example: openai managedBy: type: string description: | Origin of the template. Built-in templates use 'wso2'; custom templates default to 'customer' and may be set to any value. + maxLength: 255 example: wso2 version: type: string description: | Template content version (e.g. v1.0). Multiple versions of the same groupId can coexist; defaults to v1.0 when omitted. + maxLength: 30 example: v1.0 promptTokens: $ref: '#/components/schemas/ExtractionIdentifier'