Skip to content

[AI Gateway] Add multi-provider routing for LLM proxies#2586

Merged
Krishanx92 merged 10 commits into
mainfrom
multi-provider-routing
Jul 12, 2026
Merged

[AI Gateway] Add multi-provider routing for LLM proxies#2586
Krishanx92 merged 10 commits into
mainfrom
multi-provider-routing

Conversation

@Arshardh

Copy link
Copy Markdown
Contributor

Merging changes done from: #2486

Aakashwije and others added 4 commits July 6, 2026 23:29
Introduce additionalProviders on LLM proxies so one proxy can fan out to
multiple LLM upstreams, with per-provider conditional upstream auth and inline
request/response translators gated on the selected provider.

- gateway-controller: additionalProviders + inline transformer in the
  management API schema and validator; transformProxy exposes each additional
  provider as a named upstream and injects auth/translator policies with
  selected_provider execution conditions
- platform-api: additionalProviders across openapi, model, dto and service,
  and carried into the deployment artifact sent to the gateway
- kubernetes-operator: additionalProviders on the LlmProxy CRD (types,
  deepcopy, CRD manifests) and controller payload
- examples: multi-provider proxy and per-provider sample configs

Translator policy implementations, their build config and the integration
feature test follow in a separate PR.
edit upstream to LLM Provider

Co-authored-by: Renuka Piyumal Fernando <renukapiyumal@gmail.com>
- examples: add Apache license headers to the nine new LLM provider and
  proxy samples, matching the short 2026 form used by recent examples
- examples: remove the loopback ApiKey documents and their hardcoded key
  material from the five provider samples; point at api-key.yaml instead
  and use REPLACE_WITH_... placeholders for the proxies' provider.auth
- examples: document the optional `as` alias on openai-multi-provider-proxy
- platform-api: add LLMProxyTransformer and the transformer property to
  LLMProxyAdditionalProvider, mirroring the gateway-controller schema, and
  carry it through the model, dto, service mappers and deployment artifact
- platform-api: align the `as` description with the gateway-controller spec
…ng-clean

[AI Gateway] Add multi-provider routing for LLM proxies
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Arshardh, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 14c92d5a-ec6f-4b52-b7c0-e205045e0922

📥 Commits

Reviewing files that changed from the base of the PR and between 7213936 and 18a10d4.

📒 Files selected for processing (11)
  • gateway/gateway-controller/api/management-openapi.yaml
  • gateway/gateway-controller/pkg/api/management/generated.go
  • gateway/gateway-controller/pkg/config/llm_validator.go
  • gateway/gateway-controller/pkg/utils/llm_transformer.go
  • gateway/gateway-controller/pkg/utils/llm_transformer_multiprovider_test.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • platform-api/internal/service/llm.go
  • platform-api/internal/service/llm_test.go
📝 Walkthrough

Walkthrough

Adds multi-provider LLM proxy support with additional-provider schemas, platform mappings, conditional gateway transformers and authentication, operator Secret resolution, validation, tests, and provider/proxy examples.

Changes

Multi-provider LLM proxy support

