mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
test(anthropic): cover the cache_control rebuild shapes and type the test helpers
Codecov flagged the 5m ttl branch and the empty-system path of the wire builder; both now have a test. Greptile asked for full typing on the new test helpers.
This commit is contained in:
parent
87591e95d9
commit
2bd7771fb9
6 changed files with 47 additions and 16 deletions
|
|
@ -3613,3 +3613,16 @@ def test_anthropic_messages_pt_system_string_content_becomes_text_block():
|
|||
|
||||
assert result[1] == {"role": "system", "content": [{"type": "text", "text": "Answer in one word."}]}
|
||||
|
||||
|
||||
def test_anthropic_messages_pt_drops_a_system_message_with_no_text():
|
||||
"""Anthropic rejects empty text blocks, so a text-less system message must
|
||||
vanish rather than reach the wire as an empty system turn."""
|
||||
messages = [
|
||||
{"role": "user", "content": "First question"},
|
||||
{"role": "system", "content": ""},
|
||||
{"role": "assistant", "content": "Yes"},
|
||||
]
|
||||
|
||||
result = anthropic_messages_pt(messages=messages, model="claude-opus-4-8", llm_provider="anthropic")
|
||||
|
||||
assert [m["role"] for m in result] == ["user", "assistant"]
|
||||
|
|
|
|||
|
|
@ -6237,7 +6237,7 @@ REMINDER_TEXT = "<system-reminder>Answer with exactly one word.</system-reminder
|
|||
CACHED_SYSTEM_BLOCK = {"type": "text", "text": "You are terse.", "cache_control": {"type": "ephemeral"}}
|
||||
|
||||
|
||||
def _chat_request(config, model, messages):
|
||||
def _chat_request(config: AnthropicConfig, model: str, messages: list[dict]) -> dict:
|
||||
return config.transform_request(
|
||||
model=model,
|
||||
messages=messages,
|
||||
|
|
@ -6247,7 +6247,7 @@ def _chat_request(config, model, messages):
|
|||
)
|
||||
|
||||
|
||||
def _reminder_conversation():
|
||||
def _reminder_conversation() -> list[dict]:
|
||||
"""The shape Claude Code sends mid-session: cached system prompt, turns, a
|
||||
reminder right after a user turn, an assistant turn, a fresh user turn."""
|
||||
return [
|
||||
|
|
@ -6261,7 +6261,7 @@ def _reminder_conversation():
|
|||
]
|
||||
|
||||
|
||||
def _texts(message):
|
||||
def _texts(message: dict) -> list[str]:
|
||||
return [block["text"] for block in message["content"] if block.get("type") == "text"]
|
||||
|
||||
|
||||
|
|
@ -6418,7 +6418,7 @@ def test_chat_mid_conversation_system_keeps_earlier_turns_a_prefix_of_the_next_r
|
|||
assert len(later_blocks) > len(earlier_blocks)
|
||||
|
||||
|
||||
def _role_block_pairs(messages):
|
||||
def _role_block_pairs(messages: list[dict]) -> list[tuple[str, object]]:
|
||||
return [
|
||||
(message["role"], block)
|
||||
for message in messages
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ Anthropic, Vertex, Azure AI and Bedrock Invoke transformation tests; these pin
|
|||
the pure placement rules on the OpenAI-format message list.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.llms.anthropic.mid_conversation_system import (
|
||||
CONVERTED_SYSTEM_NOTE,
|
||||
|
|
@ -13,11 +15,11 @@ from litellm.llms.anthropic.mid_conversation_system import (
|
|||
)
|
||||
|
||||
|
||||
def _roles(messages):
|
||||
def _roles(messages: object) -> list[str]:
|
||||
return [m["role"] if isinstance(m, dict) else m.role for m in messages]
|
||||
|
||||
|
||||
def _texts(message):
|
||||
def _texts(message: dict) -> list[str]:
|
||||
return [block["text"] for block in message["content"]]
|
||||
|
||||
|
||||
|
|
@ -125,19 +127,35 @@ def test_unflagged_conversion_keeps_the_client_order_when_no_tool_result_follows
|
|||
assert _texts(placed[2]) == [CONVERTED_SYSTEM_NOTE, "reminder"]
|
||||
|
||||
|
||||
def test_unflagged_conversion_rebuilds_cache_control_on_the_converted_block():
|
||||
@pytest.mark.parametrize(
|
||||
"cache_control, expected",
|
||||
[
|
||||
({"type": "ephemeral", "ttl": "1h"}, {"type": "ephemeral", "ttl": "1h"}),
|
||||
({"type": "ephemeral", "ttl": "5m"}, {"type": "ephemeral", "ttl": "5m"}),
|
||||
({"type": "ephemeral", "ttl": "2h"}, {"type": "ephemeral"}),
|
||||
],
|
||||
)
|
||||
def test_unflagged_conversion_rebuilds_cache_control_on_the_converted_block(cache_control, expected):
|
||||
"""Only the shapes Anthropic accepts survive: ephemeral with a 5m or 1h ttl, or no ttl."""
|
||||
messages = [
|
||||
{"role": "user", "content": "q1"},
|
||||
{"role": "system", "content": "reminder", "cache_control": {"type": "ephemeral", "ttl": "1h"}},
|
||||
{"role": "system", "content": "reminder", "cache_control": cache_control},
|
||||
]
|
||||
|
||||
placed = place_mid_conversation_system(messages, supports_mid_conversation_system=False)
|
||||
|
||||
assert placed[1]["content"][1] == {
|
||||
"type": "text",
|
||||
"text": "reminder",
|
||||
"cache_control": {"type": "ephemeral", "ttl": "1h"},
|
||||
}
|
||||
assert placed[1]["content"][1] == {"type": "text", "text": "reminder", "cache_control": expected}
|
||||
|
||||
|
||||
def test_unflagged_conversion_drops_a_cache_control_that_is_not_ephemeral():
|
||||
messages = [
|
||||
{"role": "user", "content": "q1"},
|
||||
{"role": "system", "content": "reminder", "cache_control": {"type": "persistent"}},
|
||||
]
|
||||
|
||||
placed = place_mid_conversation_system(messages, supports_mid_conversation_system=False)
|
||||
|
||||
assert placed[1]["content"][1] == {"type": "text", "text": "reminder"}
|
||||
|
||||
|
||||
def test_placement_is_a_no_op_without_later_system_messages():
|
||||
|
|
|
|||
|
|
@ -415,7 +415,7 @@ class TestAzureAnthropicConfig:
|
|||
assert "context-management-2025-06-27" in headers["anthropic-beta"]
|
||||
|
||||
|
||||
def _mid_conversation_system_conversation():
|
||||
def _mid_conversation_system_conversation() -> list[dict]:
|
||||
return [
|
||||
{"role": "system", "content": [{"type": "text", "text": "You are terse.", "cache_control": {"type": "ephemeral"}}]},
|
||||
{"role": "user", "content": "First question"},
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ def test_output_format_removed_from_bedrock_invoke_request():
|
|||
), f"output_format should be removed for Bedrock Invoke, got keys: {result.keys()}"
|
||||
|
||||
|
||||
def _mid_conversation_system_conversation():
|
||||
def _mid_conversation_system_conversation() -> list[dict]:
|
||||
return [
|
||||
{"role": "system", "content": [{"type": "text", "text": "You are terse.", "cache_control": {"type": "ephemeral"}}]},
|
||||
{"role": "user", "content": "First question"},
|
||||
|
|
|
|||
|
|
@ -729,7 +729,7 @@ def test_sanitize_strips_effort_for_haiku_45():
|
|||
assert data["output_config"] == {"effort": "high"}
|
||||
|
||||
|
||||
def _mid_conversation_system_conversation():
|
||||
def _mid_conversation_system_conversation() -> list[dict]:
|
||||
return [
|
||||
{"role": "system", "content": [{"type": "text", "text": "You are terse.", "cache_control": {"type": "ephemeral"}}]},
|
||||
{"role": "user", "content": "First question"},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue