-
-
Notifications
You must be signed in to change notification settings - Fork 10.9k
fix(bedrock): map guardrailConfig to InvokeModel guardrail headers #31985
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union, cast, get_args | ||
|
|
||
| import httpx | ||
| from pydantic import TypeAdapter, ValidationError | ||
|
|
||
| import litellm | ||
| from litellm._logging import verbose_logger | ||
|
|
@@ -24,6 +25,7 @@ | |
| HTTPHandler, | ||
| _get_httpx_client, | ||
| ) | ||
| from litellm.types.llms.bedrock import GuardrailConfigBlock | ||
| from litellm.types.llms.openai import AllMessageValues | ||
| from litellm.types.utils import ModelResponse, Usage | ||
| from litellm.utils import CustomStreamWrapper | ||
|
|
@@ -37,6 +39,38 @@ | |
|
|
||
| from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM | ||
|
|
||
| _GUARDRAIL_CONFIG_VALIDATOR: "TypeAdapter[GuardrailConfigBlock]" = TypeAdapter(GuardrailConfigBlock) | ||
|
|
||
| _GUARDRAIL_CONFIG_EXPECTED_FORMAT = ( | ||
| "{'guardrailIdentifier': str, 'guardrailVersion': str, 'trace': 'enabled'|'disabled'|'enabled_full'}" | ||
| ) | ||
|
|
||
|
|
||
| def _bedrock_invoke_guardrail_headers(raw_guardrail_config: object) -> "dict[str, str]": | ||
| try: | ||
| guardrail_config = _GUARDRAIL_CONFIG_VALIDATOR.validate_python(raw_guardrail_config) | ||
| except ValidationError as e: | ||
| raise BedrockError( | ||
| status_code=400, | ||
| message="Invalid guardrailConfig={}. Expected format: {}. Error: {}".format( | ||
| raw_guardrail_config, _GUARDRAIL_CONFIG_EXPECTED_FORMAT, e | ||
| ), | ||
| ) | ||
| if "guardrailIdentifier" not in guardrail_config: | ||
| raise BedrockError( | ||
| status_code=400, | ||
| message="guardrailConfig={} is missing 'guardrailIdentifier'. Expected format: {}".format( | ||
| raw_guardrail_config, _GUARDRAIL_CONFIG_EXPECTED_FORMAT | ||
| ), | ||
| ) | ||
| trace = guardrail_config.get("trace") | ||
| candidate_headers = { | ||
| "X-Amzn-Bedrock-GuardrailIdentifier": guardrail_config.get("guardrailIdentifier"), | ||
| "X-Amzn-Bedrock-GuardrailVersion": guardrail_config.get("guardrailVersion"), | ||
| "X-Amzn-Bedrock-Trace": trace.upper() if trace is not None else None, | ||
| } | ||
| return {name: value for name, value in candidate_headers.items() if value is not None} | ||
|
|
||
|
|
||
| class AmazonInvokeConfig(BaseConfig, BaseAWSLLM): | ||
| def __init__(self, **kwargs): | ||
|
|
@@ -390,7 +424,16 @@ def validate_environment( | |
| api_key: Optional[str] = None, | ||
| api_base: Optional[str] = None, | ||
| ) -> dict: | ||
| return headers | ||
| raw_guardrail_config = optional_params.pop("guardrailConfig", None) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if raw_guardrail_config is None: | ||
| return headers | ||
| existing_header_names = frozenset(name.lower() for name in headers) | ||
| guardrail_headers = { | ||
| name: value | ||
| for name, value in _bedrock_invoke_guardrail_headers(raw_guardrail_config).items() | ||
| if name.lower() not in existing_header_names | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Medium: Guardrail override via caller-supplied headers
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The precedence here is deliberate and flipping it would not create a trust boundary. At this layer there is no provenance left to tell trusted configuration from request data: both extra_headers and guardrailConfig can come from deployment config or from the request body, and the router merges request kwargs over deployment litellm_params ( Pinning a guardrail against untrusted callers is proxy-level policy (key/team guardrails, restricting which request params are allowed), not something this transformer can enforce Generated by Claude Code |
||
| } | ||
| return {**headers, **guardrail_headers} | ||
|
|
||
| def get_error_class( | ||
| self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.