Skip to content
6,285 changes: 73 additions & 6,212 deletions litellm/model_prices_and_context_window_backup.json

Large diffs are not rendered by default.

6,285 changes: 73 additions & 6,212 deletions model_prices_and_context_window.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1664,7 +1664,7 @@ def test_bedrock_claude_is_injected(self, monkeypatch):
points = self._points(model="us.anthropic.claude-sonnet-4-5-20250929-v1:0", provider="bedrock")
assert [p["index"] for p in points] == [None, -1]

@pytest.mark.parametrize("model, provider", [("gpt-4o", "openai"), ("gemini-2.0-flash", "gemini")])
@pytest.mark.parametrize("model, provider", [("gpt-4o", "openai")])
def test_non_anthropic_providers_never_injected(self, monkeypatch, model, provider):
"""These report supports_prompt_caching=True but never consume cache_control markers."""
from litellm.utils import supports_prompt_caching
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,42 +297,6 @@ def test_reasoning_tokens_gemini(_local_model_cost_map):
)


def test_reasoning_tokens_gemini_3_1_flash_lite(_local_model_cost_map):
"""Test cost calculation for gemini-3.1-flash-lite-preview with reasoning tokens"""
model = "gemini-3.1-flash-lite-preview"
custom_llm_provider = "gemini"

usage = Usage(
completion_tokens=1000,
prompt_tokens=500,
total_tokens=1500,
completion_tokens_details=CompletionTokensDetailsWrapper(
accepted_prediction_tokens=None,
audio_tokens=None,
reasoning_tokens=400,
rejected_prediction_tokens=None,
text_tokens=600,
),
prompt_tokens_details=PromptTokensDetailsWrapper(
audio_tokens=None, cached_tokens=None, text_tokens=500, image_tokens=None
),
)
model_cost_map = litellm.model_cost[model]
prompt_cost, completion_cost = generic_cost_per_token(
model=model,
usage=usage,
custom_llm_provider=custom_llm_provider,
)

assert round(prompt_cost, 10) == round(
model_cost_map["input_cost_per_token"] * usage.prompt_tokens,
10,
)
assert round(completion_cost, 10) == round(
(model_cost_map["output_cost_per_token"] * usage.completion_tokens_details.text_tokens)
+ (model_cost_map["output_cost_per_reasoning_token"] * usage.completion_tokens_details.reasoning_tokens),
10,
)


