Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4d81b13
Add LLM provider template implementations
Tharanidk Jun 21, 2026
9468043
Add URL validation and improve provider template handling
Tharanidk Jun 21, 2026
57beb06
Add 'enabled' field to LLM provider templates and update related logic
Tharanidk Jun 22, 2026
66e3e4e
Add 'provider' field to LLM provider templates and update gateway res…
Tharanidk Jun 22, 2026
9920274
Add error handling for LLM provider template usage and prevent deleti…
Tharanidk Jun 22, 2026
6431838
fix review changes
Tharanidk Jun 22, 2026
e8c9d48
Add ui improvemets and bug fixing
Tharanidk Jun 23, 2026
d915270
Refactor LLM provider template structure: make 'id' and 'version' fie…
Tharanidk Jun 23, 2026
4066966
Add version field to LLM provider templates and update related logics…
Tharanidk Jun 23, 2026
2a00c35
add changes for gateway related files
Tharanidk Jun 24, 2026
fe2c77c
Add unit tests for multi-version support in LLM provider templates
Tharanidk Jun 24, 2026
c45077a
Update LLM provider templates and schema version: change 'provider' t…
Tharanidk Jun 24, 2026
bf3b632
Update LLM provider templates: rename 'provider' to 'managedBy', add …
Tharanidk Jun 24, 2026
1558d52
Fix UI test issues and mssql db issues
Tharanidk Jun 25, 2026
93cd3d7
fix test bugs and review changes
Tharanidk Jun 25, 2026
33c62d3
fix test failures
Tharanidk Jun 25, 2026
387df3d
fix gateway test issues
Tharanidk Jun 25, 2026
29ac49b
Rename 'groupVersionId' to 'groupId' in LLM provider templates and up…
Tharanidk Jun 25, 2026
70325d9
Refactor unique constraints in llm_provider_templates table and clean…
Tharanidk Jun 25, 2026
2a4fd77
Add 'managed_by' column to llm_provider_templates and enforce uniquen…
Tharanidk Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/rest-apis/gateway/llm-provider-template-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ Status Code **200**
|»»»»» **additionalProperties**|string|false|none|none|
|»»» spec|[LLMProviderTemplateData](schemas.md#schemallmprovidertemplatedata)|true|none|none|
|»»»» displayName|string|true|none|Human-readable LLM Template name|
|»»»» provider|string|false|none|Origin of the template. Built-in templates use 'wso2'; custom<br>templates default to 'other' and may be set to any value.|
|»»»» version|string|false|none|Template content version (e.g. v1.0). Multiple versions of the same<br>handle can coexist; defaults to v1.0 when omitted.|
|»»»» promptTokens|[ExtractionIdentifier](schemas.md#schemaextractionidentifier)|false|none|none|
|»»»»» location|string|true|none|Where to find the token information|
|»»»»» identifier|string|true|none|JSONPath expression or header name to identify the token value|
Expand Down
6 changes: 3 additions & 3 deletions gateway/gateway-controller/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ Response (the server echoes back the full k8s-shaped resource with a
server-managed `status` block):
```json
{
"apiVersion": "gateway.api-platform.wso2.com/v1alpha1",
"apiVersion": "gateway.api-platform.wso2.com/v1",
"kind": "RestApi",
"metadata": { "name": "weather-api-v1.0" },
"spec": {
Expand Down Expand Up @@ -643,7 +643,7 @@ components:
properties:
apiVersion:
type: string
example: gateway.api-platform.wso2.com/v1alpha1
example: gateway.api-platform.wso2.com/v1
kind:
type: string
example: RestApi
Expand All @@ -652,7 +652,7 @@ components:
spec:
$ref: "#/components/schemas/APIConfigData"
example:
apiVersion: gateway.api-platform.wso2.com/v1alpha1
apiVersion: gateway.api-platform.wso2.com/v1
kind: RestApi
metadata:
name: petstore-api-v1.0
Expand Down
20 changes: 20 additions & 0 deletions gateway/gateway-controller/api/management-openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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. Defaults to metadata.name when omitted.
maxLength: 253
Comment thread
Tharanidk marked this conversation as resolved.
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: 253
Comment thread
Tharanidk marked this conversation as resolved.
example: wso2
version:
type: string
description: |
Template content version (e.g. v1.0). Multiple versions of the same
group_id can coexist; defaults to v1.0 when omitted.
example: v1.0
Comment thread
Tharanidk marked this conversation as resolved.
promptTokens:
$ref: '#/components/schemas/ExtractionIdentifier'
completionTokens:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ metadata:
name: anthropic
spec:
displayName: Anthropic
groupId: anthropic
managedBy: wso2
version: v1.0
promptTokens:
location: payload
identifier: $.usage.input_tokens
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ metadata:
name: awsbedrock
spec:
displayName: AWS Bedrock
groupId: awsbedrock
managedBy: wso2
version: v1.0
promptTokens:
location: payload
identifier: $.usage.inputTokens
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ metadata:
name: gemini
spec:
displayName: Gemini
groupId: gemini
managedBy: wso2
version: v1.0
promptTokens:
location: payload
identifier: $.usageMetadata.promptTokenCount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ metadata:
name: mistralai
spec:
displayName: MistralAI
groupId: mistralai
managedBy: wso2
version: v1.0
promptTokens:
location: payload
identifier: $.usage.prompt_tokens
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ metadata:
name: openai
spec:
displayName: OpenAI
groupId: openai
managedBy: wso2
version: v1.0
promptTokens:
location: payload
identifier: $.usage.prompt_tokens
Expand Down
2 changes: 1 addition & 1 deletion gateway/gateway-controller/pkg/api/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ func (s *APIServer) getLLMProviderTemplate(sourceConfig any) (*api.LLMProviderTe
return nil, fmt.Errorf("template name is empty")
}

storedTemplate, err := s.store.GetTemplateByHandle(templateNameStr)
storedTemplate, err := s.store.GetTemplateByID(templateNameStr)
if err != nil {
return nil, fmt.Errorf("failed to get template '%s' from store: %w", templateNameStr, err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ func (m *MockStorage) GetLLMProviderTemplate(id string) (*models.StoredLLMProvid
if tmpl, ok := m.templates[id]; ok {
return tmpl, nil
}
return nil, errors.New("template not found")
return nil, fmt.Errorf("%w: uuid=%s", storage.ErrNotFound, id)
}

func (m *MockStorage) GetAllLLMProviderTemplates() ([]*models.StoredLLMProviderTemplate, error) {
Expand All @@ -322,7 +322,7 @@ func (m *MockStorage) GetLLMProviderTemplateByHandle(handle string) (*models.Sto
return nil, m.getErr
}
for _, tmpl := range m.templates {
if tmpl.GetHandle() == handle {
if tmpl.GetGroupID() == handle {
return tmpl, nil
}
}
Expand Down Expand Up @@ -1898,7 +1898,7 @@ func TestCreateLLMProviderTemplateWithDBAndEventHub(t *testing.T) {
}
require.NotNil(t, storedTemplate)

assert.Equal(t, "openai", storedTemplate.GetHandle())
assert.Equal(t, "openai", storedTemplate.GetGroupID())
assert.Equal(t, "test-gateway", mockHub.publishedEvents[0].gatewayID)
assert.Equal(t, eventhub.EventTypeLLMTemplate, mockHub.publishedEvents[0].event.EventType)
assert.Equal(t, "CREATE", mockHub.publishedEvents[0].event.Action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,16 @@ func TestGetLLMProviderTemplateByIdSuccess(t *testing.T) {
server := createTestServerWithLLM()

now := time.Now()
version := "v1.0"
template := &models.StoredLLMProviderTemplate{
UUID: "0000-template1-0000-000000000000",
Configuration: api.LLMProviderTemplate{
Metadata: api.Metadata{
Name: "0000-template1-0000-000000000000",
Name: "openai",
},
Spec: api.LLMProviderTemplateData{
DisplayName: "OpenAI Template",
Version: &version,
},
},
CreatedAt: now,
Expand All @@ -216,23 +218,25 @@ func TestGetLLMProviderTemplateByIdSuccess(t *testing.T) {
require.NoError(t, server.db.SaveLLMProviderTemplate(template))
require.NoError(t, server.store.AddTemplate(template))

c, w := createTestContext("GET", "/llm-provider-templates/template1", nil)
server.GetLLMProviderTemplateById(c, "0000-template1-0000-000000000000")
// The template is fetched by its version-specific id (handle + version).
c, w := createTestContext("GET", "/llm-provider-templates/openai-v1-0", nil)
server.GetLLMProviderTemplateById(c, "openai-v1-0")

assert.Equal(t, http.StatusOK, w.Code)

var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)

// GET returns the k8s-shaped resource body directly. The server-managed
// status block carries the handle (name) as `id`.
// GET returns the k8s-shaped resource body directly. metadata.name is the
// version-independent handle, while the server-managed status block carries
// the version-specific id (handle + sanitized version).
metadata, ok := response["metadata"].(map[string]interface{})
require.True(t, ok, "metadata should be a map, got %T", response["metadata"])
assert.Equal(t, "0000-template1-0000-000000000000", metadata["name"])
assert.Equal(t, "openai", metadata["name"])
status, ok := response["status"].(map[string]interface{})
require.True(t, ok, "status should be a map, got %T", response["status"])
assert.Equal(t, "0000-template1-0000-000000000000", status["id"])
assert.Equal(t, "openai-v1-0", status["id"])
}

// TestListLLMProvidersEmpty tests listing with no providers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (s *APIServer) CreateLLMProviderTemplate(c *gin.Context) {

log.Info("LLM provider template created successfully",
slog.String("uuid", storedTemplate.UUID),
slog.String("handle", storedTemplate.GetHandle()))
slog.String("handle", storedTemplate.GetGroupID()))

c.JSON(http.StatusCreated, buildTemplateResourceResponse(storedTemplate))
}
Expand All @@ -109,10 +109,10 @@ func (s *APIServer) ListLLMProviderTemplates(c *gin.Context, params api.ListLLMP
func (s *APIServer) GetLLMProviderTemplateById(c *gin.Context, id string) {
log := middleware.GetLogger(c, s.logger)

template, err := s.llmDeploymentService.GetLLMProviderTemplateByHandle(id)
template, err := s.llmDeploymentService.GetLLMProviderTemplateByID(id)
if err != nil {
if storage.IsNotFoundError(err) {
log.Warn("LLM provider template not found", slog.String("handle", id))
log.Warn("LLM provider template not found", slog.String("id", id))
c.JSON(http.StatusNotFound, api.ErrorResponse{
Status: "error",
Message: fmt.Sprintf("Template with id '%s' not found", id),
Expand Down Expand Up @@ -179,7 +179,7 @@ func (s *APIServer) UpdateLLMProviderTemplate(c *gin.Context, id string) {

log.Info("LLM provider template updated successfully",
slog.String("uuid", updated.UUID),
slog.String("handle", updated.GetHandle()))
slog.String("handle", updated.GetGroupID()))

c.JSON(http.StatusOK, buildTemplateResourceResponse(updated))
}
Expand Down Expand Up @@ -210,11 +210,11 @@ func (s *APIServer) DeleteLLMProviderTemplate(c *gin.Context, id string) {

log.Info("LLM provider template deleted successfully",
slog.String("uuid", deleted.UUID),
slog.String("handle", deleted.GetHandle()))
slog.String("handle", deleted.GetGroupID()))

c.JSON(http.StatusOK, gin.H{
"status": "success",
"message": "LLM provider template deleted successfully",
"id": deleted.GetHandle(),
"id": deleted.GetID(),
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func buildTemplateResourceResponse(template *models.StoredLLMProviderTemplate) a
if template == nil {
return nil
}
id := template.GetHandle()
id := template.GetID()
createdAt := template.CreatedAt
updatedAt := template.UpdatedAt
status := api.ResourceStatus{
Expand Down
14 changes: 13 additions & 1 deletion gateway/gateway-controller/pkg/api/management/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"strings"

"github.com/wso2/api-platform/common/eventhub"
api "github.com/wso2/api-platform/gateway/gateway-controller/pkg/api/management"
"github.com/wso2/api-platform/gateway/gateway-controller/pkg/models"
"github.com/wso2/api-platform/gateway/gateway-controller/pkg/storage"
"github.com/wso2/api-platform/gateway/gateway-controller/pkg/utils"
Expand Down Expand Up @@ -118,10 +117,10 @@ func (l *EventListener) handleLLMTemplateDelete(event eventhub.Event) {
}

if existingTemplate != nil {
if err := l.removeLLMTemplate(existingTemplate.GetHandle(), event.EventID); err != nil {
if err := l.removeLLMTemplate(existingTemplate.GetGroupID(), event.EventID); err != nil {
l.logger.Warn("Failed to remove LLM template lazy resource",
slog.String("template_id", entityID),
slog.String("template_handle", existingTemplate.GetHandle()),
slog.String("template_handle", existingTemplate.GetGroupID()),
slog.Any("error", err))
}
}
Expand All @@ -136,32 +135,33 @@ func (l *EventListener) syncLLMTemplate(template *models.StoredLLMProviderTempla
return nil
}

resource, err := l.buildLLMTemplateLazyResource(template.Configuration)
resource, err := l.buildLLMTemplateLazyResource(template)
if err != nil {
return err
}

return l.lazyResourceManager.StoreResource(resource, correlationID)
}

func (l *EventListener) removeLLMTemplate(handle, correlationID string) error {
if l.lazyResourceManager == nil || handle == "" {
func (l *EventListener) removeLLMTemplate(groupVersionID, correlationID string) error {
if l.lazyResourceManager == nil || groupVersionID == "" {
return nil
}

return l.lazyResourceManager.RemoveResourceByIDAndType(
handle,
groupVersionID,
utils.LazyResourceTypeLLMProviderTemplate,
correlationID,
)
}

func (l *EventListener) buildLLMTemplateLazyResource(template api.LLMProviderTemplate) (*storage.LazyResource, error) {
if template.Metadata.Name == "" {
return nil, fmt.Errorf("template handle (metadata.name) is empty")
func (l *EventListener) buildLLMTemplateLazyResource(template *models.StoredLLMProviderTemplate) (*storage.LazyResource, error) {
groupVersionID := template.GetGroupID()
if groupVersionID == "" {
return nil, fmt.Errorf("template group_id (metadata.name) is empty")
}

payload, err := json.Marshal(template)
payload, err := json.Marshal(template.Configuration)
if err != nil {
return nil, fmt.Errorf("failed to marshal template as JSON: %w", err)
}
Expand All @@ -172,7 +172,7 @@ func (l *EventListener) buildLLMTemplateLazyResource(template api.LLMProviderTem
}

return &storage.LazyResource{
ID: template.Metadata.Name,
ID: groupVersionID,
ResourceType: utils.LazyResourceTypeLLMProviderTemplate,
Resource: resource,
}, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestHandleEvent_LLMTemplateCreate_LoadsTemplateFromDBAndPublishesLazyResour

stored, err := store.GetTemplate(template.UUID)
require.NoError(t, err)
assert.Equal(t, "openai", stored.GetHandle())
assert.Equal(t, "openai", stored.GetGroupID())

resource, exists := lazyManager.GetResourceByIDAndType("openai", utils.LazyResourceTypeLLMProviderTemplate)
require.True(t, exists)
Expand Down Expand Up @@ -119,7 +119,7 @@ func TestHandleEvent_LLMTemplateDelete_RemovesLocalState(t *testing.T) {
logger: newTestLogger(),
}

resource, err := listener.buildLLMTemplateLazyResource(template.Configuration)
resource, err := listener.buildLLMTemplateLazyResource(template)
require.NoError(t, err)
require.NoError(t, lazyManager.StoreResource(resource, ""))

Expand Down
Loading
Loading