fix(caching): skip injection on messages whose tool calls carry marks

Reverts 9f08d8aef8. A default 5m mark injected on assistant text lands
before the client's 1h tool_use mark, which Anthropic rejects with a 400
because a 1h breakpoint must not follow a 5m one. Keeping the full census
in the skip check leaves the client's tool_call breakpoint as the only
one on that message.
This commit is contained in:
mateo-berri 2026-09-28 17:09:06 -07:00
parent 9f08d8aef8
commit 466022fd84
2 changed files with 19 additions and 21 deletions

View file

@ -518,26 +518,22 @@ class AnthropicCacheControlHook(CustomPromptManagement):
return []
@staticmethod
def _count_message_and_content_breakpoints(message: object) -> int:
def _count_cache_control_blocks(message: object) -> int:
message_count: Final = 1 if _carries_cache_breakpoint(message) else 0
content: Final = _as_object_list(_attribute_or_key(message, "content"))
content_count: Final = sum(1 for block in content if _carries_cache_breakpoint(block)) if content else 0
return message_count + content_count
@staticmethod
def _count_cache_control_blocks(message: object) -> int:
tool_calls: Final = _as_object_list(_attribute_or_key(message, "tool_calls"))
tool_call_count: Final = (
sum(1 for tool_call in tool_calls if _tool_call_cache_control_is_forwarded(tool_call, message))
if tool_calls
else 0
)
return AnthropicCacheControlHook._count_message_and_content_breakpoints(message) + tool_call_count
return message_count + content_count + tool_call_count
@staticmethod
def _message_has_cache_control(message: AllMessageValues) -> bool:
"""Return True if message-level or content-block cache_control is present."""
return AnthropicCacheControlHook._count_message_and_content_breakpoints(message) > 0
"""Return True if the message already carries any cache_control."""
return AnthropicCacheControlHook._count_cache_control_blocks(message) > 0
@staticmethod
def _safe_insert_cache_control_in_message(

View file

@ -1202,40 +1202,42 @@ def test_cache_control_hook_counts_pydantic_message_tool_call_marks():
assert AnthropicCacheControlHook.count_request_cache_breakpoints(cast(list[AllMessageValues], [message])) == 1
def test_injection_adds_message_mark_without_overwriting_tool_call_ttl():
def test_injection_skips_assistant_whose_tool_call_carries_a_longer_ttl_mark():
hook: Final = AnthropicCacheControlHook()
tool_call_ttl: Final = {"type": "ephemeral", "ttl": "1h"}
messages: Final = [
messages: Final[list[AllMessageValues]] = [
{"role": "user", "content": "What is the weather in Paris?"},
{
"role": "assistant",
"content": "ok",
"content": "Let me look that up.",
"tool_calls": [
{
"id": "call_1",
"id": "toolu_01A",
"type": "function",
"function": {"name": "lookup", "arguments": "{}"},
"function": {"name": "get_weather", "arguments": '{"city": "Paris"}'},
"cache_control": tool_call_ttl,
}
],
}
},
{"role": "tool", "tool_call_id": "toolu_01A", "content": "18C and sunny"},
]
_, processed, _ = hook.get_chat_completion_prompt(
model="bedrock/us.anthropic.claude-opus-4-6-v1:0",
model="anthropic/claude-haiku-4-5",
messages=messages,
non_default_params={"cache_control_injection_points": [{"location": "message", "index": -1}]},
non_default_params={"cache_control_injection_points": [{"location": "message", "role": "assistant"}]},
prompt_id=None,
prompt_variables=None,
dynamic_callback_params={},
)
assistant_message: Final = processed[0]
assistant_message: Final = processed[1]
assistant_tool_calls: Final = assistant_message.get("tool_calls")
assert assistant_message.get("cache_control") == {"type": "ephemeral"}
assert assistant_message.get("cache_control") is None
assert assistant_message.get("content") == "Let me look that up."
assert isinstance(assistant_tool_calls, list)
tool_call: Final = assistant_tool_calls[0]
assert tool_call.get("cache_control") == tool_call_ttl
assert AnthropicCacheControlHook.count_request_cache_breakpoints(processed) == 2
assert assistant_tool_calls[0].get("cache_control") == tool_call_ttl
assert AnthropicCacheControlHook.count_request_cache_breakpoints(processed) == 1
def test_cache_control_hook_caps_at_four_blocks_with_client_cache_control():