fix: Bedrock Converse

This commit is contained in:
Ishaan Jaffer 2026-01-21 10:44:39 -08:00
parent 84d17d036d
commit bd9ecca85f
3 changed files with 73 additions and 7 deletions

View file

@ -45,6 +45,7 @@ class LiteLLMMessagesToCompletionTransformationHandler:
tools: Optional[List[Dict]] = None,
top_k: Optional[int] = None,
top_p: Optional[float] = None,
output_format: Optional[Dict] = None,
extra_kwargs: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Prepare kwargs for litellm.completion/acompletion"""
@ -76,6 +77,8 @@ class LiteLLMMessagesToCompletionTransformationHandler:
request_data["top_k"] = top_k
if top_p is not None:
request_data["top_p"] = top_p
if output_format:
request_data["output_format"] = output_format
openai_request = ANTHROPIC_ADAPTER.translate_completion_input_params(
request_data
@ -130,6 +133,7 @@ class LiteLLMMessagesToCompletionTransformationHandler:
tools: Optional[List[Dict]] = None,
top_k: Optional[int] = None,
top_p: Optional[float] = None,
output_format: Optional[Dict] = None,
**kwargs,
) -> Union[AnthropicMessagesResponse, AsyncIterator]:
"""Handle non-Anthropic models asynchronously using the adapter"""
@ -148,6 +152,7 @@ class LiteLLMMessagesToCompletionTransformationHandler:
tools=tools,
top_k=top_k,
top_p=top_p,
output_format=output_format,
extra_kwargs=kwargs,
)
)
@ -189,6 +194,7 @@ class LiteLLMMessagesToCompletionTransformationHandler:
tools: Optional[List[Dict]] = None,
top_k: Optional[int] = None,
top_p: Optional[float] = None,
output_format: Optional[Dict] = None,
_is_async: bool = False,
**kwargs,
) -> Union[
@ -212,6 +218,7 @@ class LiteLLMMessagesToCompletionTransformationHandler:
tools=tools,
top_k=top_k,
top_p=top_p,
output_format=output_format,
**kwargs,
)
@ -230,6 +237,7 @@ class LiteLLMMessagesToCompletionTransformationHandler:
tools=tools,
top_k=top_k,
top_p=top_p,
output_format=output_format,
extra_kwargs=kwargs,
)
)

View file

@ -172,7 +172,7 @@ class LiteLLMAnthropicMessagesAdapter:
"""
Which anthropic params, we need to translate to the openai format.
"""
return ["messages", "metadata", "system", "tool_choice", "tools", "thinking"]
return ["messages", "metadata", "system", "tool_choice", "tools", "thinking", "output_format"]
def translate_anthropic_messages_to_openai( # noqa: PLR0915
self,
@ -554,6 +554,42 @@ class LiteLLMAnthropicMessagesAdapter:
return new_tools
def translate_anthropic_output_format_to_openai(
self, output_format: Any
) -> Optional[Dict[str, Any]]:
"""
Translate Anthropic's output_format to OpenAI's response_format.
Anthropic output_format: {"type": "json_schema", "schema": {...}}
OpenAI response_format: {"type": "json_schema", "json_schema": {"name": "...", "schema": {...}}}
Args:
output_format: Anthropic output_format dict with 'type' and 'schema'
Returns:
OpenAI-compatible response_format dict, or None if invalid
"""
if not isinstance(output_format, dict):
return None
output_type = output_format.get("type")
if output_type != "json_schema":
return None
schema = output_format.get("schema")
if not schema:
return None
# Convert to OpenAI response_format structure
return {
"type": "json_schema",
"json_schema": {
"name": "structured_output",
"schema": schema,
"strict": True,
},
}
def translate_anthropic_to_openai(
self, anthropic_message_request: AnthropicMessagesRequest
) -> ChatCompletionRequest:
@ -636,6 +672,16 @@ class LiteLLMAnthropicMessagesAdapter:
if reasoning_effort:
new_kwargs["reasoning_effort"] = reasoning_effort
## CONVERT OUTPUT_FORMAT to RESPONSE_FORMAT
if "output_format" in anthropic_message_request:
output_format = anthropic_message_request["output_format"]
if output_format:
response_format = self.translate_anthropic_output_format_to_openai(
output_format=output_format
)
if response_format:
new_kwargs["response_format"] = response_format
translatable_params = self.translatable_anthropic_params()
for k, v in anthropic_message_request.items():
if k not in translatable_params: # pass remaining params as is

View file

@ -107,14 +107,26 @@ class BaseAnthropicMessagesStructuredOutputTest(ABC):
print(f"Response: {response}")
# Validate response structure
assert "content" in response
assert len(response["content"]) > 0
# Validate response structure - handle both dict and object responses
if isinstance(response, dict):
assert "content" in response
content_list = response["content"]
else:
assert hasattr(response, "content")
content_list = response.content
content = response["content"][0]
assert "text" in content
assert len(content_list) > 0
content = content_list[0]
# Handle both dict and object content blocks
if isinstance(content, dict):
assert "text" in content
response_text = content["text"]
else:
assert hasattr(content, "text")
response_text = content.text
response_text = content["text"]
print(f"Response text: {response_text}")
# The response should be valid JSON