fix(bedrock): apply the 16 maxTokens floor to Kimi K3 on Converse (#43875)

* fix(bedrock): apply the 16 maxTokens floor to Kimi K3 on Converse

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(e2e): use lane AWS credentials for the Kimi K3 messages test

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(e2e): bypass the proxy cache and declare test metadata for the Kimi K3 probe

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-10-20 13:01:45 +00:00 • committed by GitHub
parent d776277647
commit dd20bb0ae9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 83 additions and 1 deletions

View file

@ -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:
"""

View file

@ -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}

View file

@ -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"

View file

@ -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),