Merge pull request #41509 from BerriAI/litellm_mantle_gpt5_verbosity

fix(bedrock_mantle): accept and forward verbosity on gpt-5.x chat completions
This commit is contained in:
kerry-berri 2026-09-16 17:20:33 -07:00 committed by GitHub
commit 2e4840ee18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 78 additions and 5 deletions

View file

@ -502,7 +502,12 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
elif key == "response_format":
text_format = self._transform_response_format_to_text_format(value)
if text_format:
responses_api_request["text"] = text_format
responses_api_request["text"] = self._merge_text(responses_api_request, text_format)
elif key == "verbosity":
responses_api_request["text"] = self._merge_text(
responses_api_request,
MappingProxyType({"verbosity": value}), # pyright: ignore[reportUnknownArgumentType] # untyped value
)
elif key == "tool_choice":
responses_api_request["tool_choice"] = self._normalize_tool_choice_for_responses_api(value)
elif key == "stream_options":
@ -518,6 +523,19 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
elif key == "web_search_options":
self._add_web_search_tool(responses_api_request, value)
@staticmethod
def _merge_text(
responses_api_request: "ResponsesAPIOptionalRequestParams", update: Mapping[str, object]
) -> "ResponseText":
existing: Final = cast( # cast-ok: text field is a ResponseText | dict[str, Any] | None union
"dict[str, object]",
dict(responses_api_request).get("text") or {}, # mutable-ok: one-shot merge seed
)
return cast( # cast-ok: merged mapping is a valid ResponseText shape
"ResponseText",
{**existing, **update}, # mutable-ok: one-shot merged payload
)
def _build_sanitized_litellm_params(self, litellm_params: dict) -> dict[str, object]:
"""Build sanitized litellm_params with merged metadata."""
responses_optional_param_keys: Final = set(ResponsesAPIOptionalRequestParams.__annotations__.keys())

View file

@ -22,6 +22,7 @@ from litellm.llms.bedrock_mantle.common_utils import (
BEDROCK_MANTLE_DEFAULT_REGION,
BedrockMantleAuthMixin,
)
from litellm.llms.openai.chat.gpt_5_transformation import is_gpt_reasoning_series_name
from litellm.secret_managers.main import get_secret_str
from litellm.types.llms.openai import AllMessageValues
from litellm.types.router import GenericLiteLLMParams
@ -108,13 +109,22 @@ class BedrockMantleChatConfig(BedrockMantleAuthMixin, OpenAILikeChatConfig):
def get_supported_openai_params(self, model: str) -> list:
base_params: Final = super().get_supported_openai_params(model)
extra_params: Final = tuple(
param
for param, supported in (
("verbosity", is_gpt_reasoning_series_name(model)),
("reasoning_effort", self._supports_reasoning(model)),
)
if supported and param not in base_params
)
return [*base_params, *extra_params] # mutable-ok: fresh list required by the inherited signature
def _supports_reasoning(self, model: str) -> bool:
try:
if litellm.supports_reasoning(model=model, custom_llm_provider=self.custom_llm_provider):
if "reasoning_effort" not in base_params:
base_params.append("reasoning_effort")
return litellm.supports_reasoning(model=model, custom_llm_provider=self.custom_llm_provider)
except Exception as e:
verbose_logger.debug("BedrockMantleChatConfig: error checking reasoning support: %s", e)
return base_params
return False
def get_model_response_iterator(
self,

View file

@ -4287,3 +4287,36 @@ def test_system_string_after_a_developer_message_stays_in_input_in_client_order(
assert instructions is None
assert [item["role"] for item in input_items] == ["developer", "system", "user"]
assert input_items[1] == _system_input_item("Be brief.")
def test_map_optional_params_verbosity_merges_into_text():
"""Chat verbosity must land on Responses text.verbosity alongside text.format regardless of key order."""
from litellm.completion_extras.litellm_responses_transformation.transformation import (
LiteLLMResponsesTransformationHandler,
)
from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams
handler: Final = LiteLLMResponsesTransformationHandler()
responses_api_request = ResponsesAPIOptionalRequestParams()
handler._map_optional_params_to_responses_api_request(
{"verbosity": "low", "response_format": {"type": "json_object"}},
responses_api_request,
)
assert responses_api_request["text"]["verbosity"] == "low"
assert responses_api_request["text"]["format"]["type"] == "json_object"
reversed_request = ResponsesAPIOptionalRequestParams()
handler._map_optional_params_to_responses_api_request(
{"response_format": {"type": "json_object"}, "verbosity": "low"},
reversed_request,
)
assert reversed_request["text"]["verbosity"] == "low"
assert reversed_request["text"]["format"]["type"] == "json_object"
verbosity_only_request = ResponsesAPIOptionalRequestParams()
handler._map_optional_params_to_responses_api_request(
{"verbosity": "low"},
verbosity_only_request,
)
assert verbosity_only_request["text"] == {"verbosity": "low"}

View file

@ -257,6 +257,18 @@ class TestBedrockMantleConfig:
assert "temperature" in params
assert "stream" in params
assert "max_tokens" in params
assert "verbosity" not in params
def test_verbosity_passes_through_for_gpt_5_models(self):
cfg = BedrockMantleChatConfig()
assert "verbosity" in cfg.get_supported_openai_params("openai.gpt-5.6-sol")
optional_params = litellm.get_optional_params(
model="openai.gpt-5.6-sol",
custom_llm_provider="bedrock_mantle",
verbosity="low",
drop_params=False,
)
assert optional_params["verbosity"] == "low"
class TestBedrockMantleChatAuth: