diff --git a/go/adk/pkg/agent/agent.go b/go/adk/pkg/agent/agent.go index 3d6159768..1a7953f99 100644 --- a/go/adk/pkg/agent/agent.go +++ b/go/adk/pkg/agent/agent.go @@ -285,9 +285,10 @@ func CreateLLM(ctx context.Context, m adk.Model, log logr.Logger) (adkmodel.LLM, } // Use Bedrock Converse API for ALL models (including Anthropic) cfg := &models.BedrockConfig{ - TransportConfig: transportConfigFromBase(m.BaseModel, nil), - Model: modelName, - Region: region, + TransportConfig: transportConfigFromBase(m.BaseModel, nil), + Model: modelName, + Region: region, + AdditionalModelRequestFields: m.AdditionalModelRequestFields, } return models.NewBedrockModelWithLogger(ctx, cfg, log) diff --git a/go/adk/pkg/models/bedrock.go b/go/adk/pkg/models/bedrock.go index fbadd7c24..69596e0a1 100644 --- a/go/adk/pkg/models/bedrock.go +++ b/go/adk/pkg/models/bedrock.go @@ -48,12 +48,12 @@ func sanitizeBedrockToolID(id string, idMap map[string]string, counter *int) str // BedrockConfig holds Bedrock configuration for the Converse API type BedrockConfig struct { TransportConfig - Model string - Region string - MaxTokens *int - Temperature *float64 - TopP *float64 - TopK *int + Model string + Region string + MaxTokens *int + Temperature *float64 + TopP *float64 + AdditionalModelRequestFields map[string]any } // BedrockModel implements model.LLM for Amazon Bedrock using the Converse API. @@ -158,26 +158,41 @@ func (m *BedrockModel) GenerateContent(ctx context.Context, req *model.LLMReques } } + // Build model-specific additional fields (Claude top_k, thinking, etc.) + additionalFields := m.buildAdditionalModelRequestFields() + // Set telemetry attributes telemetry.SetLLMRequestAttributes(ctx, modelName, req) if stream { - m.generateStreaming(ctx, modelName, messages, systemPrompt, inferenceConfig, toolConfig, yield) + m.generateStreaming(ctx, modelName, messages, systemPrompt, inferenceConfig, toolConfig, additionalFields, yield) } else { - m.generateNonStreaming(ctx, modelName, messages, systemPrompt, inferenceConfig, toolConfig, yield) + m.generateNonStreaming(ctx, modelName, messages, systemPrompt, inferenceConfig, toolConfig, additionalFields, yield) } } } +// buildAdditionalModelRequestFields returns a document.Interface containing +// model-specific parameters that are not part of InferenceConfiguration. +// The raw map is forwarded as-is to the Bedrock Converse API. +// Returns nil when no extra fields are configured. +func (m *BedrockModel) buildAdditionalModelRequestFields() document.Interface { + if len(m.Config.AdditionalModelRequestFields) == 0 { + return nil + } + return document.NewLazyDocument(m.Config.AdditionalModelRequestFields) +} + // generateStreaming handles streaming responses from Bedrock ConverseStream. // It properly handles both text and tool use content blocks during streaming. -func (m *BedrockModel) generateStreaming(ctx context.Context, modelId string, messages []types.Message, systemPrompt []types.SystemContentBlock, inferenceConfig *types.InferenceConfiguration, toolConfig *types.ToolConfiguration, yield func(*model.LLMResponse, error) bool) { +func (m *BedrockModel) generateStreaming(ctx context.Context, modelId string, messages []types.Message, systemPrompt []types.SystemContentBlock, inferenceConfig *types.InferenceConfiguration, toolConfig *types.ToolConfiguration, additionalFields document.Interface, yield func(*model.LLMResponse, error) bool) { output, err := m.Client.ConverseStream(ctx, &bedrockruntime.ConverseStreamInput{ - ModelId: aws.String(modelId), - Messages: messages, - System: systemPrompt, - InferenceConfig: inferenceConfig, - ToolConfig: toolConfig, + ModelId: aws.String(modelId), + Messages: messages, + System: systemPrompt, + InferenceConfig: inferenceConfig, + ToolConfig: toolConfig, + AdditionalModelRequestFields: additionalFields, }) if err != nil { @@ -323,13 +338,14 @@ func (tc *streamingToolCall) parseArgs() map[string]any { } // generateNonStreaming handles non-streaming responses from Bedrock Converse. -func (m *BedrockModel) generateNonStreaming(ctx context.Context, modelId string, messages []types.Message, systemPrompt []types.SystemContentBlock, inferenceConfig *types.InferenceConfiguration, toolConfig *types.ToolConfiguration, yield func(*model.LLMResponse, error) bool) { +func (m *BedrockModel) generateNonStreaming(ctx context.Context, modelId string, messages []types.Message, systemPrompt []types.SystemContentBlock, inferenceConfig *types.InferenceConfiguration, toolConfig *types.ToolConfiguration, additionalFields document.Interface, yield func(*model.LLMResponse, error) bool) { output, err := m.Client.Converse(ctx, &bedrockruntime.ConverseInput{ - ModelId: aws.String(modelId), - Messages: messages, - System: systemPrompt, - InferenceConfig: inferenceConfig, - ToolConfig: toolConfig, + ModelId: aws.String(modelId), + Messages: messages, + System: systemPrompt, + InferenceConfig: inferenceConfig, + ToolConfig: toolConfig, + AdditionalModelRequestFields: additionalFields, }) if err != nil { diff --git a/go/api/adk/types.go b/go/api/adk/types.go index 5274df27d..602a45798 100644 --- a/go/api/adk/types.go +++ b/go/api/adk/types.go @@ -247,6 +247,10 @@ type Bedrock struct { BaseModel // Region is the AWS region where the model is available Region string `json:"region,omitempty"` + // AdditionalModelRequestFields passes model-specific parameters to Bedrock's + // additionalModelRequestFields in the Converse API. Use this for provider-specific + // options outside the standard InferenceConfiguration block. + AdditionalModelRequestFields map[string]any `json:"additional_model_request_fields,omitempty"` } func (b *Bedrock) MarshalJSON() ([]byte, error) { diff --git a/go/api/config/crd/bases/kagent.dev_modelconfigs.yaml b/go/api/config/crd/bases/kagent.dev_modelconfigs.yaml index a0a1c0e44..e31173eb9 100644 --- a/go/api/config/crd/bases/kagent.dev_modelconfigs.yaml +++ b/go/api/config/crd/bases/kagent.dev_modelconfigs.yaml @@ -479,6 +479,14 @@ spec: bedrock: description: AWS Bedrock-specific configuration properties: + additionalModelRequestFields: + description: |- + AdditionalModelRequestFields passes model-specific parameters to Bedrock's + additionalModelRequestFields in the Converse API. Use this for provider-specific + options that are not part of the standard InferenceConfiguration block, such as + Claude extended thinking or top_k. Values are forwarded as-is to the API. + Example: {"top_k": 5, "thinking": {"type": "enabled", "budget_tokens": 16000}} + x-kubernetes-preserve-unknown-fields: true region: description: AWS region where the Bedrock model is available (e.g., us-east-1, us-west-2) diff --git a/go/api/v1alpha2/modelconfig_types.go b/go/api/v1alpha2/modelconfig_types.go index 3e72aa080..9c3896986 100644 --- a/go/api/v1alpha2/modelconfig_types.go +++ b/go/api/v1alpha2/modelconfig_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha2 import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -243,6 +244,15 @@ type BedrockConfig struct { // AWS region where the Bedrock model is available (e.g., us-east-1, us-west-2) // +required Region string `json:"region"` + + // AdditionalModelRequestFields passes model-specific parameters to Bedrock's + // additionalModelRequestFields in the Converse API. Use this for provider-specific + // options that are not part of the standard InferenceConfiguration block, such as + // Claude extended thinking or top_k. Values are forwarded as-is to the API. + // Example: {"top_k": 5, "thinking": {"type": "enabled", "budget_tokens": 16000}} + // +optional + // +kubebuilder:pruning:PreserveUnknownFields + AdditionalModelRequestFields *apiextensionsv1.JSON `json:"additionalModelRequestFields,omitempty"` } // SAPAICoreConfig contains SAP AI Core-specific configuration options. diff --git a/go/api/v1alpha2/zz_generated.deepcopy.go b/go/api/v1alpha2/zz_generated.deepcopy.go index fdd5df965..2b0ca618f 100644 --- a/go/api/v1alpha2/zz_generated.deepcopy.go +++ b/go/api/v1alpha2/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ package v1alpha2 import ( "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -323,6 +324,11 @@ func (in *BaseVertexAIConfig) DeepCopy() *BaseVertexAIConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BedrockConfig) DeepCopyInto(out *BedrockConfig) { *out = *in + if in.AdditionalModelRequestFields != nil { + in, out := &in.AdditionalModelRequestFields, &out.AdditionalModelRequestFields + *out = new(apiextensionsv1.JSON) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BedrockConfig. @@ -752,7 +758,7 @@ func (in *ModelConfigSpec) DeepCopyInto(out *ModelConfigSpec) { if in.Bedrock != nil { in, out := &in.Bedrock, &out.Bedrock *out = new(BedrockConfig) - **out = **in + (*in).DeepCopyInto(*out) } if in.SAPAICore != nil { in, out := &in.SAPAICore, &out.SAPAICore diff --git a/go/core/internal/controller/translator/agent/adk_api_translator.go b/go/core/internal/controller/translator/agent/adk_api_translator.go index d7399cdee..b11d030aa 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator.go @@ -7,6 +7,7 @@ import ( _ "embed" "encoding/binary" "encoding/hex" + "encoding/json" "errors" "fmt" "maps" @@ -683,12 +684,19 @@ func (a *adkApiTranslator) translateModel(ctx context.Context, namespace, modelC } } } + var additionalFields map[string]any + if model.Spec.Bedrock.AdditionalModelRequestFields != nil { + if err := json.Unmarshal(model.Spec.Bedrock.AdditionalModelRequestFields.Raw, &additionalFields); err != nil { + return nil, nil, nil, fmt.Errorf("failed to unmarshal bedrock additionalModelRequestFields: %w", err) + } + } bedrock := &adk.Bedrock{ BaseModel: adk.BaseModel{ Model: model.Spec.Model, Headers: model.Spec.DefaultHeaders, }, - Region: model.Spec.Bedrock.Region, + Region: model.Spec.Bedrock.Region, + AdditionalModelRequestFields: additionalFields, } // Populate TLS fields in BaseModel diff --git a/helm/kagent-crds/templates/kagent.dev_modelconfigs.yaml b/helm/kagent-crds/templates/kagent.dev_modelconfigs.yaml index a0a1c0e44..e31173eb9 100644 --- a/helm/kagent-crds/templates/kagent.dev_modelconfigs.yaml +++ b/helm/kagent-crds/templates/kagent.dev_modelconfigs.yaml @@ -479,6 +479,14 @@ spec: bedrock: description: AWS Bedrock-specific configuration properties: + additionalModelRequestFields: + description: |- + AdditionalModelRequestFields passes model-specific parameters to Bedrock's + additionalModelRequestFields in the Converse API. Use this for provider-specific + options that are not part of the standard InferenceConfiguration block, such as + Claude extended thinking or top_k. Values are forwarded as-is to the API. + Example: {"top_k": 5, "thinking": {"type": "enabled", "budget_tokens": 16000}} + x-kubernetes-preserve-unknown-fields: true region: description: AWS region where the Bedrock model is available (e.g., us-east-1, us-west-2) diff --git a/python/packages/kagent-adk/src/kagent/adk/models/_bedrock.py b/python/packages/kagent-adk/src/kagent/adk/models/_bedrock.py index 8a89617b4..e1ebdbfb6 100644 --- a/python/packages/kagent-adk/src/kagent/adk/models/_bedrock.py +++ b/python/packages/kagent-adk/src/kagent/adk/models/_bedrock.py @@ -195,6 +195,7 @@ class KAgentBedrockLlm(BaseLlm): """ extra_headers: Optional[dict[str, str]] = None + additional_model_request_fields: Optional[dict[str, Any]] = None model_config = {"arbitrary_types_allowed": True} @cached_property @@ -244,6 +245,9 @@ async def generate_content_async( if inference_config: kwargs["inferenceConfig"] = inference_config + if self.additional_model_request_fields: + kwargs["additionalModelRequestFields"] = self.additional_model_request_fields + def _run_converse_stream(**kw): resp = client.converse_stream(**kw) return list(resp.get("stream", [])) diff --git a/python/packages/kagent-adk/src/kagent/adk/types.py b/python/packages/kagent-adk/src/kagent/adk/types.py index 2ebc23adc..635fca9c9 100644 --- a/python/packages/kagent-adk/src/kagent/adk/types.py +++ b/python/packages/kagent-adk/src/kagent/adk/types.py @@ -235,6 +235,10 @@ class Gemini(BaseLLM): class Bedrock(BaseLLM): region: str | None = None + # additional_model_request_fields passes model-specific parameters to Bedrock's + # additionalModelRequestFields in the Converse API. Use this for provider-specific + # options outside the standard InferenceConfiguration block. + additional_model_request_fields: dict | None = None type: Literal["bedrock"] @@ -569,6 +573,7 @@ def _create_llm_from_model_config(model_config: ModelUnion): return KAgentBedrockLlm( model=model_config.model, extra_headers=extra_headers, + additional_model_request_fields=model_config.additional_model_request_fields, ) if model_config.type == "sap_ai_core": from .models._sap_ai_core import KAgentSAPAICoreLlm