diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index d954e33da9c..91948877ddf 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -17742,6 +17742,106 @@ }, "web_search_billing_unit": "per_query" }, + "gemini-3.1-flash-lite-image": { + "input_cost_per_image": 0.00028, + "input_cost_per_token": 2.5e-07, + "litellm_provider": "vertex_ai-language-models", + "max_input_tokens": 65536, + "max_output_tokens": 4096, + "max_tokens": 4096, + "mode": "image_generation", + "output_cost_per_image": 0.0336, + "output_cost_per_image_token": 3e-05, + "output_cost_per_token": 1.5e-06, + "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing#gemini-models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text", + "image" + ], + "supports_function_calling": false, + "supports_prompt_caching": true, + "supports_response_schema": false, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_vision": true + }, + "gemini/gemini-3.1-flash-lite-image": { + "rpm": 1000, + "tpm": 4000000, + "input_cost_per_image": 0.00028, + "input_cost_per_token": 2.5e-07, + "input_cost_per_token_batches": 1.25e-07, + "litellm_provider": "gemini", + "max_input_tokens": 65536, + "max_output_tokens": 4096, + "max_tokens": 4096, + "mode": "image_generation", + "output_cost_per_image": 0.0336, + "output_cost_per_image_token": 3e-05, + "output_cost_per_token": 1.5e-06, + "output_cost_per_token_batches": 7.5e-07, + "source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-3.1-flash-lite-image", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text", + "image" + ], + "supports_function_calling": true, + "supports_prompt_caching": false, + "supports_response_schema": false, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_vision": true + }, + "vertex_ai/gemini-3.1-flash-lite-image": { + "input_cost_per_image": 0.00028, + "input_cost_per_token": 2.5e-07, + "litellm_provider": "vertex_ai-language-models", + "max_input_tokens": 65536, + "max_output_tokens": 4096, + "max_tokens": 4096, + "mode": "image_generation", + "output_cost_per_image": 0.0336, + "output_cost_per_image_token": 3e-05, + "output_cost_per_token": 1.5e-06, + "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing#gemini-models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text", + "image" + ], + "supports_function_calling": false, + "supports_prompt_caching": true, + "supports_response_schema": false, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_vision": true + }, "gemini-3.1-flash-image-preview": { "input_cost_per_image": 0.00056, "input_cost_per_token": 5e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 8982b4f2565..f0c303323ed 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -17782,6 +17782,106 @@ }, "web_search_billing_unit": "per_query" }, + "gemini-3.1-flash-lite-image": { + "input_cost_per_image": 0.00028, + "input_cost_per_token": 2.5e-07, + "litellm_provider": "vertex_ai-language-models", + "max_input_tokens": 65536, + "max_output_tokens": 4096, + "max_tokens": 4096, + "mode": "image_generation", + "output_cost_per_image": 0.0336, + "output_cost_per_image_token": 3e-05, + "output_cost_per_token": 1.5e-06, + "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing#gemini-models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text", + "image" + ], + "supports_function_calling": false, + "supports_prompt_caching": true, + "supports_response_schema": false, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_vision": true + }, + "gemini/gemini-3.1-flash-lite-image": { + "rpm": 1000, + "tpm": 4000000, + "input_cost_per_image": 0.00028, + "input_cost_per_token": 2.5e-07, + "input_cost_per_token_batches": 1.25e-07, + "litellm_provider": "gemini", + "max_input_tokens": 65536, + "max_output_tokens": 4096, + "max_tokens": 4096, + "mode": "image_generation", + "output_cost_per_image": 0.0336, + "output_cost_per_image_token": 3e-05, + "output_cost_per_token": 1.5e-06, + "output_cost_per_token_batches": 7.5e-07, + "source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-3.1-flash-lite-image", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text", + "image" + ], + "supports_function_calling": true, + "supports_prompt_caching": false, + "supports_response_schema": false, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_vision": true + }, + "vertex_ai/gemini-3.1-flash-lite-image": { + "input_cost_per_image": 0.00028, + "input_cost_per_token": 2.5e-07, + "litellm_provider": "vertex_ai-language-models", + "max_input_tokens": 65536, + "max_output_tokens": 4096, + "max_tokens": 4096, + "mode": "image_generation", + "output_cost_per_image": 0.0336, + "output_cost_per_image_token": 3e-05, + "output_cost_per_token": 1.5e-06, + "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing#gemini-models", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions", + "/v1/batch" + ], + "supported_modalities": [ + "text", + "image" + ], + "supported_output_modalities": [ + "text", + "image" + ], + "supports_function_calling": false, + "supports_prompt_caching": true, + "supports_response_schema": false, + "supports_reasoning": true, + "supports_system_messages": true, + "supports_vision": true + }, "gemini-3.1-flash-image": { "input_cost_per_image": 0.00056, "input_cost_per_token": 5e-07, diff --git a/tests/test_litellm/test_gemini_3_1_flash_lite_image_pricing.py b/tests/test_litellm/test_gemini_3_1_flash_lite_image_pricing.py new file mode 100644 index 00000000000..adc306971e3 --- /dev/null +++ b/tests/test_litellm/test_gemini_3_1_flash_lite_image_pricing.py @@ -0,0 +1,150 @@ +"""Pricing entry for ``gemini-3.1-flash-lite-image`` (Google's Nano Banana 2 Lite). + +Google publishes: $0.25/1M input, $1.50/1M text output, and $30/1M image-output +tokens for the Lite image model (https://cloud.google.com/vertex-ai/generative-ai/pricing). +A 1K image is ~1120 output image tokens => ~$0.0336 / image. + +Without this entry, ``completion_cost`` raises "model isn't mapped yet" and Vertex +generateContent pass-through cost tracking silently logs $0. These tests pin the +values in both the primary price map and the ``litellm/`` backup, and verify +``get_model_info`` / ``completion_cost`` surface them. +""" + +import json +import os +import sys + +sys.path.insert(0, os.path.abspath("../..")) + +import litellm +from litellm import completion_cost +from litellm.types.utils import CompletionTokensDetailsWrapper, ModelResponse, Usage + +VARIANTS = [ + "gemini-3.1-flash-lite-image", + "gemini/gemini-3.1-flash-lite-image", + "vertex_ai/gemini-3.1-flash-lite-image", +] + +EXPECTED = { + "input_cost_per_token": 2.5e-07, + "output_cost_per_token": 1.5e-06, + "output_cost_per_image_token": 3e-05, + "mode": "image_generation", +} + +EXPECTED_CAPABILITIES = { + "max_output_tokens": 4096, + "max_tokens": 4096, + "supports_response_schema": False, + "supports_reasoning": True, +} + +EXPECTED_PER_ROUTE = { + "gemini-3.1-flash-lite-image": { + "supports_prompt_caching": True, + "supports_function_calling": False, + }, + "vertex_ai/gemini-3.1-flash-lite-image": { + "supports_prompt_caching": True, + "supports_function_calling": False, + }, + "gemini/gemini-3.1-flash-lite-image": { + "supports_prompt_caching": False, + "supports_function_calling": True, + "input_cost_per_token_batches": 1.25e-07, + "output_cost_per_token_batches": 7.5e-07, + }, +} + + +def _load_json(path: str) -> dict: + with open(path, encoding="utf-8") as f: + return json.load(f) + + +def _backup_path() -> str: + return os.path.join( + os.path.dirname(litellm.__file__), + "model_prices_and_context_window_backup.json", + ) + + +def _main_path() -> str: + return os.path.join( + os.path.dirname(__file__), "..", "..", "model_prices_and_context_window.json" + ) + + +class TestGeminiFlashLiteImagePricingData: + """Both price maps must carry Google's published Nano Banana 2 Lite costs.""" + + def test_present_in_both_maps(self): + main = _load_json(_main_path()) + backup = _load_json(_backup_path()) + for key in VARIANTS: + for label, data in (("main", main), ("backup", backup)): + assert key in data, f"{key} missing from {label} JSON" + entry = data[key] + for field, value in EXPECTED.items(): + assert entry[field] == value, f"{key} {field} in {label}: {entry.get(field)} != {value}" + + def test_capabilities_match_model_cards(self): + main = _load_json(_main_path()) + backup = _load_json(_backup_path()) + for key in VARIANTS: + expected = {**EXPECTED_CAPABILITIES, **EXPECTED_PER_ROUTE[key]} + for label, data in (("main", main), ("backup", backup)): + entry = data[key] + for field, value in expected.items(): + assert entry[field] == value, f"{key} {field} in {label}: {entry.get(field)} != {value}" + + def test_grounding_fields_absent(self): + """Grounding with Google Search is unsupported on Lite, so no search pricing.""" + for path in (_main_path(), _backup_path()): + data = _load_json(path) + for key in VARIANTS: + for field in ( + "supports_web_search", + "search_context_cost_per_query", + "web_search_billing_unit", + ): + assert field not in data[key], f"{key} should not define {field}" + + def test_image_output_pricing_consistent(self): + """1120 image-output tokens * output_cost_per_image_token == output_cost_per_image.""" + backup = _load_json(_backup_path()) + entry = backup["gemini-3.1-flash-lite-image"] + assert round(1120 * entry["output_cost_per_image_token"], 6) == entry["output_cost_per_image"] + + +class TestGeminiFlashLiteImageModelInfo: + """``get_model_info`` and ``completion_cost`` must report the new costs.""" + + def test_get_model_info_and_cost(self): + original = litellm.model_cost + try: + litellm.model_cost = _load_json(_backup_path()) + info = litellm.get_model_info("gemini-3.1-flash-lite-image") + assert info["input_cost_per_token"] == EXPECTED["input_cost_per_token"] + assert info["output_cost_per_token"] == EXPECTED["output_cost_per_token"] + + resp = ModelResponse() + resp.model = "gemini-3.1-flash-lite-image" + resp.usage = Usage( + prompt_tokens=7, + completion_tokens=1120, + total_tokens=1127, + completion_tokens_details=CompletionTokensDetailsWrapper( + image_tokens=1120, text_tokens=0 + ), + ) + cost = completion_cost( + completion_response=resp, + model="gemini-3.1-flash-lite-image", + custom_llm_provider="vertex_ai", + ) + expected_cost = 1120 * 3e-05 + 7 * 2.5e-07 + assert abs(cost - expected_cost) < 1e-6, f"unexpected cost {cost}" + finally: + litellm.model_cost = original