Implement requestMetadata support for Bedrock Converse API

- Add requestMetadata field to CommonRequestObject type definition
- Support request_metadata parameter in get_supported_openai_params
- Add comprehensive validation for AWS Bedrock constraints:
  * Maximum 16 key-value pairs
  * Key length 1-256 characters
  * Value length 0-256 characters
  * Character set validation [a-zA-Z0-9\s:_@0=/+,.-]
- Transform request_metadata to top-level requestMetadata field in API request
- Maintain backward compatibility with existing functionality
- Enable metadata logging and traceability for multi-cloud environments
This commit is contained in:
Tim Elfrink 2025-09-15 10:58:58 +02:00
parent 1d85623b66
commit a34bb6f6d3
2 changed files with 87 additions and 1 deletions

View File

@ -175,6 +175,77 @@ class AmazonConverseConfig(BaseConfig):
and v is not None
}
def _validate_request_metadata(self, metadata: dict) -> None:
"""
Validate requestMetadata according to AWS Bedrock Converse API constraints.
Constraints:
- Maximum of 16 items
- Keys: 1-256 characters, pattern [a-zA-Z0-9\\s:_@$#=/+,-.]{1,256}
- Values: 0-256 characters, pattern [a-zA-Z0-9\\s:_@$#=/+,-.]{0,256}
"""
import re
if not isinstance(metadata, dict):
raise litellm.exceptions.BadRequestError(
message="request_metadata must be a dictionary",
model="bedrock",
llm_provider="bedrock",
)
if len(metadata) > 16:
raise litellm.exceptions.BadRequestError(
message="request_metadata can contain a maximum of 16 items",
model="bedrock",
llm_provider="bedrock",
)
key_pattern = re.compile(r'^[a-zA-Z0-9\s:_@$#=/+,.-]{1,256}$')
value_pattern = re.compile(r'^[a-zA-Z0-9\s:_@$#=/+,.-]{0,256}$')
for key, value in metadata.items():
if not isinstance(key, str):
raise litellm.exceptions.BadRequestError(
message="request_metadata keys must be strings",
model="bedrock",
llm_provider="bedrock",
)
if not isinstance(value, str):
raise litellm.exceptions.BadRequestError(
message="request_metadata values must be strings",
model="bedrock",
llm_provider="bedrock",
)
if len(key) == 0 or len(key) > 256:
raise litellm.exceptions.BadRequestError(
message="request_metadata key length must be 1-256 characters",
model="bedrock",
llm_provider="bedrock",
)
if len(value) > 256:
raise litellm.exceptions.BadRequestError(
message="request_metadata value length must be 0-256 characters",
model="bedrock",
llm_provider="bedrock",
)
if not key_pattern.match(key):
raise litellm.exceptions.BadRequestError(
message=f"request_metadata key '{key}' contains invalid characters. Allowed: [a-zA-Z0-9\\s:_@$#=/+,.-]",
model="bedrock",
llm_provider="bedrock",
)
if not value_pattern.match(value):
raise litellm.exceptions.BadRequestError(
message=f"request_metadata value '{value}' contains invalid characters. Allowed: [a-zA-Z0-9\\s:_@$#=/+,.-]",
model="bedrock",
llm_provider="bedrock",
)
def get_supported_openai_params(self, model: str) -> List[str]:
from litellm.utils import supports_function_calling
@ -188,6 +259,7 @@ class AmazonConverseConfig(BaseConfig):
"top_p",
"extra_headers",
"response_format",
"request_metadata",
]
if (
@ -497,6 +569,10 @@ class AmazonConverseConfig(BaseConfig):
optional_params["thinking"] = AnthropicConfig._map_reasoning_effort(
value
)
if param == "request_metadata":
if value is not None:
self._validate_request_metadata(value)
optional_params["request_metadata"] = value
# Only update thinking tokens for non-GPT-OSS models
if "gpt-oss" not in model:
@ -727,6 +803,11 @@ class AmazonConverseConfig(BaseConfig):
)
inference_params.pop("json_mode", None) # used for handling json_schema
# Extract request_metadata before processing other parameters
request_metadata = inference_params.pop("request_metadata", None)
if request_metadata is not None:
self._validate_request_metadata(request_metadata)
# keep supported params in 'inference_params', and set all model-specific params in 'additional_request_params'
additional_request_params = {
k: v for k, v in inference_params.items() if k not in total_supported_params
@ -813,6 +894,10 @@ class AmazonConverseConfig(BaseConfig):
if bedrock_tool_config is not None:
data["toolConfig"] = bedrock_tool_config
# Request Metadata (top-level field)
if request_metadata is not None:
data["requestMetadata"] = request_metadata
return data
async def _async_transform_request(

View File

@ -1,5 +1,5 @@
import json
from typing import Any, List, Literal, Optional, Union
from typing import Any, Dict, List, Literal, Optional, Union
from typing_extensions import (
TYPE_CHECKING,
@ -227,6 +227,7 @@ class CommonRequestObject(
toolConfig: ToolConfigBlock
guardrailConfig: Optional[GuardrailConfigBlock]
performanceConfig: Optional[PerformanceConfigBlock]
requestMetadata: Optional[Dict[str, str]]
class RequestObject(CommonRequestObject, total=False):