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
100 changes: 100 additions & 0 deletions litellm/model_prices_and_context_window_backup.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
100 changes: 100 additions & 0 deletions model_prices_and_context_window.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Vertex cache read pricing

Medium Severity

The Vertex gemini-3.1-flash-lite-image entries set supports_prompt_caching to true but omit cache_read_input_token_cost. This leads to LiteLLM undercounting spend for cached input tokens, as Google bills them at $0.025/1M.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 246dd79. Configure here.

"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,
Expand Down
150 changes: 150 additions & 0 deletions tests/test_litellm/test_gemini_3_1_flash_lite_image_pricing.py
Original file line number Diff line number Diff line change
@@ -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"]
Comment thread
greptile-apps[bot] marked this conversation as resolved.
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
Loading