mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix(bedrock): forward the per-turn-control beta to Bedrock Invoke
Claude Code sends `anthropic-beta: per-turn-control-2026-07-01` together with a mid-conversation `role: system` message carrying a message-level `output_config` (for example an effort change). The beta header config mapped the beta to null for `bedrock`, so LiteLLM dropped it while keeping the message in place on models flagged `supports_mid_conversation_system`, and Bedrock Invoke rejected the turn with "messages.N.output_config: Extra inputs are not permitted". Direct Bedrock InvokeModel probes with a synthetic request show Claude Opus 5.5 accepts that message shape only with this beta, and that Claude Haiku 4.5, Sonnet 4.6, Opus 4.8, Opus 5, and Sonnet 5 accept the beta on a plain request, so map it to itself for `bedrock`. `bedrock_converse` is unchanged. Generated with AI Co-Authored-By: Claude Code
This commit is contained in:
parent
ba77646991
commit
c4ef88049d
3 changed files with 47 additions and 1 deletions
|
|
@ -124,7 +124,7 @@
|
|||
"mcp-servers-2025-12-04": null,
|
||||
"output-128k-2025-02-19": null,
|
||||
"structured-output-2024-03-01": null,
|
||||
"per-turn-control-2026-07-01": null,
|
||||
"per-turn-control-2026-07-01": "per-turn-control-2026-07-01",
|
||||
"prompt-caching-scope-2026-01-05": null,
|
||||
"skills-2025-10-02": null,
|
||||
"structured-outputs-2025-11-13": null,
|
||||
|
|
|
|||
|
|
@ -2359,6 +2359,37 @@ def test_bedrock_invoke_transform_hoists_only_leading_system_run(local_model_cos
|
|||
]
|
||||
|
||||
|
||||
def test_bedrock_invoke_transform_forwards_per_turn_control_beta_with_message_output_config(
|
||||
local_model_cost_map, local_beta_headers_config
|
||||
):
|
||||
"""Claude Code's per-turn control sends a mid-conversation ``role: "system"``
|
||||
message carrying a message-level ``output_config`` together with the
|
||||
per-turn-control-2026-07-01 beta. Bedrock Invoke rejects that message with
|
||||
"messages.N.output_config: Extra inputs are not permitted" unless the beta
|
||||
is present, so the beta must reach ``anthropic_beta`` and the message must
|
||||
stay in place with its ``output_config``."""
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
cfg = AmazonAnthropicClaudeMessagesConfig()
|
||||
messages = [
|
||||
{"role": "user", "content": "hi"},
|
||||
{"role": "assistant", "content": "hello"},
|
||||
{"role": "system", "content": [], "output_config": {"effort": "low"}},
|
||||
{"role": "user", "content": "continue"},
|
||||
]
|
||||
|
||||
result = cfg.transform_anthropic_messages_request(
|
||||
model="us.anthropic.claude-opus-5-5",
|
||||
messages=copy.deepcopy(messages),
|
||||
anthropic_messages_optional_request_params={"max_tokens": 256, "stream": False},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={"anthropic-beta": "per-turn-control-2026-07-01"},
|
||||
)
|
||||
|
||||
assert result["messages"] == messages
|
||||
assert result["anthropic_beta"].count("per-turn-control-2026-07-01") == 1
|
||||
|
||||
|
||||
def test_bedrock_invoke_transform_converts_mid_conversation_system_for_older_claude(local_model_cost_map):
|
||||
"""Invoke rejects ``role: "system"`` in every position on Opus 4.7, Sonnet
|
||||
4.6, Haiku 4.5, etc. ("role 'system' is not supported on this model"), but
|
||||
|
|
|
|||
|
|
@ -458,6 +458,21 @@ class TestAnthropicBetaHeadersFiltering:
|
|||
|
||||
assert filtered == ["dangerous-tool-use-2026-09-03"]
|
||||
|
||||
@pytest.mark.parametrize("provider", ["anthropic", "bedrock", "bedrock_mantle"])
|
||||
def test_per_turn_control_forwarded(self, provider):
|
||||
"""Claude Code sends per-turn-control-2026-07-01 together with a
|
||||
mid-conversation `role: system` message that carries a message-level
|
||||
`output_config` (e.g. an effort change). Bedrock Invoke accepts that
|
||||
message only when the beta is present and otherwise answers
|
||||
"messages.N.output_config: Extra inputs are not permitted", so dropping
|
||||
the header (previously mapped to null) turned every such turn into a 400."""
|
||||
filtered = filter_and_transform_beta_headers(
|
||||
beta_headers=["per-turn-control-2026-07-01"],
|
||||
provider=provider,
|
||||
)
|
||||
|
||||
assert filtered == ["per-turn-control-2026-07-01"]
|
||||
|
||||
def test_null_value_headers_filtered(self):
|
||||
"""Test that headers with null values are always filtered out."""
|
||||
for provider in [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue