fix(anthropic): hoist cache_control from tool_result content sub-blocks to tool_result level (#35126)

This commit is contained in:
The Mavik
2026-02-11 21:25:01 +05:30
committed by GitHub
parent 56ddfcf8f4
commit 7f3c10865a
2 changed files with 110 additions and 8 deletions

View File

@@ -250,15 +250,31 @@ def _merge_messages(
):
curr = HumanMessage(curr.content) # type: ignore[misc]
else:
tool_content = curr.content
cache_ctrl = None
# Extract cache_control from content blocks and hoist it
# to the tool_result level. Anthropic's API does not
# support cache_control on tool_result content sub-blocks.
if isinstance(tool_content, list):
cleaned = []
for block in tool_content:
if isinstance(block, dict) and "cache_control" in block:
cache_ctrl = block["cache_control"]
block = {
k: v for k, v in block.items() if k != "cache_control"
}
cleaned.append(block)
tool_content = cleaned
tool_result: dict = {
"type": "tool_result",
"content": tool_content,
"tool_use_id": curr.tool_call_id,
"is_error": curr.status == "error",
}
if cache_ctrl:
tool_result["cache_control"] = cache_ctrl
curr = HumanMessage( # type: ignore[misc]
[
{
"type": "tool_result",
"content": curr.content,
"tool_use_id": curr.tool_call_id,
"is_error": curr.status == "error",
},
],
[tool_result],
)
last = merged[-1] if merged else None
if any(

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import copy
import os
from collections.abc import Callable
from typing import Any, Literal, cast
@@ -412,6 +413,91 @@ def test__merge_messages_mutation() -> None:
assert messages == original_messages
def test__merge_messages_tool_message_cache_control() -> None:
"""Test that cache_control is hoisted from content blocks to tool_result level."""
# Test with cache_control in content block
messages = [
ToolMessage(
content=[
{
"type": "text",
"text": "tool output",
"cache_control": {"type": "ephemeral"},
}
],
tool_call_id="1",
)
]
original_messages = [copy.deepcopy(m) for m in messages]
expected = [
HumanMessage(
[
{
"type": "tool_result",
"content": [{"type": "text", "text": "tool output"}],
"tool_use_id": "1",
"is_error": False,
"cache_control": {"type": "ephemeral"},
}
]
)
]
actual = _merge_messages(messages)
assert expected == actual
# Verify no mutation
assert messages == original_messages
# Test with multiple content blocks, cache_control on last one
messages = [
ToolMessage(
content=[
{"type": "text", "text": "first output"},
{
"type": "text",
"text": "second output",
"cache_control": {"type": "ephemeral"},
},
],
tool_call_id="2",
)
]
expected = [
HumanMessage(
[
{
"type": "tool_result",
"content": [
{"type": "text", "text": "first output"},
{"type": "text", "text": "second output"},
],
"tool_use_id": "2",
"is_error": False,
"cache_control": {"type": "ephemeral"},
}
]
)
]
actual = _merge_messages(messages)
assert expected == actual
# Test without cache_control
messages = [ToolMessage(content="simple output", tool_call_id="3")]
expected = [
HumanMessage(
[
{
"type": "tool_result",
"content": "simple output",
"tool_use_id": "3",
"is_error": False,
}
]
)
]
actual = _merge_messages(messages)
assert expected == actual
def test__format_image() -> None:
url = "dummyimage.com/600x400/000/fff"
with pytest.raises(ValueError):