-
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
feat(bedrock): support native structured outputs for Invoke API (Claude 4.5+) #23778
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
base: main
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| AmazonInvokeConfig, | ||
| ) | ||
| from litellm.llms.bedrock.common_utils import ( | ||
| add_additional_properties_to_schema, | ||
| get_anthropic_beta_from_headers, | ||
| remove_custom_field_from_tools, | ||
| ) | ||
|
|
@@ -21,6 +22,18 @@ | |
| else: | ||
| LiteLLMLoggingObj = Any | ||
|
|
||
| # Anthropic Claude models that support native structured outputs on Bedrock InvokeModel. | ||
| # Maintained separately from the Converse path's BEDROCK_NATIVE_STRUCTURED_OUTPUT_MODELS | ||
| # because Invoke and Converse have independent feature rollouts. | ||
| # Ref: https://docs.aws.amazon.com/bedrock/latest/userguide/structured-output.html | ||
| BEDROCK_INVOKE_NATIVE_STRUCTURED_OUTPUT_MODELS = { | ||
| "claude-haiku-4-5", | ||
| "claude-sonnet-4-5", | ||
| "claude-sonnet-4-6", | ||
| "claude-opus-4-5", | ||
| "claude-opus-4-6", | ||
| } | ||
|
Comment on lines
+29
to
+35
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. Hardcoded model list should live in Per project convention, model-capability flags should be stored in The same pattern exists for the Converse path ( Rule Used: What: Do not hardcode model-specific flags in the ... (source) |
||
|
|
||
|
|
||
| class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig): | ||
| """ | ||
|
|
@@ -49,34 +62,60 @@ def custom_llm_provider(self) -> Optional[str]: | |
| def get_supported_openai_params(self, model: str) -> List[str]: | ||
| return AnthropicConfig.get_supported_openai_params(self, model) | ||
|
|
||
| @staticmethod | ||
| def _supports_native_structured_outputs(model: str) -> bool: | ||
| """Check if the Bedrock Invoke model supports native structured outputs.""" | ||
| return any( | ||
| substring in model | ||
| for substring in BEDROCK_INVOKE_NATIVE_STRUCTURED_OUTPUT_MODELS | ||
| ) | ||
|
|
||
| def map_openai_params( | ||
| self, | ||
| non_default_params: dict, | ||
| optional_params: dict, | ||
| model: str, | ||
| drop_params: bool, | ||
| ) -> dict: | ||
| # Force tool-based structured outputs for Bedrock Invoke | ||
| # (similar to VertexAI fix in #19201) | ||
| # Bedrock Invoke doesn't support output_format parameter | ||
| original_model = model | ||
| if "response_format" in non_default_params: | ||
| # Use a model name that forces tool-based approach | ||
| response_format = non_default_params.get("response_format") | ||
|
|
||
| # Native path: build output_format directly for Bedrock-supported models | ||
| # (includes haiku-4-5 which the Anthropic parent doesn't know about). | ||
| if isinstance( | ||
| response_format, dict | ||
| ) and self._supports_native_structured_outputs(model): | ||
| _output_format = self.map_response_format_to_anthropic_output_format( | ||
| response_format | ||
| ) | ||
| if _output_format is not None: | ||
| optional_params["output_format"] = _output_format | ||
| optional_params["json_mode"] = True | ||
| remaining = { | ||
| k: v | ||
| for k, v in non_default_params.items() | ||
| if k != "response_format" | ||
| } | ||
| return AnthropicConfig.map_openai_params( | ||
| self, | ||
| remaining, | ||
| optional_params, | ||
| model, | ||
| drop_params, | ||
| ) | ||
|
|
||
| # Fallback: force tool-based structured outputs for unsupported models | ||
| # (or json_object without schema on a supported model). | ||
| if response_format is not None: | ||
| model = "claude-3-sonnet-20240229" | ||
|
Comment on lines
+106
to
109
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. Fallback silently overrides model for tool injection Setting If the parent's native-supported model set ever changes (e.g., adds
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. Agreed this is fragile. It's the pre-existing pattern from the code this PR refactored -- the previous implementation also overrode the model name the same way. Calling Open to revisiting if the maintainers want to refactor the parent class. |
||
|
|
||
| optional_params = AnthropicConfig.map_openai_params( | ||
| return AnthropicConfig.map_openai_params( | ||
| self, | ||
| non_default_params, | ||
| optional_params, | ||
| model, | ||
| drop_params, | ||
| ) | ||
|
|
||
| # Restore original model name | ||
| model = original_model | ||
|
|
||
| return optional_params | ||
|
|
||
| def transform_request( | ||
| self, | ||
| model: str, | ||
|
|
@@ -105,11 +144,30 @@ def transform_request( | |
|
|
||
| _anthropic_request.pop("model", None) | ||
| _anthropic_request.pop("stream", None) | ||
| # Bedrock Invoke doesn't support output_format parameter | ||
| _anthropic_request.pop("output_format", None) | ||
| # Bedrock Invoke doesn't support output_config parameter | ||
| # Fixes: https://github.com/BerriAI/litellm/issues/22797 | ||
| _anthropic_request.pop("output_config", None) | ||
|
|
||
| # Convert Anthropic output_format to Bedrock InvokeModel output_config.format | ||
| output_format = _anthropic_request.pop("output_format", None) | ||
| if ( | ||
| output_format | ||
| and isinstance(output_format, dict) | ||
| and output_format.get("type") == "json_schema" | ||
| ): | ||
| schema = output_format.get("schema", {}) | ||
| normalized_schema = add_additional_properties_to_schema(schema) | ||
| # Preserve existing output_config keys (e.g. effort from reasoning_effort) | ||
| output_config = _anthropic_request.get("output_config") or {} | ||
| output_config["format"] = { | ||
| "type": "json_schema", | ||
| "schema": normalized_schema, | ||
| } | ||
| _anthropic_request["output_config"] = output_config | ||
| else: | ||
| # Non-native path: strip output_config entirely. | ||
| # Bedrock Invoke rejects the key itself (not just sub-keys) with | ||
| # "extraneous key [output_config] is not permitted" for models | ||
| # that don't support native structured outputs. | ||
| # Fixes: https://github.com/BerriAI/litellm/issues/22797 | ||
| _anthropic_request.pop("output_config", None) | ||
| if "anthropic_version" not in _anthropic_request: | ||
| _anthropic_request["anthropic_version"] = self.anthropic_version | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded model list violates project policy
BEDROCK_INVOKE_NATIVE_STRUCTURED_OUTPUT_MODELSis a hardcoded set of model name substrings, which means every time AWS adds a new Claude model that supports native structured outputs on the Invoke API, users must upgrade LiteLLM to get support.The project convention (also violated by the existing
BEDROCK_NATIVE_STRUCTURED_OUTPUT_MODELSinconverse_transformation.py) is to store these flags inmodel_prices_and_context_window.jsonand read them viaget_model_info. This lets users pick up new model support without an SDK upgrade.The recommended fix is to:
"supports_bedrock_invoke_structured_outputs": truekey to each model entry inmodel_prices_and_context_window.json_supports_native_structured_outputswith a lookup throughget_model_info(similar to howsupports_reasoningis used for the reasoning effort feature)Note: the same issue exists in
converse_transformation.py'sBEDROCK_NATIVE_STRUCTURED_OUTPUT_MODELS, but that's pre-existing. Fixing it here would be a good opportunity to align with the policy.Rule Used: What: Do not hardcode model-specific flags in the ... (source)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid point. The
model_prices_and_context_window.jsonapproach would be cleaner long-term, but there are a couple of blockers for this PR:bedrock/invoke/entry and it's for an old model. The lookup would need to strip thebedrock/invoke/prefix, handle inference profile IDs (us.anthropic.claude-sonnet-4-6), and fall back to the base Bedrock entry. That model ID resolution logic doesn't exist yet.BEDROCK_NATIVE_STRUCTURED_OUTPUT_MODELS) uses the same hardcoded set pattern.Happy to follow up with a separate PR to migrate both Invoke and Converse sets to JSON lookups if the maintainers prefer that approach.