Layer / File(s) Summary
Additional-provider contracts and schemas
gateway/gateway-controller/api/..., platform-api/api/..., platform-api/resources/..., kubernetes/gateway-operator/api/..., kubernetes/.../crds/...
Adds additionalProviders, provider aliases, optional authentication, transformer definitions, CRD validation, generated models, DTOs, and deepcopy support.
Platform proxy mapping and deployment
platform-api/internal/service/..., platform-api/internal/model/llm.go
Maps additional providers and transformers through proxy create, update, response, and deployment YAML generation.
Gateway validation and conditional policy generation
gateway/gateway-controller/pkg/config/..., gateway/gateway-controller/pkg/utils/...
Validates provider identities, aliases, authentication, and transformers; generates named upstreams and conditionally applies provider-specific authentication and translation policies.
Operator credential resolution and deployment payloads
kubernetes/gateway-operator/internal/controller/...
Resolves additional-provider Secret references, tracks Secret dependencies, flattens credentials into deployment payloads, and verifies the rendered values.
Provider and proxy examples
gateway/examples/*
Adds OpenAI, Anthropic, Azure OpenAI, Gemini, and Mistral provider examples plus single-provider and multi-provider proxy configurations.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant openai-header-router
  participant LLMProviderTransformer
  participant Provider
  Client->>openai-header-router: Send POST /chat/completions
  openai-header-router->>LLMProviderTransformer: Select provider from x-provider
  LLMProviderTransformer->>Provider: Apply matching transformer and auth
  Provider-->>Client: Return translated provider response
Loading

Possibly related PRs

  • wso2/api-platform#2486: Overlaps with the additional-provider schemas, validation, routing, and conditional policy implementation.
  • wso2/api-platform#2526: Extends related proxy create/update flows for Secret placeholder validation and rotated-secret cleanup.

Suggested reviewers: RakhithaRR, Tharsanan1, VirajSalaka, renuka-fernando, tharindu1st, malinthaprasan, AnuGayan, chamilaadhi, tgtshanika, senthuran16, CrowleyRajapakse, hisanhunais, HiranyaKavishani, HeshanSudarshana, ashera96, pubudu538, PasanT9, Krishanx92, Thushani-Jayasekera

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is off-template and missing required sections like Purpose, Goals, Approach, tests, security checks, and test environment. Replace it with the required template and fill in each section, including purpose, goals, approach, tests, security checks, samples, related PRs, and test environment.
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: multi-provider routing for LLM proxies.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch multi-provider-routing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
gateway/examples/openai-multi-provider-proxy.yaml (1)

92-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use operationPolicies instead of the deprecated policies field.

Same as anthropic-openai-proxy.yaml — the policies field on LLMProxyConfigData is deprecated per generated.go. New examples should use operationPolicies.

♻️ Suggested rename
-  policies:
+  operationPolicies:
     - name: api-key-auth
       version: v1
       paths:
         - path: /chat/completions
           methods: [POST]
           params:
             key: X-API-Key
             in: header
     - name: openai-header-router
       version: v1
       paths:
         - path: /chat/completions
           methods: [POST]
           params:
             headerName: x-provider
             defaultProvider: openai-provider
             mappings:
               - headerValue: openai
                 provider: openai-provider
               - headerValue: anthropic
                 provider: anthropic-provider
               - headerValue: azure-openai
                 provider: azure-openai-provider
               - headerValue: mistral
                 provider: mistral-provider
               - headerValue: gemini
                 provider: gemini-provider
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gateway/examples/openai-multi-provider-proxy.yaml` around lines 92 - 119,
Replace the deprecated policies field with operationPolicies in the
LLMProxyConfigData configuration, preserving the existing api-key-auth and
openai-header-router entries unchanged.
gateway/examples/anthropic-openai-proxy.yaml (1)

45-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use operationPolicies instead of the deprecated policies field.

The policies field on LLMProxyConfigData is marked deprecated in the generated contract (generated.go): "DEPRECATED - use operationPolicies. Still honoured (treated identically to operationPolicies)." New example files should model the non-deprecated field so users copy the correct pattern.

♻️ Suggested rename
-  policies:
+  operationPolicies:
     - name: openai-to-anthropic
       version: v1
       paths:
         - path: /chat/completions
           methods: [POST]
           params:
             model: claude-sonnet-4-5-20250929
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gateway/examples/anthropic-openai-proxy.yaml` around lines 45 - 52, Replace
the deprecated `policies` field with `operationPolicies` in the
`openai-to-anthropic` configuration, preserving the existing policy name,
version, paths, methods, and model parameters.
platform-api/resources/openapi.yaml (2)

7873-7877: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

LLMProxyAdditionalProvider.id missing length bounds.

Sibling LLMProxyProvider.id enforces minLength: 1 / maxLength: 40, but this field has none, letting arbitrarily long/short values pass OpenAPI validation before failing later at a DB lookup.

🔧 Suggested fix
         id:
           type: string
           description: Unique id of a deployed llm provider
+          minLength: 1
+          maxLength: 40
           example: anthropic-provider
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platform-api/resources/openapi.yaml` around lines 7873 - 7877, Update the
LLMProxyAdditionalProvider.id property to match LLMProxyProvider.id by adding
minLength: 1 and maxLength: 40 alongside its string type, ensuring OpenAPI
validation rejects empty or overly long provider IDs.

7890-7913: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

LLMProxyTransformer.version lacks the major-only pattern enforced elsewhere.

The description explicitly documents "Major-only translator policy version (for example v1)", mirroring Policy.version/LLMPolicy.version/OperationPolicy.version, all of which enforce pattern: '^v\d+$'. This field has no such pattern, so a full semver like v1.0.0 would pass schema validation despite the stated contract. See related comment on gateway/gateway-controller/pkg/config/llm_validator.go's validateLLMProxyTransformer, which also skips this check.

🔧 Suggested fix
         version:
           type: string
           description: >
             Major-only translator policy version (for example v1). The Gateway
             Controller resolves it to the installed full version.
           minLength: 1
+          pattern: '^v\d+$'
           example: v1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platform-api/resources/openapi.yaml` around lines 7890 - 7913, Add the same
major-only version constraint used by Policy.version, LLMPolicy.version, and
OperationPolicy.version to LLMProxyTransformer.version: set its pattern to
'^v\d+$' while retaining the existing minLength and example.
gateway/gateway-controller/pkg/config/llm_validator.go (1)

613-622: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Hoist the as format regex out of the loop.

regexp.MustCompile is called fresh on every additionalProviders entry instead of being precompiled once, unlike the existing v.metadataNameRegex pattern used just above.

🔧 Suggested fix
+// add alongside metadataNameRegex on the LLMValidator struct:
+// upstreamNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
...
-				if !regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`).MatchString(upstreamName) {
+				if !v.upstreamNameRegex.MatchString(upstreamName) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gateway/gateway-controller/pkg/config/llm_validator.go` around lines 613 -
622, Precompile the `as` name validation regex once outside the
additional-providers loop, alongside the existing `v.metadataNameRegex`, and
reuse that compiled pattern in the `provider.As` validation within the loop
instead of calling regexp.MustCompile for each entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gateway/gateway-controller/api/management-openapi.yaml`:
- Around line 5891-5914: The LLMProxyTransformer version schema currently
permits values beyond the required major-only policy format. Update the version
property under LLMProxyTransformer to validate the ^v\d+$ pattern, preserving
its existing string type and documentation so invalid values such as v1.0 or
latest are rejected at the API boundary.

In `@gateway/gateway-controller/pkg/config/llm_validator.go`:
- Around line 649-664: Update validateLLMProxyTransformer to validate Version
against the project’s major-only format, requiring a value matching ^v\d+$
rather than merely being non-empty. Reuse the existing policy version validation
convention or equivalent validator, while preserving the required-field error
handling and adding an appropriate invalid-format ValidationError for
transformer.version.

In `@kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go`:
- Around line 35-50: Add a Transformer field to LLMProxyAdditionalProvider using
the operator API’s transformer type and matching JSON/schema markers from the
management contract and gateway model. Update generated deepcopy code and
regenerate both CRD artifacts so spec.additionalProviders[*].transformer is
accepted and preserved.

In `@platform-api/internal/service/llm.go`:
- Around line 1354-1362: In the Create and Update validation flows, add eager
validation for every req.AdditionalProviders entry: verify each Id references an
existing provider and reject duplicate provider IDs, upstream names, or aliases
using the same validation/error patterns used for req.Provider.Id and policy
versions. Update the corresponding request-to-model mapping near the
AdditionalProviders field only after validation succeeds, covering both Create
and Update handlers.

---

Nitpick comments:
In `@gateway/examples/anthropic-openai-proxy.yaml`:
- Around line 45-52: Replace the deprecated `policies` field with
`operationPolicies` in the `openai-to-anthropic` configuration, preserving the
existing policy name, version, paths, methods, and model parameters.

In `@gateway/examples/openai-multi-provider-proxy.yaml`:
- Around line 92-119: Replace the deprecated policies field with
operationPolicies in the LLMProxyConfigData configuration, preserving the
existing api-key-auth and openai-header-router entries unchanged.

In `@gateway/gateway-controller/pkg/config/llm_validator.go`:
- Around line 613-622: Precompile the `as` name validation regex once outside
the additional-providers loop, alongside the existing `v.metadataNameRegex`, and
reuse that compiled pattern in the `provider.As` validation within the loop
instead of calling regexp.MustCompile for each entry.

In `@platform-api/resources/openapi.yaml`:
- Around line 7873-7877: Update the LLMProxyAdditionalProvider.id property to
match LLMProxyProvider.id by adding minLength: 1 and maxLength: 40 alongside its
string type, ensuring OpenAPI validation rejects empty or overly long provider
IDs.
- Around line 7890-7913: Add the same major-only version constraint used by
Policy.version, LLMPolicy.version, and OperationPolicy.version to
LLMProxyTransformer.version: set its pattern to '^v\d+$' while retaining the
existing minLength and example.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ee03703b-5e45-45ca-bf83-b8f702ab12dc

📥 Commits

Reviewing files that changed from the base of the PR and between e5ae371 and 7213936.

📒 Files selected for processing (29)
  • gateway/examples/anthropic-openai-proxy.yaml
  • gateway/examples/anthropic-provider.yaml
  • gateway/examples/azure-openai-provider.yaml
  • gateway/examples/azure-openai-proxy.yaml
  • gateway/examples/gemini-provider.yaml
  • gateway/examples/mistral-openai-proxy.yaml
  • gateway/examples/mistral-provider.yaml
  • gateway/examples/openai-multi-provider-proxy.yaml
  • gateway/examples/openai-provider.yaml
  • gateway/gateway-controller/api/management-openapi.yaml
  • gateway/gateway-controller/pkg/api/management/generated.go
  • gateway/gateway-controller/pkg/config/llm_validator.go
  • gateway/gateway-controller/pkg/utils/llm_transformer.go
  • gateway/gateway-controller/pkg/utils/llm_transformer_multiprovider_test.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller_test.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/gateway-operator/internal/controller/management_valuefrom_enqueue.go
  • kubernetes/gateway-operator/internal/controller/management_valuefrom_fingerprint.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • platform-api/api/generated.go
  • platform-api/internal/dto/llm_deployment.go
  • platform-api/internal/model/llm.go
  • platform-api/internal/service/llm.go
  • platform-api/internal/service/llm_deployment.go
  • platform-api/resources/openapi.yaml

Comment thread gateway/gateway-controller/api/management-openapi.yaml
Comment thread gateway/gateway-controller/pkg/config/llm_validator.go
Comment thread kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
Comment thread platform-api/internal/service/llm.go
- Constrain LLMProxyTransformer.version to major-only in the management OpenAPI schema and the gateway-controller LLM validator.
- Add operator-side LLMProxyTransformer type + additionalProviders[].transformer field; regenerate deepcopy and both CRD artifacts (operator + Helm).
- Eagerly validate additionalProviders (existence + unique upstream names) at LLM proxy Create/Update, covered by Create/Update tests.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants