Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 28 additions & 24 deletions litellm/litellm_core_utils/prompt_templates/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1393,10 +1393,10 @@ def convert_to_gemini_tool_call_invoke(
if tool_calls is not None:
for idx, tool in enumerate(tool_calls):
if "function" in tool:
gemini_function_call: Optional[VertexFunctionCall] = (
_gemini_tool_call_invoke_helper(
function_call_params=tool["function"]
)
gemini_function_call: Optional[
VertexFunctionCall
] = _gemini_tool_call_invoke_helper(
function_call_params=tool["function"]
)
if gemini_function_call is not None:
part_dict: VertexPartType = {
Expand Down Expand Up @@ -1574,7 +1574,9 @@ def convert_to_gemini_tool_call_result( # noqa: PLR0915
file_data = (
file_content.get("file_data", "")
if isinstance(file_content, dict)
else file_content if isinstance(file_content, str) else ""
else file_content
if isinstance(file_content, str)
else ""
)

if file_data:
Expand Down Expand Up @@ -2079,9 +2081,9 @@ def _sanitize_empty_text_content(
if isinstance(content, str):
if not content or not content.strip():
message = cast(AllMessageValues, dict(message)) # Make a copy
message["content"] = (
"[System: Empty message content sanitised to satisfy protocol]"
)
message[
"content"
] = "[System: Empty message content sanitised to satisfy protocol]"
verbose_logger.debug(
f"_sanitize_empty_text_content: Replaced empty text content in {message.get('role')} message"
)
Expand Down Expand Up @@ -2421,9 +2423,9 @@ def anthropic_messages_pt( # noqa: PLR0915
# Convert ChatCompletionImageUrlObject to dict if needed
image_url_value = m["image_url"]
if isinstance(image_url_value, str):
image_url_input: Union[str, dict[str, Any]] = (
image_url_value
)
image_url_input: Union[
str, dict[str, Any]
] = image_url_value
else:
# ChatCompletionImageUrlObject or dict case - convert to dict
image_url_input = {
Expand All @@ -2450,9 +2452,9 @@ def anthropic_messages_pt( # noqa: PLR0915
)

if "cache_control" in _content_element:
_anthropic_content_element["cache_control"] = (
_content_element["cache_control"]
)
_anthropic_content_element[
"cache_control"
] = _content_element["cache_control"]
user_content.append(_anthropic_content_element)
elif m.get("type", "") == "text":
m = cast(ChatCompletionTextObject, m)
Expand Down Expand Up @@ -2512,9 +2514,9 @@ def anthropic_messages_pt( # noqa: PLR0915
)

if "cache_control" in _content_element:
_anthropic_content_text_element["cache_control"] = (
_content_element["cache_control"]
)
_anthropic_content_text_element[
"cache_control"
] = _content_element["cache_control"]

user_content.append(_anthropic_content_text_element)

Expand Down Expand Up @@ -2647,9 +2649,9 @@ def anthropic_messages_pt( # noqa: PLR0915
original_content_element=dict(assistant_content_block),
)
if "cache_control" in _content_element:
_anthropic_text_content_element["cache_control"] = (
_content_element["cache_control"]
)
_anthropic_text_content_element[
"cache_control"
] = _content_element["cache_control"]
text_element = _anthropic_text_content_element

# Interleave: each thinking block precedes its server tool group.
Expand Down Expand Up @@ -2809,9 +2811,9 @@ def anthropic_messages_pt( # noqa: PLR0915
)

if "cache_control" in _content_element:
_anthropic_text_content_element["cache_control"] = (
_content_element["cache_control"]
)
_anthropic_text_content_element[
"cache_control"
] = _content_element["cache_control"]

assistant_content.append(_anthropic_text_content_element)

Expand Down Expand Up @@ -5291,7 +5293,9 @@ def default_response_schema_prompt(response_schema: dict) -> str:
prompt_str = """Use this JSON schema:
```json
{}
```""".format(response_schema)
```""".format(
response_schema
)
return prompt_str


Expand Down
2 changes: 2 additions & 0 deletions litellm/model_prices_and_context_window_backup.json
Original file line number Diff line number Diff line change
Expand Up @@ -31472,6 +31472,7 @@
},
"vertex_ai/claude-sonnet-4-6": {
"cache_creation_input_token_cost": 3.75e-06,
"cache_creation_input_token_cost_above_1hr": 6e-06,
"cache_read_input_token_cost": 3e-07,
"input_cost_per_token": 3e-06,
"litellm_provider": "vertex_ai-anthropic_models",
Expand Down Expand Up @@ -38339,6 +38340,7 @@
},
"vertex_ai/claude-sonnet-4-6@default": {
"cache_creation_input_token_cost": 3.75e-06,
"cache_creation_input_token_cost_above_1hr": 6e-06,
"cache_read_input_token_cost": 3e-07,
"input_cost_per_token": 3e-06,
"litellm_provider": "vertex_ai-anthropic_models",
Expand Down
2 changes: 2 additions & 0 deletions model_prices_and_context_window.json
Original file line number Diff line number Diff line change
Expand Up @@ -31508,6 +31508,7 @@
},
"vertex_ai/claude-sonnet-4-6": {
"cache_creation_input_token_cost": 3.75e-06,
"cache_creation_input_token_cost_above_1hr": 6e-06,
"cache_read_input_token_cost": 3e-07,
"input_cost_per_token": 3e-06,
"litellm_provider": "vertex_ai-anthropic_models",
Expand Down Expand Up @@ -38402,6 +38403,7 @@
},
"vertex_ai/claude-sonnet-4-6@default": {
"cache_creation_input_token_cost": 3.75e-06,
"cache_creation_input_token_cost_above_1hr": 6e-06,
"cache_read_input_token_cost": 3e-07,
"input_cost_per_token": 3e-06,
"litellm_provider": "vertex_ai-anthropic_models",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,34 @@ def test_cache_writing_cost_with_zero_creation_tokens_and_ephemeral_details():
assert round(result, 6) == round(expected, 6)


def test_vertex_ai_claude_sonnet_4_6_1h_cache_writes_are_priced():
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
litellm.model_cost = litellm.get_model_cost_map(url="")

usage = Usage(
prompt_tokens=0,
completion_tokens=0,
total_tokens=0,
prompt_tokens_details=PromptTokensDetailsWrapper(
cached_tokens=0,
cache_creation_tokens=0,
cache_creation_token_details=CacheCreationTokenDetails(
ephemeral_5m_input_tokens=0,
ephemeral_1h_input_tokens=1_000_000,
),
),
)

prompt_cost, completion_cost = generic_cost_per_token(
model="claude-sonnet-4-6",
usage=usage,
custom_llm_provider="vertex_ai",
)

assert prompt_cost == 6.0
assert completion_cost == 0.0
Comment thread
biubiubiuboomboomboom marked this conversation as resolved.


def test_service_tier_flex_pricing():
"""Test that flex service tier uses correct pricing (approximately 50% of standard)."""
# Set up environment for local model cost map
Expand Down
16 changes: 16 additions & 0 deletions tests/test_litellm/test_claude_opus_4_6_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,22 @@ def test_opus_4_6_alias_and_dated_metadata_match():
assert alias[key] == dated[key], f"Mismatch for {key}"


def test_vertex_ai_claude_sonnet_4_6_has_1h_cache_write_pricing_in_root_map():
json_path = os.path.join(
os.path.dirname(__file__), "../../model_prices_and_context_window.json"
)
with open(json_path) as f:
model_data = json.load(f)

vertex_entries = [
"vertex_ai/claude-sonnet-4-6",
"vertex_ai/claude-sonnet-4-6@default",
]

for model_name in vertex_entries:
assert model_data[model_name]["cache_creation_input_token_cost_above_1hr"] == 6e-06


def test_opus_4_6_bedrock_converse_registration():
assert "anthropic.claude-opus-4-6-v1" in litellm.BEDROCK_CONVERSE_MODELS
assert "global.anthropic.claude-opus-4-6-v1" in litellm.bedrock_converse_models
Expand Down
Loading