Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 15 additions & 2 deletions go/api/v1alpha2/modelconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const (
)

// ModelProvider represents the model provider type
// +kubebuilder:validation:Enum=Anthropic;OpenAI;AzureOpenAI;Ollama;Gemini;GeminiVertexAI;AnthropicVertexAI
// +kubebuilder:validation:Enum=Anthropic;OpenAI;AzureOpenAI;Ollama;Gemini;GeminiVertexAI;AnthropicVertexAI;Bedrock
type ModelProvider string

const (
Expand All @@ -36,6 +36,7 @@ const (
ModelProviderGemini ModelProvider = "Gemini"
ModelProviderGeminiVertexAI ModelProvider = "GeminiVertexAI"
ModelProviderAnthropicVertexAI ModelProvider = "AnthropicVertexAI"
ModelProviderBedrock ModelProvider = "Bedrock"
)

type BaseVertexAIConfig struct {
Expand Down Expand Up @@ -210,6 +211,13 @@ type OllamaConfig struct {

type GeminiConfig struct{}

// BedrockConfig contains AWS Bedrock-specific configuration options.
type BedrockConfig struct {
// AWS region where the Bedrock model is available (e.g., us-east-1, us-west-2)
// +required
Region string `json:"region"`
}

// TLSConfig contains TLS/SSL configuration options for model provider connections.
// This enables agents to connect to internal LiteLLM gateways or other providers
// that use self-signed certificates or custom certificate authorities.
Expand Down Expand Up @@ -255,8 +263,9 @@ type TLSConfig struct {
// +kubebuilder:validation:XValidation:message="provider.gemini must be nil if the provider is not Gemini",rule="!(has(self.gemini) && self.provider != 'Gemini')"
// +kubebuilder:validation:XValidation:message="provider.geminiVertexAI must be nil if the provider is not GeminiVertexAI",rule="!(has(self.geminiVertexAI) && self.provider != 'GeminiVertexAI')"
// +kubebuilder:validation:XValidation:message="provider.anthropicVertexAI must be nil if the provider is not AnthropicVertexAI",rule="!(has(self.anthropicVertexAI) && self.provider != 'AnthropicVertexAI')"
// +kubebuilder:validation:XValidation:message="provider.bedrock must be nil if the provider is not Bedrock",rule="!(has(self.bedrock) && self.provider != 'Bedrock')"
// +kubebuilder:validation:XValidation:message="apiKeySecret must be set if apiKeySecretKey is set",rule="!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))"
// +kubebuilder:validation:XValidation:message="apiKeySecretKey must be set if apiKeySecret is set",rule="!(has(self.apiKeySecret) && !has(self.apiKeySecretKey))"
// +kubebuilder:validation:XValidation:message="apiKeySecretKey must be set if apiKeySecret is set (except for Bedrock provider)",rule="!(has(self.apiKeySecret) && !has(self.apiKeySecretKey) && self.provider != 'Bedrock')"
// +kubebuilder:validation:XValidation:message="caCertSecretKey requires caCertSecretRef",rule="!(has(self.tls) && has(self.tls.caCertSecretKey) && size(self.tls.caCertSecretKey) > 0 && (!has(self.tls.caCertSecretRef) || size(self.tls.caCertSecretRef) == 0))"
// +kubebuilder:validation:XValidation:message="caCertSecretKey requires caCertSecretRef (unless disableVerify is true)",rule="!(has(self.tls) && (!has(self.tls.disableVerify) || !self.tls.disableVerify) && has(self.tls.caCertSecretKey) && size(self.tls.caCertSecretKey) > 0 && (!has(self.tls.caCertSecretRef) || size(self.tls.caCertSecretRef) == 0))"
// +kubebuilder:validation:XValidation:message="caCertSecretRef requires caCertSecretKey (unless disableVerify is true)",rule="!(has(self.tls) && (!has(self.tls.disableVerify) || !self.tls.disableVerify) && has(self.tls.caCertSecretRef) && size(self.tls.caCertSecretRef) > 0 && (!has(self.tls.caCertSecretKey) || size(self.tls.caCertSecretKey) == 0))"
Expand Down Expand Up @@ -306,6 +315,10 @@ type ModelConfigSpec struct {
// +optional
AnthropicVertexAI *AnthropicVertexAIConfig `json:"anthropicVertexAI,omitempty"`

// AWS Bedrock-specific configuration
// +optional
Bedrock *BedrockConfig `json:"bedrock,omitempty"`

// TLS configuration for provider connections.
// Enables agents to connect to internal LiteLLM gateways or other providers
// that use self-signed certificates or custom certificate authorities.
Expand Down
20 changes: 20 additions & 0 deletions go/api/v1alpha2/zz_generated.deepcopy.go

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

19 changes: 17 additions & 2 deletions go/config/crd/bases/kagent.dev_modelconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,16 @@ spec:
- apiVersion
- azureEndpoint
type: object
bedrock:
description: AWS Bedrock-specific configuration
properties:
region:
description: AWS region where the Bedrock model is available (e.g.,
us-east-1, us-west-2)
type: string
required:
- region
type: object
defaultHeaders:
additionalProperties:
type: string
Expand Down Expand Up @@ -576,6 +586,7 @@ spec:
- Gemini
- GeminiVertexAI
- AnthropicVertexAI
- Bedrock
type: string
tls:
description: |-
Expand Down Expand Up @@ -636,10 +647,14 @@ spec:
- message: provider.anthropicVertexAI must be nil if the provider is not
AnthropicVertexAI
rule: '!(has(self.anthropicVertexAI) && self.provider != ''AnthropicVertexAI'')'
- message: provider.bedrock must be nil if the provider is not Bedrock
rule: '!(has(self.bedrock) && self.provider != ''Bedrock'')'
- message: apiKeySecret must be set if apiKeySecretKey is set
rule: '!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))'
- message: apiKeySecretKey must be set if apiKeySecret is set
rule: '!(has(self.apiKeySecret) && !has(self.apiKeySecretKey))'
- message: apiKeySecretKey must be set if apiKeySecret is set (except
for Bedrock provider)
rule: '!(has(self.apiKeySecret) && !has(self.apiKeySecretKey) && self.provider
!= ''Bedrock'')'
- message: caCertSecretKey requires caCertSecretRef
rule: '!(has(self.tls) && has(self.tls.caCertSecretKey) && size(self.tls.caCertSecretKey)
> 0 && (!has(self.tls.caCertSecretRef) || size(self.tls.caCertSecretRef)
Expand Down
29 changes: 29 additions & 0 deletions go/internal/adk/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const (
ModelTypeGeminiAnthropic = "gemini_anthropic"
ModelTypeOllama = "ollama"
ModelTypeGemini = "gemini"
ModelTypeBedrock = "bedrock"
)

func (o *OpenAI) MarshalJSON() ([]byte, error) {
Expand Down Expand Up @@ -185,6 +186,28 @@ func (g *Gemini) GetType() string {
return ModelTypeGemini
}

type Bedrock struct {
BaseModel
// Region is the AWS region where the model is available
Region string `json:"region,omitempty"`
}

func (b *Bedrock) MarshalJSON() ([]byte, error) {
data := map[string]any{
"type": ModelTypeBedrock,
"model": b.Model,
"headers": b.Headers,
}
if b.Region != "" {
data["region"] = b.Region
}
return json.Marshal(data)
}

func (b *Bedrock) GetType() string {
return ModelTypeBedrock
}

func ParseModel(bytes []byte) (Model, error) {
var model BaseModel
if err := json.Unmarshal(bytes, &model); err != nil {
Expand Down Expand Up @@ -233,6 +256,12 @@ func ParseModel(bytes []byte) (Model, error) {
return nil, err
}
return &ollama, nil
case ModelTypeBedrock:
var bedrock Bedrock
if err := json.Unmarshal(bytes, &bedrock); err != nil {
return nil, err
}
return &bedrock, nil
}
return nil, fmt.Errorf("unknown model type: %s", model.Type)
}
Expand Down
82 changes: 82 additions & 0 deletions go/internal/controller/translator/agent/adk_api_translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,88 @@ func (a *adkApiTranslator) translateModel(ctx context.Context, namespace, modelC
populateTLSFields(&gemini.BaseModel, model.Spec.TLS)

return gemini, modelDeploymentData, secretHashBytes, nil
case v1alpha2.ModelProviderBedrock:
if model.Spec.Bedrock == nil {
return nil, nil, nil, fmt.Errorf("bedrock model config is required")
}

// Set AWS region (always required)
modelDeploymentData.EnvVars = append(modelDeploymentData.EnvVars, corev1.EnvVar{
Name: "AWS_REGION",
Value: model.Spec.Bedrock.Region,
})

// If AWS_BEARER_TOKEN_BEDROCK key exists: use bearer token auth
// Otherwise, use IAM credentials
if model.Spec.APIKeySecret != "" {
secret := &corev1.Secret{}
if err := a.kube.Get(ctx, types.NamespacedName{Namespace: namespace, Name: model.Spec.APIKeySecret}, secret); err != nil {
return nil, nil, nil, fmt.Errorf("failed to get Bedrock credentials secret: %w", err)
}

if _, hasBearerToken := secret.Data["AWS_BEARER_TOKEN_BEDROCK"]; hasBearerToken {
modelDeploymentData.EnvVars = append(modelDeploymentData.EnvVars, corev1.EnvVar{
Name: "AWS_BEARER_TOKEN_BEDROCK",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: model.Spec.APIKeySecret,
},
Key: "AWS_BEARER_TOKEN_BEDROCK",
},
},
})
} else {
modelDeploymentData.EnvVars = append(modelDeploymentData.EnvVars, corev1.EnvVar{
Name: "AWS_ACCESS_KEY_ID",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: model.Spec.APIKeySecret,
},
Key: "AWS_ACCESS_KEY_ID",
},
},
})
modelDeploymentData.EnvVars = append(modelDeploymentData.EnvVars, corev1.EnvVar{
Name: "AWS_SECRET_ACCESS_KEY",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: model.Spec.APIKeySecret,
},
Key: "AWS_SECRET_ACCESS_KEY",
},
},
})
// AWS_SESSION_TOKEN is optional, only needed for temporary/SSO credentials
if _, hasSessionToken := secret.Data["AWS_SESSION_TOKEN"]; hasSessionToken {
modelDeploymentData.EnvVars = append(modelDeploymentData.EnvVars, corev1.EnvVar{
Name: "AWS_SESSION_TOKEN",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: model.Spec.APIKeySecret,
},
Key: "AWS_SESSION_TOKEN",
},
},
})
}
}
}
bedrock := &adk.Bedrock{
BaseModel: adk.BaseModel{
Model: model.Spec.Model,
Headers: model.Spec.DefaultHeaders,
},
Region: model.Spec.Bedrock.Region,
}