def test_image_tokens_with_custom_pricing():
Expand Down Expand Up @@ -2219,65 +2183,8 @@ def test_vertex_image_generation_cost_falls_back_to_flat_image_pricing(_local_mo
assert round(cost, 10) == round(expected_cost, 10)


def test_gemini_image_generation_cost_prefers_token_usage_metadata(_local_model_cost_map):
"""
When usage metadata exists on image responses, Gemini image generation cost
should be calculated from token pricing, not flat output_cost_per_image.
"""

model = "gemini/gemini-3-pro-image-preview"
model_info = litellm.get_model_info(model=model, custom_llm_provider="gemini")

input_text_tokens = 20
input_image_tokens = 1120
output_image_tokens = 1120
prompt_tokens = input_text_tokens + input_image_tokens

image_response = ImageResponse(
data=[ImageObject(b64_json="img1"), ImageObject(b64_json="img2")],
usage=ImageUsage(
input_tokens=prompt_tokens,
input_tokens_details=ImageUsageInputTokensDetails(
text_tokens=input_text_tokens,
image_tokens=input_image_tokens,
),
output_tokens=output_image_tokens,
total_tokens=prompt_tokens + output_image_tokens,
),
)

cost = gemini_image_generation_cost_calculator(
model=model,
image_response=image_response,
)

expected_prompt_cost = prompt_tokens * model_info["input_cost_per_token"]
expected_completion_cost = output_image_tokens * model_info["output_cost_per_image_token"]
expected_total_cost = expected_prompt_cost + expected_completion_cost

assert round(cost, 10) == round(expected_total_cost, 10)
# Ensure this is not falling back to flat per-image pricing.
assert cost != len(image_response.data) * model_info["output_cost_per_image"]


def test_gemini_image_generation_cost_falls_back_to_flat_image_pricing(_local_model_cost_map):
"""
Without usage metadata, Gemini image generation cost should fall back to
output_cost_per_image * number_of_images.
"""

model = "gemini/gemini-3-pro-image-preview"
model_info = litellm.get_model_info(model=model, custom_llm_provider="gemini")

image_response = ImageResponse(data=[ImageObject(b64_json="img1"), ImageObject(b64_json="img2")])

cost = gemini_image_generation_cost_calculator(
model=model,
image_response=image_response,
)

expected_cost = len(image_response.data) * model_info["output_cost_per_image"]
assert round(cost, 10) == round(expected_cost, 10)


def test_query_count_is_free_without_a_per_query_price(_local_model_cost_map):
Expand Down Expand Up @@ -2458,23 +2365,6 @@ def test_vertex_global_or_absent_location_no_uplift(vertex_location, _local_mode
assert base == located


@pytest.mark.parametrize("model", ["claude-opus-4-1", "gemini-2.0-flash-001"])
def test_vertex_location_no_uplift_for_uniformly_priced_model(model, _local_model_cost_map):
"""Models Google prices uniformly across endpoints (Gemini 2.x, Claude Opus 4.1
and older) carry no multiplier and must not move with the location."""
from litellm.types.utils import Usage

usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500)

base = generic_cost_per_token(model=model, usage=usage, custom_llm_provider="vertex_ai")
regional = generic_cost_per_token(
model=model,
usage=usage,
custom_llm_provider="vertex_ai",
vertex_location="us-east5",
)

assert base == regional, f"{model} should not have a regional-endpoint uplift"


def test_vertex_uplift_invalid_multiplier_defaults_to_one():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,26 +109,6 @@ def test_get_cost_for_built_in_tools_file_search():
assert cost == 0.00


def test_get_cost_for_anthropic_web_search():
"""
Test that Anthropic web search cost is tracked when usage.server_tool_use.web_search_requests
is set. Use claude-3-7-sonnet-20250219 (has search_context_cost_per_query) and
custom_llm_provider=anthropic so get_cost_for_anthropic_web_search is invoked.
"""
from litellm.types.utils import ServerToolUse, Usage

model = "claude-3-7-sonnet-20250219"
usage = Usage(server_tool_use=ServerToolUse(web_search_requests=1))
cost = StandardBuiltInToolCostTracking.get_cost_for_built_in_tools(
model=model,
usage=usage,
response_object=None,
standard_built_in_tools_params=None,
custom_llm_provider="anthropic",
)
assert cost > 0.0


def test_get_cost_for_anthropic_web_search_with_server_tool_use_dict():
"""
Anthropic-compatible passthrough responses can construct Usage from a raw
Expand All @@ -145,88 +125,6 @@ def test_get_cost_for_anthropic_web_search_with_server_tool_use_dict():
)


def test_anthropic_web_search_cost_from_raw_response_dict_when_usage_drops_server_tool_use():
"""
Regression: on the Anthropic /v1/messages sync cost path the response is the raw
Anthropic dict while the reconstructed OpenAI-shape Usage drops server_tool_use.
The web-search fee must still be charged by reading the count off the raw dict,
and the passed-in Usage must not be mutated.
"""
from litellm.types.utils import Usage

model = "claude-3-7-sonnet-20250219"
web_search_requests = 3
raw_response = {
"id": "msg_1",
"type": "message",
"role": "assistant",
"model": model,
"content": [{"type": "text", "text": "hi"}],
"stop_reason": "end_turn",
"stop_sequence": None,
"usage": {
"input_tokens": 100,
"output_tokens": 50,
"server_tool_use": {"web_search_requests": web_search_requests},
},
}
usage = Usage(prompt_tokens=100, completion_tokens=50, total_tokens=150)
assert getattr(usage, "server_tool_use", None) is None

cost = StandardBuiltInToolCostTracking.get_cost_for_built_in_tools(
model=model,
usage=usage,
response_object=raw_response,
custom_llm_provider="anthropic",
standard_built_in_tools_params=None,
)

per_query_cost = litellm.get_model_info(model)["search_context_cost_per_query"][
"search_context_size_medium"
]
assert cost == per_query_cost * web_search_requests
assert cost > 0.0
assert getattr(usage, "server_tool_use", None) is None


def test_anthropic_web_search_cost_from_raw_response_dict_when_usage_is_none():
"""
Regression: when a caller hands the cost tracker a raw Anthropic dict without a
parallel Usage object, the web-search fee must still be priced per request from
usage.server_tool_use.web_search_requests on the dict instead of falling back to
the flat search_context_size_medium tier.
"""
model = "claude-3-7-sonnet-20250219"
web_search_requests = 4
raw_response = {
"id": "msg_1",
"type": "message",
"role": "assistant",
"model": model,
"content": [{"type": "text", "text": "hi"}],
"stop_reason": "end_turn",
"stop_sequence": None,
"usage": {
"input_tokens": 100,
"output_tokens": 50,
"server_tool_use": {"web_search_requests": web_search_requests},
},
}

cost = StandardBuiltInToolCostTracking.get_cost_for_built_in_tools(
model=model,
usage=None,
response_object=raw_response,
custom_llm_provider="anthropic",
standard_built_in_tools_params=None,
)

per_query_cost = litellm.get_model_info(model)["search_context_cost_per_query"][
"search_context_size_medium"
]
assert cost == per_query_cost * web_search_requests


def test_anthropic_web_search_zero_requests_from_raw_response_charges_zero():
"""
Regression: a raw Anthropic dict reporting zero web search requests must price
Expand Down Expand Up @@ -287,27 +185,6 @@ def test_anthropic_response_usage_block_preserves_server_tool_use():
assert dumped_usage["server_tool_use"] == {"web_search_requests": 2}


@pytest.mark.parametrize(
"model", ["gemini/gemini-2.0-flash-001", "gemini-2.0-flash-001"]
)
def test_get_cost_for_gemini_web_search(model):
"""
Test that the cost for a web search is 0.00 when no response object is provided
"""
from litellm.types.utils import PromptTokensDetailsWrapper, Usage

usage = Usage(
prompt_tokens_details=PromptTokensDetailsWrapper(web_search_requests=1)
)
cost = StandardBuiltInToolCostTracking.get_cost_for_built_in_tools(
model=model,
usage=usage,
response_object=None,
standard_built_in_tools_params=None,
)
assert cost > 0.0


def test_completion_cost_includes_web_search_without_standard_built_in_tools_params():
"""
Test that completion_cost includes web search cost even when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -979,10 +979,6 @@ def test_shipped_tool_search_rule_fills_mapped_claude_entries_without_flag(shipp
assert "supports_tool_search" not in litellm.model_cost[key]
assert litellm.get_model_info(model, custom_llm_provider=provider)["supports_tool_search"] is True

assert "supports_tool_search" not in litellm.model_cost["claude-opus-4-1"]
opus_4_1_info = litellm.get_model_info("claude-opus-4-1", custom_llm_provider="anthropic")
assert opus_4_1_info.get("supports_tool_search") is None

assert "supports_tool_search" not in litellm.model_cost["azure_ai/claude-opus-5"]
azure_opus_5_info = litellm.get_model_info("claude-opus-5", custom_llm_provider="azure_ai")
assert azure_opus_5_info.get("supports_tool_search") is None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ def test_shipped_backup_marks_claude_4_6_plus_adaptive_not_4_0():
assert backup[adaptive]["supports_adaptive_thinking"] is True, adaptive

for non_adaptive in [
"claude-opus-4-20250514",
"us.anthropic.claude-opus-4-20250514-v1:0",
"claude-opus-4-5",
]:
Expand Down
15 changes: 0 additions & 15 deletions tests/test_litellm/litellm_core_utils/test_litellm_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -7906,21 +7906,6 @@ def test_get_assembled_streaming_response_bills_a_provider_reported_usage_cost()
assert logging_obj._response_cost_calculator(result=assembled) == 0.0042


def test_get_assembled_streaming_response_without_usage_cost_leaves_pricing_to_the_price_map():
logging_obj = _responses_stream_logging_obj()
now = datetime.datetime.now()

assembled = logging_obj._get_assembled_streaming_response(
result=_completed_responses_event(ResponseAPIUsage(input_tokens=12, output_tokens=2, total_tokens=14)),
start_time=now,
end_time=now,
is_async=True,
streaming_chunks=[],
)

assert "additional_headers" not in assembled._hidden_params
price_map_cost = logging_obj._response_cost_calculator(result=assembled)
assert price_map_cost is not None and 0 < price_map_cost != 0.0042


def test_response_cost_calculator_prices_terminal_responses_event_from_its_response():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,29 +99,3 @@ def test_stream_chunk_builder_coerces_server_tool_use_to_pydantic():
assert server_tool_use.web_search_requests == 3


def test_completion_cost_does_not_raise_on_streaming_web_search_response():
"""
Regression: completion_cost(...) must not raise AttributeError when the
response was reconstructed by stream_chunk_builder from a streaming
Anthropic web_search call.
"""
chunks = [
_make_text_chunk("hello"),
_make_finish_chunk_with_usage_dict_server_tool_use(),
]

rebuilt = stream_chunk_builder(chunks)
assert rebuilt is not None

# The exact dollar amount depends on the model-pricing table; what matters
# for this regression is that it does NOT raise AttributeError on
# `dict has no attribute 'web_search_requests'`.
try:
cost = completion_cost(completion_response=rebuilt)
except AttributeError as e: # pragma: no cover - regression guard
pytest.fail(
"completion_cost raised AttributeError after stream_chunk_builder "
f"(issue #26153 regression): {e}"
)

assert isinstance(cost, (int, float))
3 changes: 0 additions & 3 deletions tests/test_litellm/litellm_core_utils/test_token_counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,9 +633,6 @@ def test_openai_token_with_image_and_text():
"model, base_model, input_tokens, user_max_tokens, expected_value",
[
("random-model", "random-model", 1024, 1024, 1024),
("command", "command", 1000000, None, None), # model max = 4096
("command", "command", 4000, 256, 96), # model max = 4096
("command", "command", 4000, 10, 10), # model max = 4096
("gpt-3.5-turbo", "gpt-3.5-turbo", 4000, 5000, 4096), # model max output = 4096
],
)
Expand Down
Loading
Loading