fix(bedrock): send compaction only with its beta, and add the beta for an adopted block

Follow-ups to forwarding the compact-2026-09-04 `compaction` parameter on
Bedrock Invoke:

- `compaction` stays in the body only when it is an object and
  compact-2026-09-04 survives the route's beta filter. bedrock_mantle has no
  entry for that beta, so a mantle request keeps the earlier strip instead of
  reaching Bedrock with the field and no beta (400 "Extra inputs are not
  permitted"). A null or non-object value is dropped the same way.
- A request that adopts the signed compaction block now gets the beta even
  without a client anthropic-beta header, as the Anthropic route does
  (carries_signed_compaction_block).
- The compaction tests pin the bundled beta config (local_beta_headers_config)
  so they do not read the remote copy an earlier test left cached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Homen Shum 2026-11-04 02:27:02 -08:00
parent af0501aa6a
commit c2da82a06d
3 changed files with 90 additions and 3 deletions

View file

@ -23,6 +23,7 @@ from litellm.llms.anthropic.common_utils import (
OBJECT_MAPPING_ADAPTER,
AnthropicModelInfo,
carried_thinking_keys,
carries_signed_compaction_block,
validated_object_mapping,
)
from litellm.llms.anthropic.messages.native.transformation import (
@ -555,12 +556,15 @@ class AmazonAnthropicClaudeMessagesConfig(
"""
A top-level ``compaction`` request needs the ``compact-2026-09-04``
anthropic-beta; without it Bedrock answers 400 "compaction: Extra inputs
are not permitted". Later requests that adopt the returned block need the
same beta, which the caller sends as a header.
are not permitted". A later request that adopts the returned signed
block needs the same beta, so it is added for that block too, as the
Anthropic route does.
Ref: https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-compaction.html
"""
if isinstance(anthropic_messages_request.get("compaction"), dict):
if isinstance(anthropic_messages_request.get("compaction"), dict) or carries_signed_compaction_block(
anthropic_messages_request.get("messages") or ()
):
beta_set.add("compact-2026-09-04")
def strips_beta_gated_fields_from_final_headers(self) -> bool:
@ -843,6 +847,16 @@ class AmazonAnthropicClaudeMessagesConfig(
if filtered_betas:
anthropic_messages_request["anthropic_beta"] = filtered_betas
# ``compaction`` travels only with its beta. A beta map without compact-2026-09-04
# (bedrock_mantle) or a non-object value keeps the earlier strip instead of a 400.
if "compaction" in anthropic_messages_request and not (
isinstance(anthropic_messages_request["compaction"], dict) and "compact-2026-09-04" in filtered_betas
):
verbose_logger.debug(
"Bedrock Invoke: dropping `compaction`; compact-2026-09-04 is not served on this route"
)
anthropic_messages_request.pop("compaction")
final_request: Final = OBJECT_MAPPING_ADAPTER.validate_python(anthropic_messages_request) # pyright: ignore[reportUnknownArgumentType] # built from the base signature's bare dict
gated: Final = strip_beta_gated_native_request(
{},

View file

@ -1547,6 +1547,7 @@ def test_bedrock_messages_preserves_compact_context_management_and_adds_beta():
assert result["max_tokens"] == 4096
@pytest.mark.usefixtures("local_beta_headers_config")
def test_bedrock_messages_keeps_compaction_param_and_adds_beta():
"""
The ``compact-2026-09-04`` beta uses a top-level ``compaction`` parameter, not a
@ -1580,6 +1581,7 @@ def test_bedrock_messages_keeps_compaction_param_and_adds_beta():
assert set(result).issubset(cfg.BEDROCK_INVOKE_ALLOWED_TOP_LEVEL_FIELDS)
@pytest.mark.usefixtures("local_beta_headers_config")
def test_bedrock_messages_keeps_compact_2026_09_04_beta_for_adopting_request():
"""
A request that adopts the signed block sends it first in ``messages`` and must
@ -1603,6 +1605,54 @@ def test_bedrock_messages_keeps_compact_2026_09_04_beta_for_adopting_request():
assert result["messages"][0]["content"][0] == block
@pytest.mark.usefixtures("local_beta_headers_config")
def test_bedrock_messages_adds_compact_2026_09_04_beta_for_signed_block_without_client_header():
"""
A client that adopts the signed block but sends no anthropic-beta header still
needs ``compact-2026-09-04``; the Anthropic route adds it for the block, and so
does Bedrock Invoke.
"""
from litellm.types.router import GenericLiteLLMParams
cfg = AmazonAnthropicClaudeMessagesConfig()
block = {"type": "compaction", "content": "<summary>TR-TITCK: Type IB.</summary>", "signature": "sig"}
messages = [{"role": "user", "content": [block, {"type": "text", "text": "Now KR-MFDS."}]}]
result = cfg.transform_anthropic_messages_request(
model="anthropic.claude-opus-5",
messages=messages,
anthropic_messages_optional_request_params={"max_tokens": 512},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert result["anthropic_beta"] == ["compact-2026-09-04"]
assert result["messages"][0]["content"][0] == block
@pytest.mark.usefixtures("local_beta_headers_config")
@pytest.mark.parametrize("compaction", [None, "summarize", []])
def test_bedrock_messages_drops_compaction_that_is_not_an_object(compaction):
"""
Only an object ``compaction`` gets the beta. Any other value is dropped, as it
was before the field was allowed, instead of reaching Bedrock without its beta.
"""
from litellm.types.router import GenericLiteLLMParams
cfg = AmazonAnthropicClaudeMessagesConfig()
result = cfg.transform_anthropic_messages_request(
model="anthropic.claude-opus-5",
messages=[{"role": "user", "content": [{"type": "text", "text": "Hi"}]}],
anthropic_messages_optional_request_params={"max_tokens": 512, "compaction": compaction},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert "compaction" not in result
assert "compact-2026-09-04" not in result.get("anthropic_beta", [])
def test_bedrock_messages_filters_unsupported_context_management_edits():
"""
Mixed edit lists must drop the LiteLLM-internal ``clear_thinking_20251015``

View file

@ -796,3 +796,26 @@ async def test_mantle_anthropic_messages_streaming_sends_stream_and_passes_throu
assert "event: message_start" in text
assert '"text": "pong"' in text
assert "event: message_stop" in text
@pytest.mark.usefixtures("local_beta_headers_config")
def test_mantle_messages_drops_compaction_without_its_beta():
"""
The bedrock_mantle beta map has no compact-2026-09-04, so the beta is filtered
out on this route. ``compaction`` must go with it, or the body carries a field
that Bedrock refuses without the beta.
"""
from litellm.types.router import GenericLiteLLMParams
config = AmazonMantleMessagesConfig()
headers: dict = {}
request = config.transform_anthropic_messages_request(
model="mantle/anthropic.claude-mythos-preview",
messages=[{"role": "user", "content": "Hello"}],
anthropic_messages_optional_request_params={"max_tokens": 100, "compaction": {"type": "summarize"}},
litellm_params=GenericLiteLLMParams(),
headers=headers,
)
assert "compaction" not in request
assert "compact-2026-09-04" not in headers.get("anthropic-beta", "")