feat: add SAP AI Core provider via Orchestration Service#1610
feat: add SAP AI Core provider via Orchestration Service#1610Huimintai wants to merge 5 commits intokagent-dev:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request adds comprehensive support for SAP AI Core as a new model provider. The implementation enables users to access all SAP AI Core model families (Anthropic, OpenAI, Gemini, Amazon, Meta, Mistral, DeepSeek, Qwen, etc.) through a unified Orchestration Service endpoint.
Changes:
- Adds SAPAICore model provider enum and configuration types across Go, Python, and UI layers
- Implements KAgentSAPAICoreLlm with full Orchestration protocol support, OAuth2 token caching, and automatic deployment URL discovery
- Integrates OAuth2 credential injection via Kubernetes secrets for secure authentication
- Adds HTTP API endpoints (create/update/list/get) with proper configuration flattening
- Implements CEL validation rules for provider-specific configuration enforcement
- Registers 48 supported models from SAP's Orchestration Service
- Adds environment variable registration for SAP_AI_CORE_CLIENT_ID and SAP_AI_CORE_CLIENT_SECRET
- Updates UI components to include SAPAICore provider with appropriate icons and form handling
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| go/api/v1alpha2/modelconfig_types.go | Defines SAPAICoreConfig struct with BaseURL, ResourceGroup, and AuthURL fields; adds validation rules for provider configuration |
| go/api/adk/types.go | Adds SAPAICore model type with JSON marshaling and embedding config support |
| go/core/internal/controller/translator/agent/adk_api_translator.go | Implements translation of K8s ModelConfig resources to ADK SAPAICore models with OAuth2 secret injection |
| go/core/internal/httpserver/handlers/modelconfig.go | Adds SAPAICore parameter handling in create, update, list, and get endpoints |
| go/core/internal/httpserver/handlers/modelproviderconfig.go | Registers SAPAICore in supported provider list with required/optional field metadata |
| go/core/internal/httpserver/handlers/models.go | Lists 48 supported models from SAP Orchestration Service with function calling capabilities |
| go/core/pkg/env/providers.go | Registers SAP_AI_CORE_CLIENT_ID and SAP_AI_CORE_CLIENT_SECRET environment variables |
| go/api/httpapi/types.go | Adds SAPAICoreParams to create/update model config request types |
| python/packages/kagent-adk/src/kagent/adk/types.py | Adds SAPAICore Pydantic model and includes it in ModelUnion |
| python/packages/kagent-adk/src/kagent/adk/models/_sap_ai_core.py | Implements KAgentSAPAICoreLlm with async OAuth2 token management, TLS configuration, deployment URL discovery, and stream/non-stream request handling |
| python/packages/kagent-adk/src/kagent/adk/models/init.py | Exports KAgentSAPAICoreLlm from models package |
| ui/src/types/index.ts | Adds SAPAICoreConfigPayload interface for UI form handling |
| ui/src/lib/providers.ts | Registers SAPAICore in provider list with documentation links |
| ui/src/components/ProviderCombobox.tsx | Adds SAPAICore to provider icon mapping |
| ui/src/components/ModelProviderCombobox.tsx | Adds SAPAICore to model provider icon mapping |
| ui/src/components/icons/SAPAICore.tsx | Adds SAPAICore provider SVG icon component |
| ui/src/app/models/new/page.tsx | Adds SAPAICore payload handling in model configuration form |
| helm/kagent-crds/templates/kagent.dev_modelproviderconfigs.yaml | Updates CRD to include SAPAICore enum value |
| helm/kagent-crds/templates/kagent.dev_modelconfigs.yaml | Adds SAPAICore configuration schema and validation rules to CRD |
| go/api/config/crd/bases/*.yaml | Corresponding CRD base definitions for SAPAICore support |
| go/api/v1alpha2/zz_generated.deepcopy.go | Auto-generated deep copy functions for SAPAICoreConfig |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
71e796a to
1cfa50a
Compare
|
Hey there, just as an FYI you'll need to sign all commits in order for me to approve. I will start reviewing in the meantime |
Add SAPAICore as a new model provider that routes through SAP AI Core's Orchestration Service, providing unified access to all model families (Anthropic, OpenAI, Gemini, Amazon, Meta, Mistral, DeepSeek, Qwen, etc.) via a single endpoint. Go changes: - Add ModelProviderSAPAICore enum and SAPAICoreConfig CRD struct - Add SAPAICore ADK model type with MarshalJSON/ParseModel/ModelToEmbeddingConfig - Add translateModel case with OAuth2 credential injection via K8s Secret - Add HTTP API support (create/update/list/get) with proper flatten - Add CEL validation rules (XValidation, apiKeySecretKey exemption) - Add 48 supported models from Orchestration Service - Add SAP_AI_CORE_CLIENT_ID/SECRET env var registration Python changes: - Add KAgentSAPAICoreLlm (BaseLlm) with Orchestration protocol support - Async OAuth2 token management (asyncio.to_thread, threading.Lock) - Cached httpx.AsyncClient with TLS configuration - Auto-discovery of orchestration deployment URL (1h TTL) - Automatic retry on 401/403/404/502/503/504 with cache invalidation - Full tool call support (streaming + non-streaming) - api_key_passthrough support via set_passthrough_key() UI changes: - Add SAPAICore to provider type union, combobox icons, and form handling - Add SAPAICoreConfigPayload for create/update flows Signed-off-by: Huimin Tai <huimin.tai@sap.com>
Implement SAPAICoreModel in the Go ADK runtime, enabling the golang-adk image to use SAP AI Core models via the Orchestration Service. - Add SAPAICoreModel with OAuth2 token management (thread-safe cache) - Add automatic orchestration deployment URL discovery (1h TTL) - Implement model.LLM interface (GenerateContent) with Orchestration protocol: modules format request, orchestration_result/final_result SSE stream parsing - Add retry logic on 401/403/404/502/503/504 with cache invalidation - Add *adk.SAPAICore case in CreateLLM() Verified with go vet, existing unit tests, and oneshot E2E against live SAP AI Core (both streaming and non-streaming). Signed-off-by: Huimin Tai <huimin.tai@sap.com>
7a61618 to
e65b343
Compare
Thanks ! All commits are signed off. Please review:> |
Signed-off-by: Lis <1205913055@qq.com>
Signed-off-by: Huimin Tai <huimin.tai@sap.com>
62a621a to
210a453
Compare
EItanya
left a comment
There was a problem hiding this comment.
Thanks for the contribution! This is a solid implementation — the Orchestration protocol handling, OAuth2 caching, and deployment URL discovery are all well thought out. A few things need to be addressed before we can merge.
Comment left by Claude on behalf of @EItanya
| } | ||
|
|
||
| // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SAPAICoreConfig. | ||
| func (in *SAPAICoreConfig) DeepCopy() *SAPAICoreConfig { |
There was a problem hiding this comment.
The DeepCopy() function for SAPAICoreConfig is truncated — it's missing the closing lines:
in.DeepCopyInto(out)
return out
}This will cause a compile error. Please regenerate with make -C go generate.
| @@ -0,0 +1,180 @@ | |||
| package models | |||
There was a problem hiding this comment.
There are no unit tests for any of the new code. At minimum we need coverage for:
genaiContentsToOrchTemplate— message conversion with text, tool calls, and function responsesbuildOrchestrationBody— request body construction with various config options_build_orchestration_template(Python equivalent)- Stream parsing (both
handleStreamand_stream_request) - OAuth token caching and expiry logic
- Deployment URL resolution and caching
Please add unit tests for the Go and Python implementations.
| }, nil | ||
| } | ||
|
|
||
| func (m *SAPAICoreModel) ensureToken() (string, error) { |
There was a problem hiding this comment.
ensureToken() and resolveDeploymentURL() don't accept a context.Context, so their HTTP calls can't be cancelled by the caller. If the auth server or deployment API is slow and the parent context is cancelled, these will hang until the 5-minute client timeout.
Please thread context.Context through these methods, similar to how doRequest uses http.NewRequestWithContext.
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| _token_cache: dict[tuple[str, str], tuple[str, float]] = {} |
There was a problem hiding this comment.
_token_cache and _token_cache_lock are module-level globals using a threading.Lock, but the class already has instance-level _token / _token_expires_at fields. This creates two parallel caching layers with subtle interactions — the module-level cache also leaks tokens across instances and is harder to test.
Consider consolidating to instance-level caching only, or at minimum document why the module-level cache exists (e.g., sharing tokens across multiple KAgentSAPAICoreLlm instances with the same auth URL).
| return KAgentSAPAICoreLlm( | ||
| model=model_config.model, | ||
| base_url=base_url, | ||
| resource_group=getattr(model_config, "resource_group", "default"), |
There was a problem hiding this comment.
Nit: resource_group and auth_url are defined directly on the SAPAICore class, so you can use model_config.resource_group and model_config.auth_url instead of getattr(). Direct attribute access is cleaner and type-safe.
| func isRetryableError(err error) bool { | ||
| if he, ok := err.(*orchHTTPError); ok { | ||
| switch he.StatusCode { | ||
| case 401, 403, 404, 502, 503, 504: |
There was a problem hiding this comment.
Nit: retrying on 404 makes sense for the completion endpoint (stale deployment URL), but if it comes from the auth endpoint it indicates misconfiguration that won't resolve on retry. Might be worth distinguishing which call returned the 404, or at minimum logging which URL returned the retryable error to aid debugging.
| LocalObjectReference: corev1.LocalObjectReference{ | ||
| Name: model.Spec.APIKeySecret, | ||
| }, | ||
| Key: "client_id", |
There was a problem hiding this comment.
The secret keys "client_id" and "client_secret" are hardcoded here. Other providers allow users to specify the key name via apiKeySecretKey. This is a reasonable choice for SAP OAuth (these names are standard), but it should be documented somewhere (CRD field description or provider help text) so users know what keys to put in their Secret.
…pcopy.go Merge conflict left the function body truncated, missing in.DeepCopyInto(out) and return out statements. Signed-off-by: Huimin Tai <huimin.tai@sap.com>
e940590 to
f126dbc
Compare


Add SAPAICore as a new model provider that routes through SAP AI Core's Orchestration Service, providing unified access to all model families (Anthropic, OpenAI, Gemini, Amazon, Meta, Mistral, DeepSeek, Qwen, etc.) via a single endpoint.
Go changes:
Python changes:
UI changes: