fix(bedrock): honor the json_mode kwarg on invoke Nova structured output and keep gpt-4o-mini off chat web search

This commit is contained in:
mateo-berri 2026-09-09 13:52:08 -07:00
parent 420282acb7
commit 5bdf45726d
5 changed files with 50 additions and 11 deletions

View file

@ -1805,6 +1805,7 @@ class AmazonConverseConfig(BaseConfig):
data=request_data,
messages=messages,
encoding=encoding,
json_mode=json_mode,
)
def _transform_reasoning_content(self, reasoning_content_blocks: list[BedrockConverseReasoningContentBlock]) -> str:
@ -2237,6 +2238,7 @@ class AmazonConverseConfig(BaseConfig):
data: dict | str,
messages: list,
encoding,
json_mode: bool | None = None,
) -> ModelResponse:
## LOGGING
if logging_obj is not None:
@ -2247,7 +2249,9 @@ class AmazonConverseConfig(BaseConfig):
additional_args={"complete_input_dict": data},
)
json_mode: Final[bool | None] = optional_params.get("json_mode", None)
resolved_json_mode: Final[bool | None] = (
json_mode if json_mode is not None else optional_params.get("json_mode", None)
)
## RESPONSE OBJECT
try:
completion_response: Final = ConverseResponseBlock(**response.json())
@ -2339,7 +2343,7 @@ class AmazonConverseConfig(BaseConfig):
chat_completion_message["thinking_blocks"] = self._transform_thinking_blocks(reasoningContentBlocks)
chat_completion_message["content"] = content_str
filtered_tools: Final = self._filter_json_mode_tools(
json_mode=json_mode,
json_mode=resolved_json_mode,
tools=tools,
chat_completion_message=chat_completion_message,
)
@ -2363,7 +2367,7 @@ class AmazonConverseConfig(BaseConfig):
# When json_mode filtered out all synthetic tool calls the response
# is plain content, not a pending tool invocation. Fix finish_reason
# so callers (e.g. OpenAI SDK) don't misinterpret it.
if json_mode and not filtered_tools and tools:
if resolved_json_mode and not filtered_tools and tools:
initial_finish_reason = "stop"
(

View file

@ -29407,8 +29407,7 @@
"search_context_size_high": 0.025,
"search_context_size_low": 0.025,
"search_context_size_medium": 0.025
},
"supports_web_search": true
}
},
"gpt-4o-mini-2024-07-18": {
"cache_read_input_token_cost": 7.5e-08,
@ -29436,8 +29435,7 @@
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true
"supports_vision": true
},
"gpt-4o-mini-audio-preview": {
"deprecation_date": "2026-05-07",

View file

@ -29407,8 +29407,7 @@
"search_context_size_high": 0.025,
"search_context_size_low": 0.025,
"search_context_size_medium": 0.025
},
"supports_web_search": true
}
},
"gpt-4o-mini-2024-07-18": {
"cache_read_input_token_cost": 7.5e-08,
@ -29436,8 +29435,7 @@
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true
"supports_vision": true
},
"gpt-4o-mini-audio-preview": {
"deprecation_date": "2026-05-07",

View file

@ -6727,3 +6727,41 @@ def test_forced_tool_choice_forwarded_on_converse_models_that_support_it(
)
assert result == {"any": {}}
def test_transform_response_honors_json_mode_kwarg_when_optional_params_lack_it():
response_json = {
"metrics": {"latencyMs": 900},
"output": {
"message": {
"content": [
{
"toolUse": {
"input": {"city": "Paris", "population": 2100000},
"name": "json_tool_call",
"toolUseId": "tooluse_invoke_nova_json",
}
}
],
"role": "assistant",
}
},
"stopReason": "tool_use",
"usage": {"inputTokens": 40, "outputTokens": 20, "totalTokens": 60},
}
raw_response = httpx.Response(200, json=response_json, request=httpx.Request("POST", "https://bedrock.test"))
logging_obj = MagicMock()
result = AmazonConverseConfig().transform_response(
model="bedrock/invoke/us.amazon.nova-micro-v1:0",
raw_response=raw_response,
model_response=ModelResponse(),
logging_obj=logging_obj,
request_data={},
messages=[],
optional_params={"tools": [{"type": "function", "function": {"name": "json_tool_call", "parameters": {}}}]},
litellm_params={},
encoding=None,
json_mode=True,
)
assert result.choices[0].message.tool_calls is None
assert json.loads(result.choices[0].message.content) == {"city": "Paris", "population": 2100000}

View file

@ -6,6 +6,7 @@ ai21.jamba-instruct-v1:0
twelvelabs.pegasus-1-2-v1:0
us.twelvelabs.pegasus-1-2-v1:0
eu.twelvelabs.pegasus-1-2-v1:0
global.twelvelabs.pegasus-1-2-v1:0
amazon.titan-text-express-v1
amazon.titan-text-lite-v1
amazon.titan-text-premier-v1:0