fix(bedrock): forward the compact-2026-09-04 compaction param on Invoke /v1/messages

The compact-2026-09-04 beta is a top-level `compaction` request parameter
({"type": "summarize"}), not a context_management edit. The Bedrock Invoke
allowlist is built from BedrockInvokeAnthropicMessagesRequest, which had no
`compaction` field, so the field was dropped and the request reached Bedrock
as an ordinary turn. The bedrock beta map also dropped compact-2026-09-04, so
adopting requests lost the beta.

- add `compaction` to BedrockInvokeAnthropicMessagesRequest
- add compact-2026-09-04 to the bedrock beta map (Invoke only, not Converse)
- add the beta automatically when a `compaction` param is present

Ref: https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-compaction.html

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Homen Shum 2026-11-04 02:01:50 -08:00
parent fd4cef22ea
commit af0501aa6a
5 changed files with 81 additions and 1 deletions

View file

@ -200,6 +200,7 @@
"bash_20250124": null,
"code-execution-2025-08-25": null,
"compact-2026-01-12": "compact-2026-01-12",
"compact-2026-09-04": "compact-2026-09-04",
"computer-use-2025-01-24": "computer-use-2025-01-24",
"computer-use-2025-11-24": "computer-use-2025-11-24",
"context-1m-2025-08-07": "context-1m-2025-08-07",

View file

@ -550,6 +550,19 @@ class AmazonAnthropicClaudeMessagesConfig(
"edits": retained_edits,
}
@staticmethod
def _add_compaction_beta_for_bedrock_invoke(anthropic_messages_request: dict, beta_set: set) -> None:
"""
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.
Ref: https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-compaction.html
"""
if isinstance(anthropic_messages_request.get("compaction"), dict):
beta_set.add("compact-2026-09-04")
def strips_beta_gated_fields_from_final_headers(self) -> bool:
return False
@ -617,6 +630,7 @@ class AmazonAnthropicClaudeMessagesConfig(
anthropic_messages_request=anthropic_messages_request,
beta_set=beta_set,
)
self._add_compaction_beta_for_bedrock_invoke(anthropic_messages_request, beta_set)
self._get_tool_search_beta_header_for_bedrock(
model=model,

View file

@ -1257,6 +1257,13 @@ class BedrockInvokeAnthropicMessagesRequest(TypedDict, total=False):
# other edit types (e.g. `clear_thinking_20251015`) never reach Bedrock.
context_management: dict
# `compaction` ({"type": "summarize", "instructions"?: str}) is the
# `compact-2026-09-04` beta: one summarization call that returns a single
# signed compaction block. Bedrock rejects it together with
# `context_management`. Ref:
# https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-compaction.html
compaction: dict
class BedrockBatchRecordKind(Enum):
"""

View file

@ -1547,6 +1547,62 @@ def test_bedrock_messages_preserves_compact_context_management_and_adds_beta():
assert result["max_tokens"] == 4096
def test_bedrock_messages_keeps_compaction_param_and_adds_beta():
"""
The ``compact-2026-09-04`` beta uses a top-level ``compaction`` parameter, not a
``context_management`` edit. Bedrock InvokeModel accepts it with that beta and
returns one signed compaction block. Before this fix the top-level allowlist
dropped ``compaction``, so the request reached Bedrock as an ordinary turn.
Ref: https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-compaction.html
"""
from litellm.types.router import GenericLiteLLMParams
cfg = AmazonAnthropicClaudeMessagesConfig()
messages = [
{"role": "user", "content": [{"type": "text", "text": "Is CHG-TR-0042 Type IB in TR-TITCK?"}]},
{"role": "assistant", "content": [{"type": "text", "text": "Type IB."}]},
{"role": "user", "content": [{"type": "text", "text": "Now KR-MFDS."}]},
]
optional_params = {"max_tokens": 1024, "compaction": {"type": "summarize", "instructions": "Keep rule ids."}}
result = cfg.transform_anthropic_messages_request(
model="anthropic.claude-opus-5",
messages=messages,
anthropic_messages_optional_request_params=optional_params,
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert result["compaction"] == {"type": "summarize", "instructions": "Keep rule ids."}
assert "compact-2026-09-04" in result["anthropic_beta"]
assert "context_management" not in result
assert set(result).issubset(cfg.BEDROCK_INVOKE_ALLOWED_TOP_LEVEL_FIELDS)
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
carry the ``compact-2026-09-04`` beta again. The bedrock beta map has to keep it.
"""
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={"anthropic-beta": "compact-2026-09-04"},
)
assert "compact-2026-09-04" in result["anthropic_beta"]
assert result["messages"][0]["content"][0] == block
def test_bedrock_messages_filters_unsupported_context_management_edits():
"""
Mixed edit lists must drop the LiteLLM-internal ``clear_thinking_20251015``

View file

@ -99,7 +99,9 @@ class TestAnthropicBetaHeadersFiltering:
filtered = filter_and_transform_beta_headers(
beta_headers=all_headers, provider=provider
)
assert ("compact-2026-09-04" in filtered) is (provider in ("anthropic", "azure_ai", "vertex_ai"))
# Bedrock InvokeModel serves the compact-2026-09-04 `compaction` parameter; Converse does not.
# https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-compaction.html
assert ("compact-2026-09-04" in filtered) is (provider in ("anthropic", "azure_ai", "vertex_ai", "bedrock"))
assert ("mid-conversation-tool-changes-2026-07-01" in filtered) is (provider != "bedrock_converse")
for header in unsupported_headers: