fix(cache_control): keep a configured point that sits beside junk entries on every chat seed path

`configured_injection_points` kept the point dicts of a mixed `cache_control_injection_points` list as a tuple,
but only read a `list` back. The chat seed and the `/v1/responses` dialect stamp write the normalized value back
onto the request, and on a deployment the OpenAI dialect does not stamp (an Anthropic model, Claude on Bedrock
Mantle) that value is the tuple itself, so the hook then read it as no configured points and the valid point was
silently dropped. The stamped OpenAI dialect and the `/v1/messages` path kept it only because they build a new list.

The normalizer now reads back the tuple it wrote, so the point reaches the wire on every path; an all-dict list
still passes through as the same object.
This commit is contained in:
mateo-berri 2026-10-07 21:17:15 -07:00
parent 519429d256
commit 5b5898456c
2 changed files with 22 additions and 4 deletions

View file

@ -91,10 +91,10 @@ def _validated_object_list(value: object) -> list[object] | None:
def configured_injection_points(value: object) -> Sequence[CacheControlInjectionPoint]:
if not isinstance(value, list):
if not isinstance(value, (list, tuple)):
return ()
if all(isinstance(entry, dict) for entry in value):
return cast(list[CacheControlInjectionPoint], value)
return cast(Sequence[CacheControlInjectionPoint], value)
return tuple(cast(CacheControlInjectionPoint, entry) for entry in value if isinstance(entry, dict))

View file

@ -4189,6 +4189,7 @@ class TestMalformedInjectionPointsAreIgnored:
Every entry point now reads it as no configured points, the way ``null`` already read."""
SHAPES = ("system", 5, {"location": "message", "role": "system"}, ["system"], None)
MIXED = ["system", {"location": "message", "role": "system"}, 3]
MESSAGES = [{"role": "system", "content": "sys"}, {"role": "user", "content": "hi"}]
@pytest.mark.parametrize("value", SHAPES)
@ -4196,8 +4197,25 @@ class TestMalformedInjectionPointsAreIgnored:
assert configured_injection_points(value) == ()
def test_keeps_the_point_entries_of_a_mixed_list(self):
mixed = ["system", {"location": "message", "role": "system"}, 3]
assert configured_injection_points(mixed) == ({"location": "message", "role": "system"},)
assert configured_injection_points(self.MIXED) == ({"location": "message", "role": "system"},)
def test_the_point_beside_junk_entries_survives_the_chat_seed_on_an_unstamped_deployment(self):
params: dict = {"cache_control_injection_points": copy.deepcopy(self.MIXED)}
AnthropicCacheControlHook.maybe_seed_default_injection_points(
non_default_params=params,
messages=copy.deepcopy(self.MESSAGES),
model="claude-sonnet-4-5",
custom_llm_provider="anthropic",
)
_, processed, _ = AnthropicCacheControlHook().get_chat_completion_prompt(
model="claude-sonnet-4-5",
messages=copy.deepcopy(self.MESSAGES),
non_default_params=params,
prompt_id=None,
prompt_variables=None,
dynamic_callback_params={},
)
assert processed[0] == {"role": "system", "content": "sys", "cache_control": {"type": "ephemeral"}}
@pytest.mark.parametrize("value", SHAPES)
def test_chat_prompt_hook_leaves_the_request_untouched(self, value):