// Populate TLS fields in BaseModel
populateTLSFields(&bedrock.BaseModel, model.Spec.TLS)

return bedrock, modelDeploymentData, secretHashBytes, nil
}

return nil, nil, nil, fmt.Errorf("unknown model provider: %s", model.Spec.Provider)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
operation: translateAgent
targetObject: bedrock-agent
namespace: test
objects:
- apiVersion: v1
kind: Secret
metadata:
name: bedrock-credentials
namespace: test
data:
AWS_ACCESS_KEY_ID: QUtJQVRFU1RLRVk= # base64 encoded "AKIATESTKEY"
AWS_SECRET_ACCESS_KEY: dGVzdHNlY3JldGtleQ== # base64 encoded "testsecretkey"
- apiVersion: kagent.dev/v1alpha2
kind: ModelConfig
metadata:
name: bedrock-model
namespace: test
spec:
provider: Bedrock
model: us.anthropic.claude-sonnet-4-20250514-v1:0
apiKeySecret: bedrock-credentials
bedrock:
region: us-east-1
- apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: bedrock-agent
namespace: test
spec:
type: Declarative
declarative:
description: An agent using AWS Bedrock with Claude
systemMessage: You are a helpful AI assistant running on AWS Bedrock.
modelConfig: bedrock-model
tools: []
Loading
Loading