diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 9deed66d647..b505ae47c61 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -385,7 +385,7 @@ class AmazonConverseConfig(BaseConfig): @staticmethod def _requires_min_max_tokens(model: str) -> bool: - return re.search(r"openai\.gpt-\d|xai\.grok-", model) is not None + return re.search(r"openai\.gpt-\d|xai\.grok-|moonshotai\.kimi-k3", model) is not None def _is_nova_2_model(self, model: str) -> bool: """ diff --git a/tests/e2e/coverage_registry/llm_conversational.yaml b/tests/e2e/coverage_registry/llm_conversational.yaml index b11ae907cfb..c40aeade1c1 100644 --- a/tests/e2e/coverage_registry/llm_conversational.yaml +++ b/tests/e2e/coverage_registry/llm_conversational.yaml @@ -68,6 +68,7 @@ - {id: llm.messages.anthropic.vision.nonstream.works, module: llm, tier: P0, subject_endpoint: messages, route: anthropic, capability: vision, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Vision via Messages API"} - {id: llm.messages.anthropic.prompt_cache_5m.nonstream.works, module: llm, tier: P0, subject_endpoint: messages, route: anthropic, capability: prompt_cache_5m, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Prompt caching via Messages API"} - {id: llm.messages.anthropic.thinking.nonstream.works, module: llm, tier: P1, subject_endpoint: messages, route: anthropic, capability: thinking, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Extended thinking via Messages API"} +- {id: llm.messages.bedrock_converse.basic.nonstream.works_for_kimi_k3_max_tokens_1, module: llm, tier: P0, subject_endpoint: messages, route: bedrock_converse, capability: basic, streaming: nonstream, assertions: [works_for_kimi_k3_max_tokens_1], source: "llm_translation/test_bedrock_kimi_k3_messages_e2e.py", rationale: "Bedrock Converse accepts max_tokens=1 on the Anthropic Messages surface after LiteLLM applies the model minimum", fail_before_fix: proven} - {id: llm.messages.bedrock_invoke.mid_conversation_system.nonstream.cache_hit, module: llm, tier: P0, subject_endpoint: messages, route: bedrock_invoke, capability: mid_conversation_system, streaming: nonstream, assertions: [works, cache_hit], source: "llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py", rationale: "Flagged Claude 4.8+/5 must keep mid-conversation system reminders in messages; hoisting mutates the system prefix and collapses the prompt cache (#32578/#32831/#32882)", fail_before_fix: proven} - {id: llm.messages.bedrock_invoke.mid_conversation_system.nonstream.works, module: llm, tier: P0, subject_endpoint: messages, route: bedrock_invoke, capability: mid_conversation_system, streaming: nonstream, assertions: [works], source: "llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py", rationale: "Claude <= 4.7 rejects role system inside messages; unflagged models must convert reminders to user turns in place (hoisting collapses the prompt cache) or every Claude Code session 400s (#32831)", fail_before_fix: proven} - {id: llm.messages.bedrock_invoke.web_search_server_tool.nonstream.works, module: llm, tier: P0, subject_endpoint: messages, route: bedrock_invoke, capability: web_search_server_tool, streaming: nonstream, assertions: [works], source: "llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py", rationale: "Bedrock hosts no web_search server tool, so this only works because interception rewrites it before the upstream call and the agentic loop feeds the results back in native shape; a regression that short-circuits or forwards it instead yields raw text or AWS's 400", fail_before_fix: unproven} diff --git a/tests/e2e/llm_translation/test_bedrock_kimi_k3_messages_e2e.py b/tests/e2e/llm_translation/test_bedrock_kimi_k3_messages_e2e.py new file mode 100644 index 00000000000..b381fd0d9e1 --- /dev/null +++ b/tests/e2e/llm_translation/test_bedrock_kimi_k3_messages_e2e.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from typing import Final + +import pytest +from anthropic.types import Message, MessageParam +from e2e_metadata import Domain, Mode, Provider, Route, Subject, meta +from lifecycle import ResourceManager +from models import LiteLLMParamsBody +from proxy_client import ProxyClient +from sdk_clients import NO_PROXY_CACHE, SdkClients + +pytestmark = pytest.mark.e2e + +BEDROCK_MODEL: Final = "bedrock/us.moonshotai.kimi-k3" +MODEL_NAME: Final = "claude-kimi-k3" +MODEL_SWITCH_BETA: Final = ( + "claude-code-20250219,context-1m-2025-08-07,interleaved-thinking-2025-05-14," + "redact-thinking-2026-02-12,context-management-2025-06-27," + "prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07" +) + + +def _register_model(proxy: ProxyClient, resources: ResourceManager) -> tuple[str, str]: + model_id: Final = proxy.create_model( + MODEL_NAME, + LiteLLMParamsBody( + model=BEDROCK_MODEL, + aws_access_key_id="os.environ/AWS_ACCESS_KEY_ID", + aws_secret_access_key="os.environ/AWS_SECRET_ACCESS_KEY", + aws_region_name="os.environ/AWS_REGION", + drop_params=True, + ), + ) + resources.defer(lambda: proxy.delete_model(model_id)) + return MODEL_NAME, resources.key(models=[MODEL_NAME]) + + +class TestBedrockKimiK3Messages: + @pytest.mark.covers("llm.messages.bedrock_converse.basic.nonstream.works_for_kimi_k3_max_tokens_1") + @meta( + Subject( + domain=Domain.LLM_TRANSLATION, + route=Route.MESSAGES, + providers=(Provider.BEDROCK,), + models=(BEDROCK_MODEL,), + mode=Mode.NONSTREAM, + ) + ) + def test_model_switch_probe_max_tokens_1_returns_message( + self, proxy: ProxyClient, resources: ResourceManager, sdk: SdkClients + ) -> None: + model, key = _register_model(proxy, resources) + client: Final = sdk.anthropic(key) + message_param: Final[MessageParam] = { + "role": "user", + "content": [{"type": "text", "text": "Hi", "cache_control": {"type": "ephemeral"}}], + } + message: Final = client.messages.create( + model=model, + max_tokens=1, + stream=False, + system="You are Claude Code, an assistant for software development.", + messages=[message_param], + extra_headers={"anthropic-beta": MODEL_SWITCH_BETA}, + extra_query={"beta": "true"}, + extra_body=NO_PROXY_CACHE, + ) + + assert isinstance(message, Message) + assert message.role == "assistant" diff --git a/tests/unit/llms/bedrock/chat/test_converse_transformation.py b/tests/unit/llms/bedrock/chat/test_converse_transformation.py index f0dd40d4bc9..1fb5542c964 100644 --- a/tests/unit/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/unit/llms/bedrock/chat/test_converse_transformation.py @@ -896,6 +896,16 @@ def test_reasoning_with_forced_tool_choice_switches_to_auto(): ("us.xai.grok-4.6", "max_tokens", 1, 16), ("global.xai.grok-4.6", "max_completion_tokens", 1, 16), ("us.xai.grok-4.6", "max_tokens", 32, 32), + ("us.moonshotai.kimi-k3", "max_tokens", 1, 16), + ("global.moonshotai.kimi-k3", "max_completion_tokens", 1, 16), + ("moonshotai.kimi-k3", "max_tokens", 64, 64), + ( + "arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.moonshotai.kimi-k3", + "max_tokens", + 1, + 16, + ), + ("moonshotai.kimi-k2.5", "max_tokens", 1, 1), ("anthropic.claude-sonnet-4-5-20250929-v1:0", "max_tokens", 1, 1), ("arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.openai.gpt-6-astra", "max_tokens", 1, 16), ("arn:aws:bedrock:us-east-1:123456789012:inference-profile/global.xai.grok-4.6", "max_tokens", 1, 16),