mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
feat(bedrock): default to native structured output on Opus 5 and Opus 4.8 (#50222)
* feat(bedrock): enable native structured output for Opus 5 models Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(bedrock): keep govcloud and bare model rows off native output Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(bedrock): retry rejected native schemas with the JSON tool on default-native models Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(bedrock): keep the rejection retry on the unforceable-tool native path Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(bedrock): retry rejected schemas on the Messages path for default-native models Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(bedrock): keep JSON-tool streaming coverage on unchanged route Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * style(bedrock): format Messages native output retry Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(bedrock): allow profile-only native output metadata Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(bedrock): only disable default-native output on schema-rejection retries Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(bedrock): type fixture parameters in native output retry tests Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(bedrock): retry with the JSON tool when Bedrock rejects a schema's grammar size Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(bedrock): drop strict tools after a native-output retry hits the grammar limit Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * style(bedrock): format native output fallback Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
7b550cdd94
commit
14d50ed40d
16 changed files with 437 additions and 76 deletions
|
|
@ -77,6 +77,7 @@ from litellm.llms.bedrock.common_utils import (
|
||||||
bedrock_model_supports_regex_lookaround,
|
bedrock_model_supports_regex_lookaround,
|
||||||
bedrock_native_output_format,
|
bedrock_native_output_format,
|
||||||
bedrock_native_output_is_opt_in,
|
bedrock_native_output_is_opt_in,
|
||||||
|
bedrock_native_output_retry_requested,
|
||||||
bedrock_supports_tool_search,
|
bedrock_supports_tool_search,
|
||||||
deployment_model_info,
|
deployment_model_info,
|
||||||
requests_output_config_format,
|
requests_output_config_format,
|
||||||
|
|
@ -1425,6 +1426,16 @@ class AmazonConverseConfig(BaseConfig):
|
||||||
deployment_model_info(litellm_params),
|
deployment_model_info(litellm_params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def uses_native_output(
|
||||||
|
self, model: str, optional_params: Mapping[str, object], litellm_params: Mapping[str, object]
|
||||||
|
) -> bool:
|
||||||
|
return bedrock_native_output_retry_requested(
|
||||||
|
bedrock_capability_model(model, litellm_params.get("base_model")),
|
||||||
|
optional_params,
|
||||||
|
self.custom_llm_provider or "bedrock_converse",
|
||||||
|
deployment_model_info(litellm_params),
|
||||||
|
)
|
||||||
|
|
||||||
def update_optional_params_with_thinking_tokens(self, non_default_params: dict, optional_params: dict):
|
def update_optional_params_with_thinking_tokens(self, non_default_params: dict, optional_params: dict):
|
||||||
"""
|
"""
|
||||||
Handles scenario where max tokens is not specified. For anthropic models (anthropic api/bedrock/vertex ai), this requires having the max tokens being set and being greater than the thinking token budget.
|
Handles scenario where max tokens is not specified. For anthropic models (anthropic api/bedrock/vertex ai), this requires having the max tokens being set and being greater than the thinking token budget.
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ from litellm.llms.bedrock.common_utils import (
|
||||||
bedrock_capability_model,
|
bedrock_capability_model,
|
||||||
bedrock_chat_uses_native_structured_output,
|
bedrock_chat_uses_native_structured_output,
|
||||||
bedrock_cleaned_output_schema,
|
bedrock_cleaned_output_schema,
|
||||||
|
bedrock_native_output_retry_requested,
|
||||||
bedrock_supports_tool_search,
|
bedrock_supports_tool_search,
|
||||||
deployment_model_info,
|
deployment_model_info,
|
||||||
get_anthropic_beta_from_headers,
|
get_anthropic_beta_from_headers,
|
||||||
|
|
@ -161,6 +162,13 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig):
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return requests_opt_in_native_output(model, optional_params, deployment_model_info(litellm_params))
|
return requests_opt_in_native_output(model, optional_params, deployment_model_info(litellm_params))
|
||||||
|
|
||||||
|
def uses_native_output(
|
||||||
|
self, model: str, optional_params: Mapping[str, object], litellm_params: Mapping[str, object]
|
||||||
|
) -> bool:
|
||||||
|
return bedrock_native_output_retry_requested(
|
||||||
|
model, optional_params, model_info=deployment_model_info(litellm_params)
|
||||||
|
)
|
||||||
|
|
||||||
def map_openai_params(
|
def map_openai_params(
|
||||||
self,
|
self,
|
||||||
non_default_params: dict,
|
non_default_params: dict,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from collections.abc import Callable, Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
|
from dataclasses import dataclass
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Final, TypeAlias
|
from typing import Final, TypeAlias
|
||||||
|
|
||||||
|
|
@ -6,6 +7,7 @@ from litellm.llms.anthropic.common_utils import is_claude_strict_tools_rejection
|
||||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||||
from litellm.llms.bedrock.common_utils import (
|
from litellm.llms.bedrock.common_utils import (
|
||||||
NativeOutputFallback,
|
NativeOutputFallback,
|
||||||
|
bedrock_strict_tools_fallback,
|
||||||
is_bedrock_native_output_rejection,
|
is_bedrock_native_output_rejection,
|
||||||
warn_bedrock_native_output_rejected,
|
warn_bedrock_native_output_rejected,
|
||||||
warn_bedrock_strict_tools_dropped,
|
warn_bedrock_strict_tools_dropped,
|
||||||
|
|
@ -17,7 +19,15 @@ NativeOutputResponse: TypeAlias = ModelResponse | CustomStreamWrapper
|
||||||
NativeOutputAttemptResult: TypeAlias = NativeOutputResponse | Coroutine[object, object, NativeOutputResponse]
|
NativeOutputAttemptResult: TypeAlias = NativeOutputResponse | Coroutine[object, object, NativeOutputResponse]
|
||||||
NativeOutputAttempt: TypeAlias = Callable[[NativeOutputFallback | None], NativeOutputAttemptResult]
|
NativeOutputAttempt: TypeAlias = Callable[[NativeOutputFallback | None], NativeOutputAttemptResult]
|
||||||
NativeOutputFallbackFactory: TypeAlias = Callable[[], NativeOutputFallback | None]
|
NativeOutputFallbackFactory: TypeAlias = Callable[[], NativeOutputFallback | None]
|
||||||
_RetryFor: TypeAlias = Callable[[BaseLLMException], NativeOutputFallback | None]
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class _SelectedFallback:
|
||||||
|
fallback: NativeOutputFallback
|
||||||
|
native_output_retry: bool
|
||||||
|
|
||||||
|
|
||||||
|
_RetryFor: TypeAlias = Callable[[BaseLLMException], _SelectedFallback | None]
|
||||||
|
|
||||||
|
|
||||||
def _native_output_retry(
|
def _native_output_retry(
|
||||||
|
|
@ -47,17 +57,42 @@ def _fallback_for(
|
||||||
fallback: NativeOutputFallbackFactory | None,
|
fallback: NativeOutputFallbackFactory | None,
|
||||||
strict_tools_fallback: NativeOutputFallbackFactory | None,
|
strict_tools_fallback: NativeOutputFallbackFactory | None,
|
||||||
model: str,
|
model: str,
|
||||||
) -> NativeOutputFallback | None:
|
) -> _SelectedFallback | None:
|
||||||
native_output_retry: Final = _native_output_retry(error, fallback, model)
|
native_output_retry: Final = _native_output_retry(error, fallback, model)
|
||||||
if native_output_retry is not None:
|
if native_output_retry is not None:
|
||||||
return native_output_retry
|
return _SelectedFallback(native_output_retry, native_output_retry=True)
|
||||||
return _strict_tools_retry(error, strict_tools_fallback, model)
|
strict_tools_retry: Final = _strict_tools_retry(error, strict_tools_fallback, model)
|
||||||
|
if strict_tools_retry is None:
|
||||||
|
return None
|
||||||
|
return _SelectedFallback(strict_tools_retry, native_output_retry=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _strict_tools_retry_after_native_output_retry(
|
||||||
|
error: BaseLLMException,
|
||||||
|
retry: _SelectedFallback,
|
||||||
|
strict_tools_fallback: NativeOutputFallbackFactory | None,
|
||||||
|
model: str,
|
||||||
|
) -> NativeOutputFallback | None:
|
||||||
|
if (
|
||||||
|
not retry.native_output_retry
|
||||||
|
or strict_tools_fallback is None
|
||||||
|
or not is_claude_strict_tools_rejection(error.status_code, error.message)
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
composed_retry: Final = bedrock_strict_tools_fallback(
|
||||||
|
model, retry.fallback.optional_params, retry.fallback.litellm_params
|
||||||
|
)
|
||||||
|
if composed_retry is not None:
|
||||||
|
warn_bedrock_strict_tools_dropped(model)
|
||||||
|
return composed_retry
|
||||||
|
|
||||||
|
|
||||||
async def _await_with_native_output_fallback(
|
async def _await_with_native_output_fallback(
|
||||||
first_attempt: Coroutine[object, object, NativeOutputResponse],
|
first_attempt: Coroutine[object, object, NativeOutputResponse],
|
||||||
attempt: NativeOutputAttempt,
|
attempt: NativeOutputAttempt,
|
||||||
retry_for: _RetryFor,
|
retry_for: _RetryFor,
|
||||||
|
strict_tools_fallback: NativeOutputFallbackFactory | None,
|
||||||
|
model: str,
|
||||||
) -> NativeOutputResponse:
|
) -> NativeOutputResponse:
|
||||||
try:
|
try:
|
||||||
return await first_attempt
|
return await first_attempt
|
||||||
|
|
@ -65,8 +100,17 @@ async def _await_with_native_output_fallback(
|
||||||
retry: Final = retry_for(error)
|
retry: Final = retry_for(error)
|
||||||
if retry is None:
|
if retry is None:
|
||||||
raise
|
raise
|
||||||
second_attempt: Final = attempt(retry)
|
try:
|
||||||
return await second_attempt if isinstance(second_attempt, Coroutine) else second_attempt
|
second_attempt: Final = attempt(retry.fallback)
|
||||||
|
return await second_attempt if isinstance(second_attempt, Coroutine) else second_attempt
|
||||||
|
except BaseLLMException as error:
|
||||||
|
composed_retry: Final = _strict_tools_retry_after_native_output_retry(
|
||||||
|
error, retry, strict_tools_fallback, model
|
||||||
|
)
|
||||||
|
if composed_retry is None:
|
||||||
|
raise
|
||||||
|
third_attempt: Final = attempt(composed_retry)
|
||||||
|
return await third_attempt if isinstance(third_attempt, Coroutine) else third_attempt
|
||||||
|
|
||||||
|
|
||||||
def call_with_native_output_fallback(
|
def call_with_native_output_fallback(
|
||||||
|
|
@ -86,7 +130,15 @@ def call_with_native_output_fallback(
|
||||||
retry: Final = retry_for(error)
|
retry: Final = retry_for(error)
|
||||||
if retry is None:
|
if retry is None:
|
||||||
raise
|
raise
|
||||||
return attempt(retry)
|
try:
|
||||||
|
return attempt(retry.fallback)
|
||||||
|
except BaseLLMException as retry_error:
|
||||||
|
composed_retry: Final = _strict_tools_retry_after_native_output_retry(
|
||||||
|
retry_error, retry, strict_tools_fallback, model
|
||||||
|
)
|
||||||
|
if composed_retry is None:
|
||||||
|
raise
|
||||||
|
return attempt(composed_retry)
|
||||||
if isinstance(first_attempt, Coroutine):
|
if isinstance(first_attempt, Coroutine):
|
||||||
return _await_with_native_output_fallback(first_attempt, attempt, retry_for)
|
return _await_with_native_output_fallback(first_attempt, attempt, retry_for, strict_tools_fallback, model)
|
||||||
return first_attempt
|
return first_attempt
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,15 @@ def _bedrock_model_supports(model: str, key: str, custom_llm_provider: str = "be
|
||||||
return supports_factory(model=model, custom_llm_provider=custom_llm_provider, key=key)
|
return supports_factory(model=model, custom_llm_provider=custom_llm_provider, key=key)
|
||||||
|
|
||||||
|
|
||||||
|
def _bedrock_native_output_enabled(
|
||||||
|
model: str, custom_llm_provider: str, model_info: Mapping[str, object] | None
|
||||||
|
) -> bool:
|
||||||
|
deployment_flag: Final = (model_info or {}).get("supports_native_structured_output")
|
||||||
|
if isinstance(deployment_flag, bool):
|
||||||
|
return deployment_flag
|
||||||
|
return _bedrock_model_supports(model, "supports_native_structured_output", custom_llm_provider)
|
||||||
|
|
||||||
|
|
||||||
_DEPLOYMENT_MODEL_INFO: Final = TypeAdapter(dict[str, object])
|
_DEPLOYMENT_MODEL_INFO: Final = TypeAdapter(dict[str, object])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -312,9 +321,9 @@ def _bedrock_native_output_opt_in(
|
||||||
def bedrock_supports_native_structured_output(
|
def bedrock_supports_native_structured_output(
|
||||||
model: str, custom_llm_provider: str = "bedrock", model_info: Mapping[str, object] | None = None
|
model: str, custom_llm_provider: str = "bedrock", model_info: Mapping[str, object] | None = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return _bedrock_model_supports(
|
return _bedrock_native_output_enabled(model, custom_llm_provider, model_info) or _bedrock_native_output_opt_in(
|
||||||
model, "supports_native_structured_output", custom_llm_provider
|
model, custom_llm_provider, model_info
|
||||||
) or _bedrock_native_output_opt_in(model, custom_llm_provider, model_info)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _bedrock_map_native_output_flag(model: str) -> object:
|
def _bedrock_map_native_output_flag(model: str) -> object:
|
||||||
|
|
@ -349,7 +358,7 @@ def bedrock_chat_opts_into_native_structured_output(
|
||||||
custom_llm_provider: str = "bedrock",
|
custom_llm_provider: str = "bedrock",
|
||||||
model_info: Mapping[str, object] | None = None,
|
model_info: Mapping[str, object] | None = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return _bedrock_model_supports(model, "supports_native_structured_output", custom_llm_provider) or (
|
return _bedrock_native_output_enabled(model, custom_llm_provider, model_info) or (
|
||||||
thinking_requested and _bedrock_native_output_opt_in(model, custom_llm_provider, model_info)
|
thinking_requested and _bedrock_native_output_opt_in(model, custom_llm_provider, model_info)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -370,15 +379,15 @@ def bedrock_chat_uses_native_structured_output(
|
||||||
def bedrock_native_output_is_deployment_opt_in(
|
def bedrock_native_output_is_deployment_opt_in(
|
||||||
model: str, custom_llm_provider: str = "bedrock", model_info: Mapping[str, object] | None = None
|
model: str, custom_llm_provider: str = "bedrock", model_info: Mapping[str, object] | None = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return not _bedrock_model_supports(
|
return not _bedrock_native_output_enabled(model, custom_llm_provider, model_info) and _bedrock_native_output_opt_in(
|
||||||
model, "supports_native_structured_output", custom_llm_provider
|
model, custom_llm_provider, model_info
|
||||||
) and _bedrock_native_output_opt_in(model, custom_llm_provider, model_info)
|
)
|
||||||
|
|
||||||
|
|
||||||
def bedrock_native_output_is_opt_in(
|
def bedrock_native_output_is_opt_in(
|
||||||
model: str, custom_llm_provider: str = "bedrock", model_info: Mapping[str, object] | None = None
|
model: str, custom_llm_provider: str = "bedrock", model_info: Mapping[str, object] | None = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return not _bedrock_model_supports(model, "supports_native_structured_output", custom_llm_provider) and (
|
return not _bedrock_native_output_enabled(model, custom_llm_provider, model_info) and (
|
||||||
_bedrock_native_output_opt_in(model, custom_llm_provider, model_info)
|
_bedrock_native_output_opt_in(model, custom_llm_provider, model_info)
|
||||||
or _bedrock_native_output_replaces_unforceable_tool(model, model_info)
|
or _bedrock_native_output_replaces_unforceable_tool(model, model_info)
|
||||||
)
|
)
|
||||||
|
|
@ -387,7 +396,21 @@ def bedrock_native_output_is_opt_in(
|
||||||
def without_bedrock_native_output_opt_in(litellm_params: Mapping[str, object]) -> Mapping[str, object]:
|
def without_bedrock_native_output_opt_in(litellm_params: Mapping[str, object]) -> Mapping[str, object]:
|
||||||
return {
|
return {
|
||||||
**litellm_params,
|
**litellm_params,
|
||||||
"model_info": {**deployment_model_info(litellm_params), "supports_bedrock_native_structured_output": False},
|
"model_info": {
|
||||||
|
**deployment_model_info(litellm_params),
|
||||||
|
"supports_bedrock_native_structured_output": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def for_bedrock_json_tool_retry(litellm_params: Mapping[str, object]) -> Mapping[str, object]:
|
||||||
|
opt_in_disabled: Final = without_bedrock_native_output_opt_in(litellm_params)
|
||||||
|
return {
|
||||||
|
**opt_in_disabled,
|
||||||
|
"model_info": {
|
||||||
|
**deployment_model_info(opt_in_disabled),
|
||||||
|
"supports_native_structured_output": False,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -395,6 +418,7 @@ _BEDROCK_NATIVE_OUTPUT_UNSUPPORTED: Final = "output_config.format: Extra inputs
|
||||||
_BEDROCK_NATIVE_OUTPUT_REJECTIONS: Final = (
|
_BEDROCK_NATIVE_OUTPUT_REJECTIONS: Final = (
|
||||||
_BEDROCK_NATIVE_OUTPUT_UNSUPPORTED,
|
_BEDROCK_NATIVE_OUTPUT_UNSUPPORTED,
|
||||||
"compilation exceeded limits: Schema is too complex",
|
"compilation exceeded limits: Schema is too complex",
|
||||||
|
"The compiled grammar is too large",
|
||||||
"output_config.format.schema:",
|
"output_config.format.schema:",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -475,8 +499,27 @@ def requests_output_config_format(optional_params: Mapping[str, object]) -> bool
|
||||||
|
|
||||||
|
|
||||||
def requests_native_output(anthropic_optional_params: Mapping[str, object]) -> bool:
|
def requests_native_output(anthropic_optional_params: Mapping[str, object]) -> bool:
|
||||||
return isinstance(anthropic_optional_params.get("output_format"), Mapping) or requests_output_config_format(
|
output_config: Final = json_object_or_none(anthropic_optional_params.get("outputConfig"))
|
||||||
anthropic_optional_params
|
return (
|
||||||
|
isinstance(anthropic_optional_params.get("output_format"), Mapping)
|
||||||
|
or requests_output_config_format(anthropic_optional_params)
|
||||||
|
or (
|
||||||
|
anthropic_optional_params.get("json_mode") is True
|
||||||
|
and output_config is not None
|
||||||
|
and isinstance(output_config.get("textFormat"), Mapping)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def bedrock_native_output_retry_requested(
|
||||||
|
model: str,
|
||||||
|
optional_params: Mapping[str, object],
|
||||||
|
custom_llm_provider: str = "bedrock",
|
||||||
|
model_info: Mapping[str, object] | None = None,
|
||||||
|
) -> bool:
|
||||||
|
return requests_native_output(optional_params) and (
|
||||||
|
bedrock_native_output_is_opt_in(model, custom_llm_provider, model_info)
|
||||||
|
or _bedrock_native_output_enabled(model, custom_llm_provider, model_info)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -518,7 +561,7 @@ def bedrock_native_output_schema(
|
||||||
model_info: Mapping[str, object] | None = None,
|
model_info: Mapping[str, object] | None = None,
|
||||||
) -> Mapping[str, object] | None:
|
) -> Mapping[str, object] | None:
|
||||||
native_schema: Final = bedrock_cleaned_output_schema(model, schema, custom_llm_provider)
|
native_schema: Final = bedrock_cleaned_output_schema(model, schema, custom_llm_provider)
|
||||||
if _bedrock_model_supports(model, "supports_native_structured_output", custom_llm_provider):
|
if _bedrock_native_output_enabled(model, custom_llm_provider, model_info):
|
||||||
return schema if native_schema is None else native_schema
|
return schema if native_schema is None else native_schema
|
||||||
if _bedrock_native_output_opt_in(model, custom_llm_provider, model_info):
|
if _bedrock_native_output_opt_in(model, custom_llm_provider, model_info):
|
||||||
return native_schema
|
return native_schema
|
||||||
|
|
|
||||||
|
|
@ -40,15 +40,17 @@ from litellm.llms.bedrock.common_utils import (
|
||||||
BEDROCK_INVOKE_CONTEXT_MANAGEMENT_EDIT_BETAS,
|
BEDROCK_INVOKE_CONTEXT_MANAGEMENT_EDIT_BETAS,
|
||||||
BedrockError,
|
BedrockError,
|
||||||
apply_bedrock_invoke_structured_output,
|
apply_bedrock_invoke_structured_output,
|
||||||
bedrock_native_output_is_deployment_opt_in,
|
bedrock_native_output_format,
|
||||||
|
bedrock_native_output_retry_requested,
|
||||||
bedrock_supports_tool_search,
|
bedrock_supports_tool_search,
|
||||||
ensure_bedrock_anthropic_messages_tool_names,
|
ensure_bedrock_anthropic_messages_tool_names,
|
||||||
|
for_bedrock_json_tool_retry,
|
||||||
get_anthropic_beta_from_headers,
|
get_anthropic_beta_from_headers,
|
||||||
is_bedrock_native_output_rejection,
|
is_bedrock_native_output_rejection,
|
||||||
|
json_object_or_none,
|
||||||
normalize_bedrock_opus_output_config_effort,
|
normalize_bedrock_opus_output_config_effort,
|
||||||
normalize_custom_field_on_tools,
|
normalize_custom_field_on_tools,
|
||||||
normalize_tool_input_schema_types_for_bedrock_invoke,
|
normalize_tool_input_schema_types_for_bedrock_invoke,
|
||||||
requests_native_output,
|
|
||||||
requests_output_config_format,
|
requests_output_config_format,
|
||||||
strip_unsupported_bedrock_invoke_output_config_keys,
|
strip_unsupported_bedrock_invoke_output_config_keys,
|
||||||
supports_bedrock_prompt_cache_ttl,
|
supports_bedrock_prompt_cache_ttl,
|
||||||
|
|
@ -133,13 +135,24 @@ class AmazonAnthropicClaudeMessagesConfig(
|
||||||
) -> GenericLiteLLMParams | None:
|
) -> GenericLiteLLMParams | None:
|
||||||
model_info: Final = _MODEL_INFO.validate_python(litellm_params.model_info)
|
model_info: Final = _MODEL_INFO.validate_python(litellm_params.model_info)
|
||||||
if not (
|
if not (
|
||||||
requests_native_output(anthropic_messages_optional_request_params)
|
bedrock_native_output_retry_requested(
|
||||||
and bedrock_native_output_is_deployment_opt_in(model, model_info=model_info)
|
model, anthropic_messages_optional_request_params, model_info=model_info
|
||||||
|
)
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
return litellm_params.model_copy(
|
legacy_output_format: Final = json_object_or_none(
|
||||||
update={"model_info": {**(model_info or {}), "supports_bedrock_native_structured_output": False}}
|
anthropic_messages_optional_request_params.get("output_format")
|
||||||
)
|
)
|
||||||
|
output_config: Final = json_object_or_none(anthropic_messages_optional_request_params.get("output_config"))
|
||||||
|
output_config_format: Final = (
|
||||||
|
json_object_or_none(output_config.get("format")) if output_config is not None else None
|
||||||
|
)
|
||||||
|
output_format: Final = legacy_output_format if legacy_output_format is not None else output_config_format
|
||||||
|
if output_format is None or (bedrock_native_output_format(model, output_format, model_info=model_info) is None):
|
||||||
|
return None
|
||||||
|
retry_params: Final = for_bedrock_json_tool_retry({"model_info": model_info})
|
||||||
|
retry_model_info: Final = _MODEL_INFO.validate_python(retry_params.get("model_info"))
|
||||||
|
return litellm_params.model_copy(update={"model_info": retry_model_info})
|
||||||
|
|
||||||
def should_retry_anthropic_messages_without_native_output(
|
def should_retry_anthropic_messages_without_native_output(
|
||||||
self, e: httpx.HTTPStatusError, request_body: Mapping[str, object], model: str
|
self, e: httpx.HTTPStatusError, request_body: Mapping[str, object], model: str
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ from litellm.llms.bedrock.common_utils import (
|
||||||
bedrock_route_for_request,
|
bedrock_route_for_request,
|
||||||
bedrock_strict_tools_fallback,
|
bedrock_strict_tools_fallback,
|
||||||
deployment_model_info,
|
deployment_model_info,
|
||||||
without_bedrock_native_output_opt_in,
|
for_bedrock_json_tool_retry,
|
||||||
without_bedrock_route_prefix,
|
without_bedrock_route_prefix,
|
||||||
)
|
)
|
||||||
from litellm.llms.bedrock_mantle.chat.claude_transformation import bedrock_mantle_chat_config
|
from litellm.llms.bedrock_mantle.chat.claude_transformation import bedrock_mantle_chat_config
|
||||||
|
|
@ -4369,7 +4369,7 @@ def _native_output_fallback(
|
||||||
first_attempt_params: Mapping[str, object],
|
first_attempt_params: Mapping[str, object],
|
||||||
first_attempt_litellm_params: Mapping[str, object],
|
first_attempt_litellm_params: Mapping[str, object],
|
||||||
) -> NativeOutputFallback:
|
) -> NativeOutputFallback:
|
||||||
litellm_params: Final = without_bedrock_native_output_opt_in(first_attempt_litellm_params)
|
litellm_params: Final = for_bedrock_json_tool_retry(first_attempt_litellm_params)
|
||||||
remapped_params: Final = ctx.optional_params_for_model_info(deployment_model_info(litellm_params))
|
remapped_params: Final = ctx.optional_params_for_model_info(deployment_model_info(litellm_params))
|
||||||
return NativeOutputFallback(
|
return NativeOutputFallback(
|
||||||
optional_params={
|
optional_params={
|
||||||
|
|
@ -4500,7 +4500,7 @@ def _complete_bedrock(ctx: CompletionDispatchContext) -> _CompletionDispatchResu
|
||||||
converse_attempt,
|
converse_attempt,
|
||||||
(
|
(
|
||||||
partial(_converse_native_output_fallback, ctx, converse_params, dict(litellm_params))
|
partial(_converse_native_output_fallback, ctx, converse_params, dict(litellm_params))
|
||||||
if AmazonConverseConfig().uses_opt_in_native_output(converse_model, converse_params, litellm_params)
|
if AmazonConverseConfig().uses_native_output(converse_model, converse_params, litellm_params)
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
converse_model,
|
converse_model,
|
||||||
|
|
@ -4556,7 +4556,7 @@ def _complete_bedrock(ctx: CompletionDispatchContext) -> _CompletionDispatchResu
|
||||||
(
|
(
|
||||||
partial(_native_output_fallback, ctx, invoke_params, dict(litellm_params))
|
partial(_native_output_fallback, ctx, invoke_params, dict(litellm_params))
|
||||||
if isinstance(provider_config, AmazonAnthropicClaudeConfig)
|
if isinstance(provider_config, AmazonAnthropicClaudeConfig)
|
||||||
and provider_config.uses_opt_in_native_output(model, invoke_params, litellm_params)
|
and provider_config.uses_native_output(model, invoke_params, litellm_params)
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
model,
|
model,
|
||||||
|
|
|
||||||
|
|
@ -2242,7 +2242,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2331,7 +2331,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2420,7 +2420,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2504,7 +2504,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2593,7 +2593,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2715,7 +2715,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -2759,7 +2759,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -2803,7 +2803,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -2847,7 +2847,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -2891,7 +2891,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -75504,7 +75504,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -75544,7 +75544,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
|
||||||
|
|
@ -2242,7 +2242,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2331,7 +2331,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2420,7 +2420,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2504,7 +2504,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2593,7 +2593,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
@ -2715,7 +2715,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -2759,7 +2759,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -2803,7 +2803,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -2847,7 +2847,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -2891,7 +2891,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -75504,7 +75504,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_bedrock_native_structured_output": true,
|
"supports_bedrock_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
|
|
@ -75544,7 +75544,7 @@
|
||||||
"supports_tool_choice": true,
|
"supports_tool_choice": true,
|
||||||
"supports_vision": true,
|
"supports_vision": true,
|
||||||
"supports_xhigh_reasoning_effort": true,
|
"supports_xhigh_reasoning_effort": true,
|
||||||
"supports_native_structured_output": false,
|
"supports_native_structured_output": true,
|
||||||
"supports_max_reasoning_effort": true,
|
"supports_max_reasoning_effort": true,
|
||||||
"supports_output_config": true,
|
"supports_output_config": true,
|
||||||
"supports_parallel_tool_use_config": true,
|
"supports_parallel_tool_use_config": true,
|
||||||
|
|
|
||||||
|
|
@ -2047,8 +2047,9 @@ def _invoke_chat_params(
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("model", ["us.anthropic.claude-opus-4-8", "global.anthropic.claude-sonnet-5"])
|
@pytest.mark.parametrize("model", ["us.anthropic.claude-opus-4-8", "global.anthropic.claude-sonnet-5"])
|
||||||
def test_invoke_uses_opt_in_native_output_when_the_opt_in_drove_native_output(
|
def test_invoke_uses_opt_in_native_output_when_the_opt_in_drove_native_output(
|
||||||
local_model_cost_map, model, thinking_params
|
local_model_cost_map, model, thinking_params, monkeypatch
|
||||||
):
|
):
|
||||||
|
monkeypatch.setitem(litellm.model_cost[model], "supports_native_structured_output", False)
|
||||||
optional_params: Final = _invoke_chat_params(
|
optional_params: Final = _invoke_chat_params(
|
||||||
model, None, {"response_format": _CITY_RESPONSE_FORMAT, **thinking_params}
|
model, None, {"response_format": _CITY_RESPONSE_FORMAT, **thinking_params}
|
||||||
)
|
)
|
||||||
|
|
@ -2083,6 +2084,15 @@ def test_invoke_uses_opt_in_native_output_is_false_when_native_output_did_not_co
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invoke_uses_default_native_output_for_schema_retry(local_model_cost_map: None) -> None:
|
||||||
|
model: Final = "us.anthropic.claude-opus-5"
|
||||||
|
optional_params: Final = _invoke_chat_params(
|
||||||
|
model, None, {"response_format": _CITY_RESPONSE_FORMAT, "thinking": {"type": "disabled"}}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert AmazonAnthropicClaudeConfig().uses_native_output(model, optional_params, {})
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("forced_tool_use", "map_flag", "model_info", "thinking_params", "native"),
|
("forced_tool_use", "map_flag", "model_info", "thinking_params", "native"),
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -5244,6 +5244,44 @@ def test_models_whose_thinking_stops_under_native_output_keep_the_json_tool_only
|
||||||
assert ("toolConfig" in request) is not native
|
assert ("toolConfig" in request) is not native
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("local_model_cost_map")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("model", "thinking_params", "native"),
|
||||||
|
[
|
||||||
|
("us.anthropic.claude-opus-5", None, True),
|
||||||
|
("us.anthropic.claude-opus-5", {"thinking": {"type": "adaptive"}}, True),
|
||||||
|
("us.anthropic.claude-opus-5", {"thinking": {"type": "disabled"}}, True),
|
||||||
|
("us.anthropic.claude-opus-4-8", None, True),
|
||||||
|
("us.anthropic.claude-opus-4-8", {"thinking": {"type": "adaptive"}}, True),
|
||||||
|
("us.anthropic.claude-opus-4-8", {"thinking": {"type": "disabled"}}, True),
|
||||||
|
("us-gov.anthropic.claude-opus-5", None, False),
|
||||||
|
("us-gov.anthropic.claude-opus-4-8", None, False),
|
||||||
|
],
|
||||||
|
ids=[
|
||||||
|
"opus-5-unset",
|
||||||
|
"opus-5-adaptive",
|
||||||
|
"opus-5-disabled",
|
||||||
|
"opus-4-8-unset",
|
||||||
|
"opus-4-8-adaptive",
|
||||||
|
"opus-4-8-disabled",
|
||||||
|
"opus-5-us-gov",
|
||||||
|
"opus-4-8-us-gov",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_opus_5_and_4_8_cost_map_defaults_use_native_output_across_thinking_modes(
|
||||||
|
model: str, thinking_params: Mapping[str, object] | None, native: bool
|
||||||
|
) -> None:
|
||||||
|
_, request = _converse_request_for_json_schema(model, False, thinking_params)
|
||||||
|
|
||||||
|
if native:
|
||||||
|
assert request["outputConfig"]["textFormat"]["type"] == "json_schema"
|
||||||
|
assert "toolConfig" not in request
|
||||||
|
return
|
||||||
|
|
||||||
|
assert "outputConfig" not in request
|
||||||
|
assert [tool["toolSpec"]["name"] for tool in request["toolConfig"]["tools"]] == ["json_tool_call"]
|
||||||
|
|
||||||
|
|
||||||
def _converse_capital_answer(request: httpx.Request) -> httpx.Response:
|
def _converse_capital_answer(request: httpx.Request) -> httpx.Response:
|
||||||
return httpx.Response(
|
return httpx.Response(
|
||||||
200,
|
200,
|
||||||
|
|
@ -11599,7 +11637,10 @@ _OUTPUT_CONFIG_FORMAT: Final = {
|
||||||
ids=["adaptive", "reasoning_effort", "budget", "output_config_format"],
|
ids=["adaptive", "reasoning_effort", "budget", "output_config_format"],
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("model", ["us.anthropic.claude-opus-4-8", "global.anthropic.claude-sonnet-5"])
|
@pytest.mark.parametrize("model", ["us.anthropic.claude-opus-4-8", "global.anthropic.claude-sonnet-5"])
|
||||||
def test_uses_opt_in_native_output_when_the_opt_in_drove_native_output(local_model_cost_map, model, request_params):
|
def test_uses_opt_in_native_output_when_the_opt_in_drove_native_output(
|
||||||
|
local_model_cost_map, monkeypatch, model, request_params
|
||||||
|
):
|
||||||
|
monkeypatch.setitem(litellm.model_cost[model], "supports_native_structured_output", False)
|
||||||
optional_params: Final = _converse_params(model, None, request_params)
|
optional_params: Final = _converse_params(model, None, request_params)
|
||||||
|
|
||||||
assert AmazonConverseConfig().uses_opt_in_native_output(model, optional_params, {"model_info": {"id": "d1"}})
|
assert AmazonConverseConfig().uses_opt_in_native_output(model, optional_params, {"model_info": {"id": "d1"}})
|
||||||
|
|
@ -11630,7 +11671,10 @@ def test_uses_opt_in_native_output_is_false_when_native_output_did_not_come_from
|
||||||
assert not AmazonConverseConfig().uses_opt_in_native_output(model, optional_params, {"model_info": model_info})
|
assert not AmazonConverseConfig().uses_opt_in_native_output(model, optional_params, {"model_info": model_info})
|
||||||
|
|
||||||
|
|
||||||
def test_uses_opt_in_native_output_reads_the_base_model_of_an_application_inference_profile(local_model_cost_map):
|
def test_uses_opt_in_native_output_reads_the_base_model_of_an_application_inference_profile(
|
||||||
|
local_model_cost_map, monkeypatch
|
||||||
|
):
|
||||||
|
monkeypatch.setitem(litellm.model_cost["us.anthropic.claude-opus-4-8"], "supports_native_structured_output", False)
|
||||||
profile: Final = "arn:aws:bedrock:us-east-1:000000000000:application-inference-profile/profile-id"
|
profile: Final = "arn:aws:bedrock:us-east-1:000000000000:application-inference-profile/profile-id"
|
||||||
litellm_params: Final = {"base_model": "bedrock/us.anthropic.claude-opus-4-8"}
|
litellm_params: Final = {"base_model": "bedrock/us.anthropic.claude-opus-4-8"}
|
||||||
optional_params: Final = _converse_params(
|
optional_params: Final = _converse_params(
|
||||||
|
|
@ -11698,8 +11742,9 @@ _BEDROCK_SCHEMA_REFUSAL: Final = "output_config.format.schema: pattern is too co
|
||||||
)
|
)
|
||||||
@pytest.mark.respx(assert_all_called=True)
|
@pytest.mark.respx(assert_all_called=True)
|
||||||
def test_a_refused_native_schema_is_sent_again_only_when_the_retry_drops_native_output(
|
def test_a_refused_native_schema_is_sent_again_only_when_the_retry_drops_native_output(
|
||||||
caller_params: dict, expected_calls: int, respx_mock: respx.Router, local_model_cost_map: None
|
caller_params: dict, expected_calls: int, respx_mock: respx.Router, local_model_cost_map: None, monkeypatch
|
||||||
) -> None:
|
) -> None:
|
||||||
|
monkeypatch.setitem(litellm.model_cost["us.anthropic.claude-opus-4-8"], "supports_native_structured_output", False)
|
||||||
route: Final = respx_mock.post(url__regex=r"/converse$").mock(
|
route: Final = respx_mock.post(url__regex=r"/converse$").mock(
|
||||||
return_value=httpx.Response(400, json={"message": _BEDROCK_SCHEMA_REFUSAL})
|
return_value=httpx.Response(400, json={"message": _BEDROCK_SCHEMA_REFUSAL})
|
||||||
)
|
)
|
||||||
|
|
@ -11722,6 +11767,61 @@ def test_a_refused_native_schema_is_sent_again_only_when_the_retry_drops_native_
|
||||||
assert all("outputConfig" not in json.loads(call.request.content) for call in route.calls[1:])
|
assert all("outputConfig" not in json.loads(call.request.content) for call in route.calls[1:])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.respx(assert_all_called=True)
|
||||||
|
def test_a_default_native_schema_rejection_retries_with_the_json_tool(
|
||||||
|
respx_mock: respx.Router, local_model_cost_map: None
|
||||||
|
) -> None:
|
||||||
|
route: Final = respx_mock.post(url__regex=r"/converse$").mock(
|
||||||
|
return_value=httpx.Response(400, json={"message": _BEDROCK_SCHEMA_REFUSAL})
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(litellm.BadRequestError, match=re.escape(_BEDROCK_SCHEMA_REFUSAL)):
|
||||||
|
completion(
|
||||||
|
model="bedrock/converse/us.anthropic.claude-opus-5",
|
||||||
|
messages=[{"role": "user", "content": "What is the capital of France?"}],
|
||||||
|
response_format=CAPITAL_RESPONSE_FORMAT,
|
||||||
|
thinking={"type": "disabled"},
|
||||||
|
num_retries=0,
|
||||||
|
aws_access_key_id="fake",
|
||||||
|
aws_secret_access_key="fake",
|
||||||
|
aws_region_name="us-east-1",
|
||||||
|
client=HTTPHandler(),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert route.call_count == 2
|
||||||
|
first_request: Final = json.loads(route.calls[0].request.content)
|
||||||
|
retry_request: Final = json.loads(route.calls[1].request.content)
|
||||||
|
assert "outputConfig" in first_request and "toolConfig" not in first_request
|
||||||
|
assert "toolConfig" in retry_request and "outputConfig" not in retry_request
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.respx(assert_all_called=True)
|
||||||
|
def test_an_unforceable_tool_native_schema_rejection_retries_with_the_json_tool(
|
||||||
|
respx_mock: respx.Router, local_model_cost_map: None
|
||||||
|
) -> None:
|
||||||
|
route: Final = respx_mock.post(url__regex=r"/converse$").mock(
|
||||||
|
return_value=httpx.Response(400, json={"message": _BEDROCK_SCHEMA_REFUSAL})
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(litellm.BadRequestError, match=re.escape(_BEDROCK_SCHEMA_REFUSAL)):
|
||||||
|
completion(
|
||||||
|
model="bedrock/converse/anthropic.claude-fable-5-1",
|
||||||
|
messages=[{"role": "user", "content": "What is the capital of France?"}],
|
||||||
|
response_format=CAPITAL_RESPONSE_FORMAT,
|
||||||
|
num_retries=0,
|
||||||
|
aws_access_key_id="fake",
|
||||||
|
aws_secret_access_key="fake",
|
||||||
|
aws_region_name="us-east-1",
|
||||||
|
client=HTTPHandler(),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert route.call_count == 2
|
||||||
|
first_request: Final = json.loads(route.calls[0].request.content)
|
||||||
|
retry_request: Final = json.loads(route.calls[1].request.content)
|
||||||
|
assert "outputConfig" in first_request and "toolConfig" not in first_request
|
||||||
|
assert "toolConfig" in retry_request and "outputConfig" not in retry_request
|
||||||
|
|
||||||
|
|
||||||
CONTROL_PLANE_ENDPOINT: Final = "https://bedrock-fips.us-east-1.amazonaws.com"
|
CONTROL_PLANE_ENDPOINT: Final = "https://bedrock-fips.us-east-1.amazonaws.com"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1214,7 +1214,7 @@ async def test_converse_json_schema_stream_sends_claude_prose_only_when_claude_s
|
||||||
|
|
||||||
transport: Final = httpx.MockTransport(handle_request)
|
transport: Final = httpx.MockTransport(handle_request)
|
||||||
request: Final = {
|
request: Final = {
|
||||||
"model": "bedrock/us.anthropic.claude-opus-5",
|
"model": "bedrock/anthropic.claude-opus-5",
|
||||||
"messages": [{"role": "user", "content": "What is the LCM of 24 and 120? Reply in JSON."}],
|
"messages": [{"role": "user", "content": "What is the LCM of 24 and 120? Reply in JSON."}],
|
||||||
"response_format": {
|
"response_format": {
|
||||||
"type": "json_schema",
|
"type": "json_schema",
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,33 @@ _MODEL: Final = "us.anthropic.claude-opus-4-8"
|
||||||
_FALLBACK: Final = NativeOutputFallback(
|
_FALLBACK: Final = NativeOutputFallback(
|
||||||
optional_params={"tools": "json_tool_call"}, litellm_params={"model_info": {"opted_out": True}}
|
optional_params={"tools": "json_tool_call"}, litellm_params={"model_info": {"opted_out": True}}
|
||||||
)
|
)
|
||||||
|
_NATIVE_RETRY_WITH_STRICT_TOOLS: Final = NativeOutputFallback(
|
||||||
|
optional_params={
|
||||||
|
"tools": (
|
||||||
|
{"function": {"name": "json_schema", "strict": True}},
|
||||||
|
{"function": {"name": "custom", "strict": True}},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
litellm_params={
|
||||||
|
"model_info": {
|
||||||
|
"supports_native_structured_output": False,
|
||||||
|
"supports_bedrock_native_structured_output": False,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_COMPOSED_FALLBACK: Final = NativeOutputFallback(
|
||||||
|
optional_params={"tools": [{"function": {"name": "json_schema"}}, {"function": {"name": "custom"}}]},
|
||||||
|
litellm_params=_NATIVE_RETRY_WITH_STRICT_TOOLS.litellm_params,
|
||||||
|
)
|
||||||
_REJECTION: Final = BedrockError(
|
_REJECTION: Final = BedrockError(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
message='{"message":"The model returned the following errors: output_config.format: Extra inputs are not permitted"}',
|
message='{"message":"The model returned the following errors: output_config.format: Extra inputs are not permitted"}',
|
||||||
)
|
)
|
||||||
|
_GRAMMAR_REJECTION: Final = BedrockError(
|
||||||
|
status_code=400,
|
||||||
|
message='{"message":"The model returned the following errors: The compiled grammar is too large, which would cause '
|
||||||
|
'performance issues. Simplify your tool schemas or reduce the number of strict tools."}',
|
||||||
|
)
|
||||||
_SCHEMA_REFUSAL: Final = BedrockError(
|
_SCHEMA_REFUSAL: Final = BedrockError(
|
||||||
status_code=400, message='{"message":"output_config.format.schema: pattern is too complex for structured output"}'
|
status_code=400, message='{"message":"output_config.format.schema: pattern is too complex for structured output"}'
|
||||||
)
|
)
|
||||||
|
|
@ -58,8 +81,15 @@ _MODES: Final = pytest.mark.parametrize("asynchronous", [False, True], ids=["syn
|
||||||
|
|
||||||
|
|
||||||
@_MODES
|
@_MODES
|
||||||
def test_a_native_output_rejection_is_sent_again_once_with_the_fallback_params(asynchronous: bool) -> None:
|
@pytest.mark.parametrize(
|
||||||
bedrock: Final = ScriptedBedrock(errors=(_REJECTION, None), asynchronous=asynchronous)
|
"rejection",
|
||||||
|
[_REJECTION, _GRAMMAR_REJECTION],
|
||||||
|
ids=["format_not_permitted", "compiled_grammar_too_large"],
|
||||||
|
)
|
||||||
|
def test_a_native_output_rejection_is_sent_again_once_with_the_fallback_params(
|
||||||
|
asynchronous: bool, rejection: BedrockError
|
||||||
|
) -> None:
|
||||||
|
bedrock: Final = ScriptedBedrock(errors=(rejection, None), asynchronous=asynchronous)
|
||||||
|
|
||||||
response: Final = _call(bedrock, _FALLBACK)
|
response: Final = _call(bedrock, _FALLBACK)
|
||||||
|
|
||||||
|
|
@ -77,6 +107,19 @@ def test_a_second_native_output_rejection_reaches_the_caller(asynchronous: bool)
|
||||||
assert len(bedrock.sent) == 2
|
assert len(bedrock.sent) == 2
|
||||||
|
|
||||||
|
|
||||||
|
@_MODES
|
||||||
|
def test_a_grammar_rejection_after_a_native_retry_reaches_the_caller_without_strict_tools_fallback(
|
||||||
|
asynchronous: bool,
|
||||||
|
) -> None:
|
||||||
|
bedrock: Final = ScriptedBedrock(errors=(_GRAMMAR_REJECTION, _GRAMMAR_REJECTION), asynchronous=asynchronous)
|
||||||
|
|
||||||
|
with pytest.raises(BedrockError) as raised:
|
||||||
|
_resolve(call_with_native_output_fallback(bedrock, lambda: _FALLBACK, _MODEL))
|
||||||
|
|
||||||
|
assert raised.value is _GRAMMAR_REJECTION
|
||||||
|
assert bedrock.sent == (None, _FALLBACK)
|
||||||
|
|
||||||
|
|
||||||
@_MODES
|
@_MODES
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("error", "fallback"),
|
("error", "fallback"),
|
||||||
|
|
@ -155,13 +198,16 @@ _OTHER_400S_WITH_A_STRICT_TOOL: Final = tuple(
|
||||||
|
|
||||||
|
|
||||||
def _call_with_strict_tools_fallback(
|
def _call_with_strict_tools_fallback(
|
||||||
bedrock: ScriptedBedrock, fallback: NativeOutputFallback | None, strict_tools_fallback: NativeOutputFallback | None
|
bedrock: ScriptedBedrock,
|
||||||
|
fallback: NativeOutputFallback | None,
|
||||||
|
strict_tools_fallback: NativeOutputFallback | None,
|
||||||
|
model: str = _MODEL,
|
||||||
) -> object:
|
) -> object:
|
||||||
return _resolve(
|
return _resolve(
|
||||||
call_with_native_output_fallback(
|
call_with_native_output_fallback(
|
||||||
bedrock,
|
bedrock,
|
||||||
None if fallback is None else lambda: fallback,
|
None if fallback is None else lambda: fallback,
|
||||||
_MODEL,
|
model,
|
||||||
strict_tools_fallback=None if strict_tools_fallback is None else lambda: strict_tools_fallback,
|
strict_tools_fallback=None if strict_tools_fallback is None else lambda: strict_tools_fallback,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -223,3 +269,18 @@ def test_each_rejection_is_sent_again_with_its_own_fallback_when_both_are_set(
|
||||||
_call_with_strict_tools_fallback(bedrock, _FALLBACK, _STRICT_FALLBACK)
|
_call_with_strict_tools_fallback(bedrock, _FALLBACK, _STRICT_FALLBACK)
|
||||||
|
|
||||||
assert bedrock.sent == (None, retried_with)
|
assert bedrock.sent == (None, retried_with)
|
||||||
|
|
||||||
|
|
||||||
|
@_MODES
|
||||||
|
def test_a_strict_tools_rejection_after_native_retry_composes_both_fallbacks(asynchronous: bool) -> None:
|
||||||
|
bedrock: Final = ScriptedBedrock(
|
||||||
|
errors=(_GRAMMAR_REJECTION, _GRAMMAR_REJECTION, None),
|
||||||
|
asynchronous=asynchronous,
|
||||||
|
)
|
||||||
|
|
||||||
|
response: Final = _call_with_strict_tools_fallback(
|
||||||
|
bedrock, _NATIVE_RETRY_WITH_STRICT_TOOLS, _STRICT_FALLBACK, "us.anthropic.claude-sonnet-4-6"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(response, ModelResponse) and response.id == "answer-3"
|
||||||
|
assert bedrock.sent == (None, _NATIVE_RETRY_WITH_STRICT_TOOLS, _COMPOSED_FALLBACK)
|
||||||
|
|
|
||||||
|
|
@ -2047,7 +2047,7 @@ class TestBedrockBatchAnthropicRowParams:
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("model", "native_output"),
|
("model", "native_output"),
|
||||||
[("bedrock/us.anthropic.claude-opus-4-8", False), (MODEL, True)],
|
[("bedrock/us.anthropic.claude-sonnet-5-5", False), (MODEL, True)],
|
||||||
ids=["opt_in_native_output_model", "native_output_model"],
|
ids=["opt_in_native_output_model", "native_output_model"],
|
||||||
)
|
)
|
||||||
def test_thinking_json_schema_rows_use_native_output_only_on_models_that_always_take_it(
|
def test_thinking_json_schema_rows_use_native_output_only_on_models_that_always_take_it(
|
||||||
|
|
|
||||||
|
|
@ -4016,7 +4016,10 @@ _NATIVE_OUTPUT_REJECTION: Final = {
|
||||||
"message": "The model returned the following errors: compilation exceeded limits: Schema is too complex."
|
"message": "The model returned the following errors: compilation exceeded limits: Schema is too complex."
|
||||||
}
|
}
|
||||||
_STALE_SIGNATURE_ERROR: Final = {"message": "messages.1.content.0: Invalid `signature` in `thinking` block"}
|
_STALE_SIGNATURE_ERROR: Final = {"message": "messages.1.content.0: Invalid `signature` in `thinking` block"}
|
||||||
_OPTED_OUT: Final = {"supports_bedrock_native_structured_output": False}
|
_OPTED_OUT: Final = {
|
||||||
|
"supports_native_structured_output": False,
|
||||||
|
"supports_bedrock_native_structured_output": False,
|
||||||
|
}
|
||||||
_CONSTRAINED_SCHEMA: Final = {
|
_CONSTRAINED_SCHEMA: Final = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4158,21 +4161,21 @@ async def test_bedrock_messages_native_output_rejection_and_signature_400_each_g
|
||||||
|
|
||||||
@pytest.mark.usefixtures("local_model_cost_map")
|
@pytest.mark.usefixtures("local_model_cost_map")
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("model", "model_info", "error"),
|
("model", "model_info", "error", "should_retry"),
|
||||||
[
|
[
|
||||||
("us.anthropic.claude-opus-4-8", None, {"message": "messages.0.content: Field required"}),
|
("us.anthropic.claude-opus-4-8", None, {"message": "messages.0.content: Field required"}, False),
|
||||||
("us.anthropic.claude-sonnet-4-6", None, _NATIVE_OUTPUT_REJECTION),
|
("us.anthropic.claude-sonnet-4-6", None, _NATIVE_OUTPUT_REJECTION, True),
|
||||||
("us.anthropic.claude-opus-4-8", _OPTED_OUT, _NATIVE_OUTPUT_REJECTION),
|
("us.anthropic.claude-opus-4-8", _OPTED_OUT, _NATIVE_OUTPUT_REJECTION, False),
|
||||||
],
|
],
|
||||||
ids=["other_400", "native_model", "deployment_opt_out"],
|
ids=["other_400", "native_rejection", "deployment_opt_out"],
|
||||||
)
|
)
|
||||||
async def test_bedrock_messages_400_is_not_retried_unless_it_rejects_opt_in_native_output(
|
async def test_bedrock_messages_retries_only_native_output_rejections(
|
||||||
model: str, model_info: Mapping[str, object] | None, error: Mapping[str, object]
|
model: str, model_info: Mapping[str, object] | None, error: Mapping[str, object], should_retry: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
bedrock, raised = await _send_bedrock_messages(model, model_info, [(400, error), (200, _ANTHROPIC_MESSAGE)])
|
bedrock, raised = await _send_bedrock_messages(model, model_info, [(400, error), (200, _ANTHROPIC_MESSAGE)])
|
||||||
|
|
||||||
assert isinstance(raised, litellm.BadRequestError)
|
assert (raised is None) is should_retry
|
||||||
assert len(bedrock.bodies) == 1
|
assert len(bedrock.bodies) == (2 if should_retry else 1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("local_model_cost_map")
|
@pytest.mark.usefixtures("local_model_cost_map")
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ from litellm.llms.bedrock.common_utils import(
|
||||||
bedrock_strict_tools_fallback,
|
bedrock_strict_tools_fallback,
|
||||||
deployment_model_info,
|
deployment_model_info,
|
||||||
extract_model_name_from_bedrock_arn,
|
extract_model_name_from_bedrock_arn,
|
||||||
|
for_bedrock_json_tool_retry,
|
||||||
get_bedrock_base_model,
|
get_bedrock_base_model,
|
||||||
get_bedrock_cross_region_inference_regions,
|
get_bedrock_cross_region_inference_regions,
|
||||||
is_bedrock_native_output_rejection,
|
is_bedrock_native_output_rejection,
|
||||||
|
|
@ -2328,6 +2329,10 @@ def test_bedrock_capability_model_reads_base_model_only_for_application_profile_
|
||||||
|
|
||||||
_FORMAT_REJECTION: Final = "The model returned the following errors: output_config.format: Extra inputs are not permitted"
|
_FORMAT_REJECTION: Final = "The model returned the following errors: output_config.format: Extra inputs are not permitted"
|
||||||
_COMPILE_REJECTION: Final = "The model returned the following errors: compilation exceeded limits: Schema is too complex."
|
_COMPILE_REJECTION: Final = "The model returned the following errors: compilation exceeded limits: Schema is too complex."
|
||||||
|
_GRAMMAR_REJECTION: Final = (
|
||||||
|
"The model returned the following errors: The compiled grammar is too large, which would cause performance issues. "
|
||||||
|
"Simplify your tool schemas or reduce the number of strict tools."
|
||||||
|
)
|
||||||
_SCHEMA_PATTERN_REJECTION: Final = (
|
_SCHEMA_PATTERN_REJECTION: Final = (
|
||||||
"The model returned the following errors: output_config.format.schema: Unsupported regex feature in pattern "
|
"The model returned the following errors: output_config.format.schema: Unsupported regex feature in pattern "
|
||||||
"field: pattern is too complex for structured output: reduce the {n,m} upper bound, narrow the character class "
|
"field: pattern is too complex for structured output: reduce the {n,m} upper bound, narrow the character class "
|
||||||
|
|
@ -2340,6 +2345,7 @@ _SCHEMA_PATTERN_REJECTION: Final = (
|
||||||
[
|
[
|
||||||
(400, f'{{"message":"{_FORMAT_REJECTION}"}}', True),
|
(400, f'{{"message":"{_FORMAT_REJECTION}"}}', True),
|
||||||
(400, f'{{"message":"{_COMPILE_REJECTION}"}}', True),
|
(400, f'{{"message":"{_COMPILE_REJECTION}"}}', True),
|
||||||
|
(400, _GRAMMAR_REJECTION, True),
|
||||||
(400, f'{{"message":"{_SCHEMA_PATTERN_REJECTION}"}}', True),
|
(400, f'{{"message":"{_SCHEMA_PATTERN_REJECTION}"}}', True),
|
||||||
(400, "Malformed input request: #: extraneous key [output_config] is not permitted", False),
|
(400, "Malformed input request: #: extraneous key [output_config] is not permitted", False),
|
||||||
(400, "The model returned the following errors: messages.0.content: Field required", False),
|
(400, "The model returned the following errors: messages.0.content: Field required", False),
|
||||||
|
|
@ -2349,6 +2355,7 @@ _SCHEMA_PATTERN_REJECTION: Final = (
|
||||||
ids=[
|
ids=[
|
||||||
"format_not_permitted",
|
"format_not_permitted",
|
||||||
"schema_too_complex",
|
"schema_too_complex",
|
||||||
|
"compiled_grammar_too_large",
|
||||||
"schema_pattern_too_complex",
|
"schema_pattern_too_complex",
|
||||||
"other_key_not_permitted",
|
"other_key_not_permitted",
|
||||||
"other_400",
|
"other_400",
|
||||||
|
|
@ -2365,7 +2372,7 @@ def test_is_bedrock_native_output_rejection_matches_only_the_400s_that_reject_na
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("model", "model_info", "expected"),
|
("model", "model_info", "expected"),
|
||||||
[
|
[
|
||||||
("us.anthropic.claude-opus-4-8", None, True),
|
("us.anthropic.claude-opus-4-8", None, False),
|
||||||
("global.anthropic.claude-sonnet-5", None, True),
|
("global.anthropic.claude-sonnet-5", None, True),
|
||||||
("us-gov.anthropic.claude-opus-4-8", None, False),
|
("us-gov.anthropic.claude-opus-4-8", None, False),
|
||||||
("us.anthropic.claude-opus-4-8", {"supports_bedrock_native_structured_output": False}, False),
|
("us.anthropic.claude-opus-4-8", {"supports_bedrock_native_structured_output": False}, False),
|
||||||
|
|
@ -2389,9 +2396,43 @@ def test_without_bedrock_native_output_opt_in_turns_the_opt_in_off_and_keeps_the
|
||||||
"us.anthropic.claude-opus-4-8", model_info=deployment_model_info(turned_off)
|
"us.anthropic.claude-opus-4-8", model_info=deployment_model_info(turned_off)
|
||||||
)
|
)
|
||||||
assert turned_off["api_base"] == "https://bedrock.example"
|
assert turned_off["api_base"] == "https://bedrock.example"
|
||||||
assert deployment_model_info(turned_off)["id"] == "deployment-1"
|
assert deployment_model_info(turned_off) == {
|
||||||
|
"id": "deployment-1",
|
||||||
|
"supports_bedrock_native_structured_output": False,
|
||||||
|
}
|
||||||
assert litellm_params["model_info"] == {"id": "deployment-1"}
|
assert litellm_params["model_info"] == {"id": "deployment-1"}
|
||||||
|
|
||||||
|
native_default: Final = without_bedrock_native_output_opt_in(
|
||||||
|
{"model_info": {"supports_native_structured_output": True}}
|
||||||
|
)
|
||||||
|
assert deployment_model_info(native_default) == {
|
||||||
|
"supports_native_structured_output": True,
|
||||||
|
"supports_bedrock_native_structured_output": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_for_bedrock_json_tool_retry_disables_both_native_output_flags(local_model_cost_map: None) -> None:
|
||||||
|
litellm_params: Final = {
|
||||||
|
"model_info": {
|
||||||
|
"id": "deployment-1",
|
||||||
|
"supports_native_structured_output": True,
|
||||||
|
"supports_bedrock_native_structured_output": True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_params: Final = for_bedrock_json_tool_retry(litellm_params)
|
||||||
|
|
||||||
|
assert deployment_model_info(retry_params) == {
|
||||||
|
"id": "deployment-1",
|
||||||
|
"supports_native_structured_output": False,
|
||||||
|
"supports_bedrock_native_structured_output": False,
|
||||||
|
}
|
||||||
|
assert litellm_params["model_info"] == {
|
||||||
|
"id": "deployment-1",
|
||||||
|
"supports_native_structured_output": True,
|
||||||
|
"supports_bedrock_native_structured_output": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_bedrock_response_stream_shape_is_unavailable_when_botocore_describes_the_service_as_a_list():
|
def test_bedrock_response_stream_shape_is_unavailable_when_botocore_describes_the_service_as_a_list():
|
||||||
import litellm.llms.bedrock.common_utils as mod
|
import litellm.llms.bedrock.common_utils as mod
|
||||||
|
|
|
||||||
|
|
@ -332,6 +332,18 @@ def _cross_region_profiles(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_BEDROCK_PROFILE_ONLY_NATIVE_OUTPUT_MODELS: Final = frozenset(
|
||||||
|
{
|
||||||
|
"anthropic.claude-opus-5-5",
|
||||||
|
"anthropic.claude-fable-5-1",
|
||||||
|
"anthropic.claude-sonnet-5",
|
||||||
|
"anthropic.claude-haiku-5-5",
|
||||||
|
"anthropic.claude-opus-5",
|
||||||
|
"anthropic.claude-opus-4-8",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_get_model_info_bedrock_cross_region_capability_parity():
|
def test_get_model_info_bedrock_cross_region_capability_parity():
|
||||||
"""
|
"""
|
||||||
Cross-region inference profiles carry litellm_provider "bedrock_converse", so the
|
Cross-region inference profiles carry litellm_provider "bedrock_converse", so the
|
||||||
|
|
@ -362,6 +374,13 @@ def test_get_model_info_bedrock_cross_region_capability_parity():
|
||||||
if _govcloud_may_lag(k, cap):
|
if _govcloud_may_lag(k, cap):
|
||||||
assert base_value or not v[cap], f"{cap} is on {k} but not on {base_model_key}"
|
assert base_value or not v[cap], f"{cap} is on {k} but not on {base_model_key}"
|
||||||
continue
|
continue
|
||||||
|
if (
|
||||||
|
cap == "supports_native_structured_output"
|
||||||
|
and base_model_key in _BEDROCK_PROFILE_ONLY_NATIVE_OUTPUT_MODELS
|
||||||
|
and v[cap] is True
|
||||||
|
):
|
||||||
|
assert base_value is False, f"{cap} is already enabled on bare model {base_model_key}"
|
||||||
|
continue
|
||||||
assert (
|
assert (
|
||||||
v[cap] == base_value
|
v[cap] == base_value
|
||||||
), f"{cap} is {v[cap]} on {k} but {base_value} on {base_model_key}"
|
), f"{cap} is {v[cap]} on {k} but {base_value} on {base_model_key}"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue