BerriAI/litellm · error · BedrockError

Invalid guardrailConfig={raw_guardrail_config}. Expected for

Error message

Invalid guardrailConfig={raw_guardrail_config}. Expected format: {_GUARDRAIL_CONFIG_EXPECTED_FORMAT}. Error: {e}

What it means

Raised by _bedrock_invoke_guardrail_headers when the guardrailConfig parameter fails pydantic TypeAdapter validation against GuardrailConfigBlock. The message embeds the offending value, the expected shape ({'guardrailIdentifier': str, 'guardrailVersion': str, 'trace': 'enabled'|'disabled'|'enabled_full'}) and the pydantic ValidationError detail. It is a BedrockError 400 (bad request configuration).

Source

Thrown at litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py:53

    LiteLLMLoggingObj = _LiteLLMLoggingObj
else:
    LiteLLMLoggingObj = Any

from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM

_GUARDRAIL_CONFIG_VALIDATOR: Final["TypeAdapter[GuardrailConfigBlock]"] = TypeAdapter(GuardrailConfigBlock)

_GUARDRAIL_CONFIG_EXPECTED_FORMAT: Final = (
    "{'guardrailIdentifier': str, 'guardrailVersion': str, 'trace': 'enabled'|'disabled'|'enabled_full'}"
)


def _bedrock_invoke_guardrail_headers(raw_guardrail_config: object) -> "dict[str, str]":
    try:
        guardrail_config: Final = _GUARDRAIL_CONFIG_VALIDATOR.validate_python(raw_guardrail_config)
    except ValidationError as e:
        raise BedrockError(
            status_code=400,
            message=f"Invalid guardrailConfig={raw_guardrail_config}. Expected format: {_GUARDRAIL_CONFIG_EXPECTED_FORMAT}. Error: {e}",
        )
    if "guardrailIdentifier" not in guardrail_config:
        raise BedrockError(
            status_code=400,
            message=f"guardrailConfig={raw_guardrail_config} is missing 'guardrailIdentifier'. Expected format: {_GUARDRAIL_CONFIG_EXPECTED_FORMAT}",
        )
    trace: Final = guardrail_config.get("trace")
    candidate_headers: Final = {
        "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):

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Pass a plain dict with string values: {'guardrailIdentifier': '<id-or-arn>', 'guardrailVersion': '1', 'trace': 'enabled'}.
  2. Parse JSON strings first (json.loads) before handing them to litellm.
  3. Read the embedded pydantic error - it names exactly which key/type failed.
  4. Ensure guardrailVersion is quoted ('1', not 1) and trace is one of the three allowed strings.

Example fix

# before
resp = litellm.completion(
    model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
    messages=msgs,
    extra_body={"guardrailConfig": '{"guardrailIdentifier": "gr-123"}'},  # JSON string - invalid
)

# after
import json
resp = litellm.completion(
    model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
    messages=msgs,
    extra_body={"guardrailConfig": {
        "guardrailIdentifier": "gr-123",
        "guardrailVersion": "1",
        "trace": "enabled",
    }},
)
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED_TRACE = {"enabled", "disabled", "enabled_full"}
def validate_guardrail_config(cfg):
    if not isinstance(cfg, dict):
        raise ValueError("guardrailConfig must be a dict")
    if "guardrailIdentifier" not in cfg or not isinstance(cfg["guardrailIdentifier"], str):
        raise ValueError("guardrailIdentifier must be a str")
    if not isinstance(cfg.get("guardrailVersion", ""), str):
        raise ValueError("guardrailVersion must be a str (quote it: '1')")
    if cfg.get("trace") is not None and cfg["trace"] not in ALLOWED_TRACE:
        raise ValueError(f"trace must be one of {ALLOWED_TRACE}")
    return cfg

Type guard

def is_valid_guardrail_config(cfg: object) -> bool:
    return (
        isinstance(cfg, dict)
        and isinstance(cfg.get("guardrailIdentifier"), str)
        and isinstance(cfg.get("guardrailVersion", ""), str)
        and cfg.get("trace", "enabled") in {"enabled", "disabled", "enabled_full"}
    )

Try / catch

from litellm.exceptions import BedrockError
try:
    resp = litellm.completion(model="bedrock/<model>", messages=msgs, extra_body={"guardrailConfig": cfg})
except BedrockError as e:
    if e.status_code == 400 and "Invalid guardrailConfig" in str(e):
        raise ValueError(f"bad guardrail config from user input: {e.message}") from e
    raise

Prevention

When it happens

Trigger: Passing guardrail_config (or extra_body guardrailConfig) to a bedrock/ invoke call as a string, list, or dict with wrong types - e.g. guardrailVersion as an int, trace set to 'on', or the whole config passed as a JSON string instead of a dict.

Common situations: Copying AWS CLI JSON examples into the Python SDK without parsing them (left as a string), version drift where trace gained new enum values, or constructing the config from untyped user input.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/5a7ee38f38baf6c8. Report an issue: GitHub.