diff --git a/litellm/llms/anthropic/experimental_pass_through/context_management/editors/compact.py b/litellm/llms/anthropic/experimental_pass_through/context_management/editors/compact.py index 2a87afb5990..36499a21259 100644 --- a/litellm/llms/anthropic/experimental_pass_through/context_management/editors/compact.py +++ b/litellm/llms/anthropic/experimental_pass_through/context_management/editors/compact.py @@ -13,8 +13,14 @@ Mirrors Anthropic's native ``compact_20260112`` for non-Anthropic providers: """ import re +import sys from collections.abc import Mapping, Sequence -from typing import TYPE_CHECKING, Any, Final, Literal, NotRequired, Optional, TypedDict, Union, cast +from typing import TYPE_CHECKING, Any, Final, Literal, Optional, TypedDict, Union, cast + +if sys.version_info >= (3, 11): + from typing import NotRequired +else: + from typing_extensions import NotRequired from typing_extensions import ReadOnly diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 3af7d9e5019..4145395c7e5 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -12269,7 +12269,7 @@ }, "claude-3-haiku-20240307": { "cache_creation_input_token_cost": 3e-07, - "cache_creation_input_token_cost_above_1hr": 6e-06, + "cache_creation_input_token_cost_above_1hr": 5e-07, "cache_read_input_token_cost": 3e-08, "deprecation_date": "2026-04-20", "input_cost_per_token": 2.5e-07, @@ -12288,7 +12288,7 @@ }, "claude-3-opus-20240229": { "cache_creation_input_token_cost": 1.875e-05, - "cache_creation_input_token_cost_above_1hr": 6e-06, + "cache_creation_input_token_cost_above_1hr": 3e-05, "cache_read_input_token_cost": 1.5e-06, "deprecation_date": "2026-01-05", "input_cost_per_token": 1.5e-05, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 3af7d9e5019..4145395c7e5 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -12269,7 +12269,7 @@ }, "claude-3-haiku-20240307": { "cache_creation_input_token_cost": 3e-07, - "cache_creation_input_token_cost_above_1hr": 6e-06, + "cache_creation_input_token_cost_above_1hr": 5e-07, "cache_read_input_token_cost": 3e-08, "deprecation_date": "2026-04-20", "input_cost_per_token": 2.5e-07, @@ -12288,7 +12288,7 @@ }, "claude-3-opus-20240229": { "cache_creation_input_token_cost": 1.875e-05, - "cache_creation_input_token_cost_above_1hr": 6e-06, + "cache_creation_input_token_cost_above_1hr": 3e-05, "cache_read_input_token_cost": 1.5e-06, "deprecation_date": "2026-01-05", "input_cost_per_token": 1.5e-05, diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_compact_typing_compatibility.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_compact_typing_compatibility.py new file mode 100644 index 00000000000..075f66c7263 --- /dev/null +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/context_management/test_compact_typing_compatibility.py @@ -0,0 +1,49 @@ +""" +Unit tests for Python 3.10+ typing compatibility in context management compact editor. + +Fixes Issue #38076: +- Verifies that NotRequired is correctly imported either from typing (>= 3.11) or typing_extensions (< 3.11). +- Verifies that _SummaryCallKwargs TypedDict definition is valid across Python versions. +""" + +import importlib +import sys + + +def test_compact_editor_not_required_import(): + """Verify that compact.py imports NotRequired correctly based on sys.version_info.""" + import litellm.llms.anthropic.experimental_pass_through.context_management.editors.compact as compact_module + + assert hasattr(compact_module, "NotRequired") + if sys.version_info >= (3, 11): + import typing + + assert compact_module.NotRequired is typing.NotRequired + else: + import typing_extensions + + assert compact_module.NotRequired is typing_extensions.NotRequired + + +def test_compact_editor_summary_call_kwargs_definition(): + """Verify that _SummaryCallKwargs TypedDict is properly constructed.""" + from litellm.llms.anthropic.experimental_pass_through.context_management.editors.compact import ( + _SummaryCallKwargs, + ) + + annotations = _SummaryCallKwargs.__annotations__ + assert "model" in annotations + assert "messages" in annotations + assert "max_tokens" in annotations + assert "timeout" in annotations + assert "litellm_metadata" in annotations + assert "user" in annotations + assert "allowed_model_region" in annotations + + +def test_compact_module_can_be_imported(): + """Ensure litellm compact module imports without error on the current runtime.""" + compact_module = importlib.import_module( + "litellm.llms.anthropic.experimental_pass_through.context_management.editors.compact" + ) + assert compact_module is not None diff --git a/tests/test_litellm/test_claude_3_cache_pricing.py b/tests/test_litellm/test_claude_3_cache_pricing.py new file mode 100644 index 00000000000..8c4a79ae1c0 --- /dev/null +++ b/tests/test_litellm/test_claude_3_cache_pricing.py @@ -0,0 +1,53 @@ +""" +Unit tests for Claude 3 cache creation pricing above 1 hour in model prices map. + +Fixes Issue #38056: +- claude-3-haiku-20240307 cache_creation_input_token_cost_above_1hr should be 5e-07 (2x input cost of 2.5e-07) +- claude-3-opus-20240229 cache_creation_input_token_cost_above_1hr should be 3e-05 (2x input cost of 1.5e-05) +""" + +import json +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).parents[2] +MAIN_PATH = REPO_ROOT / "model_prices_and_context_window.json" +BACKUP_PATH = REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json" + + +def _load_prices(path: Path) -> dict: + with open(path) as f: + return json.load(f) + + +@pytest.mark.parametrize("path", [MAIN_PATH, BACKUP_PATH], ids=["main", "backup"]) +def test_claude_3_haiku_cache_creation_cost_above_1hr(path: Path): + prices = _load_prices(path) + model = "claude-3-haiku-20240307" + assert model in prices, f"{model} not found in {path}" + model_info = prices[model] + + input_cost = model_info["input_cost_per_token"] + assert input_cost == 2.5e-07 + + # Cache creation cost above 1hr is 2x standard input cost (5e-07) + expected_cache_creation_above_1hr = input_cost * 2 + assert model_info["cache_creation_input_token_cost_above_1hr"] == expected_cache_creation_above_1hr + assert model_info["cache_creation_input_token_cost_above_1hr"] == 5e-07 + + +@pytest.mark.parametrize("path", [MAIN_PATH, BACKUP_PATH], ids=["main", "backup"]) +def test_claude_3_opus_cache_creation_cost_above_1hr(path: Path): + prices = _load_prices(path) + model = "claude-3-opus-20240229" + assert model in prices, f"{model} not found in {path}" + model_info = prices[model] + + input_cost = model_info["input_cost_per_token"] + assert input_cost == 1.5e-05 + + # Cache creation cost above 1hr is 2x standard input cost (3e-05) + expected_cache_creation_above_1hr = input_cost * 2 + assert model_info["cache_creation_input_token_cost_above_1hr"] == expected_cache_creation_above_1hr + assert model_info["cache_creation_input_token_cost_above_1hr"] == 3e-05