fix(bedrock): match any openai.gpt-<digit> model in the Converse reasoning gate

Backports the Converse part of fbc6fb56ae from main (PR #31884) together with the OpenAI
GPT reasoning gate this line never had (PR #38279: 74e86d3c0d, 9cc276a96e, 418012aac5):
reasoning_effort maps to reasoning.effort for openai.gpt-<digit> models and Anthropic's
thinking block is skipped for them. Without it a GPT-6 model fell through to Anthropic's
thinking block and Bedrock rejected the first real turn after a Claude Code /model switch
with 400 Unknown parameter: 'thinking'. #38279's cost-map JSON and cross-region test
changes, the Nova 2 tool_choice registry keys, and the invoke json_mode forwarding stay
on main
This commit is contained in:
mateo-berri 2026-09-19 12:55:31 -07:00
parent c32db1b512
commit 9f48ccaecf
3 changed files with 87 additions and 3 deletions

View file

@ -286,6 +286,10 @@ class AmazonConverseConfig(BaseConfig):
def _requires_min_max_tokens(model: str) -> bool:
return re.search(r"openai\.gpt-\d|xai\.grok-", model) is not None
@staticmethod
def _is_openai_gpt_reasoning_model(model: str) -> bool:
return re.search(r"openai\.gpt-\d", model) is not None
def _is_nova_2_model(self, model: str) -> bool:
"""
Check if the model is a Nova 2 model that supports reasoningConfig.
@ -416,12 +420,16 @@ class AmazonConverseConfig(BaseConfig):
Handle the reasoning_effort parameter based on the model type.
- GPT-OSS models: passed through unchanged via additionalModelRequestFields.
- OpenAI GPT-5.x and GPT-6 models: mapped to ``reasoning.effort`` via additionalModelRequestFields.
- Nova 2 models: transformed to reasoningConfig.
- Anthropic models: mapped to ``thinking`` (and ``output_config.effort`` on
adaptive Claude 4.6 / 4.7).
"""
if "gpt-oss" in model:
optional_params["reasoning_effort"] = reasoning_effort
elif self._is_openai_gpt_reasoning_model(model):
reasoning: Final[BedrockConverseGptReasoningEffortBlock] = {"effort": reasoning_effort}
optional_params["reasoning"] = reasoning # rebind-ok: out-param store like siblings
elif self._is_nova_2_model(model):
reasoning_config: Final = self._transform_reasoning_effort_to_reasoning_config(reasoning_effort)
optional_params.update(reasoning_config)
@ -553,7 +561,11 @@ class AmazonConverseConfig(BaseConfig):
# only anthropic and mistral support tool choice config. otherwise (E.g. cohere) will fail the call - https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ToolChoice.html
supported_params.append("tool_choice")
if "gpt-oss" in model:
if (
"gpt-oss" in model
or self._is_openai_gpt_reasoning_model(model)
or self._is_openai_gpt_reasoning_model(base_model)
):
supported_params.append("reasoning_effort")
elif self._is_nova_2_model(model):
# Nova 2 models support reasoning_effort (transformed to reasoningConfig)
@ -905,7 +917,7 @@ class AmazonConverseConfig(BaseConfig):
optional_params["_parallel_tool_use_config"] = {
"tool_choice": {"type": "auto", "disable_parallel_tool_use": not value}
}
if param == "thinking":
if param == "thinking" and not self._is_openai_gpt_reasoning_model(model):
if (
isinstance(value, dict)
and value.get("type") == "adaptive"

View file

@ -2,7 +2,7 @@ import json
from enum import Enum
from typing import TYPE_CHECKING, Any, Final, Literal
from typing_extensions import Required, TypedDict, override
from typing_extensions import ReadOnly, Required, TypedDict, override
from .openai import ChatCompletionToolCallChunk
@ -96,6 +96,10 @@ class BedrockConverseReasoningContentBlockDelta(TypedDict, total=False):
text: str
class BedrockConverseGptReasoningEffortBlock(TypedDict):
effort: ReadOnly[str]
class GuardrailConverseTextBlock(TypedDict, total=False):
text: str

View file

@ -288,6 +288,74 @@ def test_reasoning_with_forced_tool_choice_switches_to_auto():
assert optional_params["tool_choice"] == {"auto": {}}
@pytest.mark.parametrize(
"model",
[
"us.openai.gpt-5.6-sol",
"global.openai.gpt-5.6-terra",
"bedrock/converse/us.openai.gpt-5.6-luna",
"us.openai.gpt-6-astra",
"bedrock/converse/global.openai.gpt-6-astra",
],
)
def test_reasoning_effort_maps_to_reasoning_effort_for_openai_gpt5_converse(model):
"""OpenAI GPT-5.x on Bedrock Converse routes reasoning_effort to
``additionalModelRequestFields.reasoning.effort`` rather than Anthropic ``thinking``."""
config = AmazonConverseConfig()
assert "reasoning_effort" in config.get_supported_openai_params(model=model)
optional_params = config.map_openai_params(
non_default_params={"reasoning_effort": "high"},
optional_params={},
model=model,
drop_params=False,
)
assert optional_params["reasoning"] == {"effort": "high"}
assert "thinking" not in optional_params
assert "reasoning_effort" not in optional_params
_, additional_request_params, _, _ = config._prepare_request_params(optional_params, model)
assert additional_request_params["reasoning"] == {"effort": "high"}
assert "thinking" not in additional_request_params
@pytest.mark.parametrize(
"model",
[
"us.openai.gpt-5.6-sol",
"bedrock/converse/global.openai.gpt-5.6-luna",
"us.openai.gpt-6-astra",
],
)
def test_openai_gpt5_converse_never_forwards_thinking(model):
"""GPT-5.x on Converse must never send Anthropic ``thinking``/``output_config`` (Bedrock rejects them).
Regression: ``thinking`` is not advertised as supported, and even when supplied alongside
``reasoning_effort`` in either order it never survives into the request."""
config = AmazonConverseConfig()
supported = config.get_supported_openai_params(model=model)
assert "thinking" not in supported
assert "output_config" not in supported
thinking_block = {"type": "enabled", "budget_tokens": 2048}
for non_default_params in (
{"reasoning_effort": "high", "thinking": thinking_block},
{"thinking": thinking_block, "reasoning_effort": "high"},
):
optional_params = config.map_openai_params(
non_default_params=dict(non_default_params),
optional_params={},
model=model,
drop_params=False,
)
_, additional_request_params, _, _ = config._prepare_request_params(optional_params, model)
assert additional_request_params["reasoning"] == {"effort": "high"}
assert "thinking" not in additional_request_params
@pytest.mark.parametrize(
"model, param, value, expected_max_tokens",
[