fix(gemini): read web search cost from model_info instead of hardcode

The Gemini web search cost calculator hardcoded $0.035 per request,
which is only correct for Gemini 2.x models. Gemini 3.x models
charge $0.014 per request.

Read from search_context_cost_per_query in model_info (same field
used by Anthropic, OpenAI, and Perplexity) with fallback to the
legacy $0.035 for models not yet updated in the JSON.

Also add search_context_cost_per_query to all 25 Gemini models
that support web search in model_prices_and_context_window.json.

Fixes #24369
This commit is contained in:
Chesars 2026-03-22 18:00:13 -03:00
parent f5194b5ce3
commit 040c6fe920
4 changed files with 225 additions and 48 deletions

View file

@ -31,11 +31,17 @@ def cost_per_token(
def cost_per_web_search_request(usage: "Usage", model_info: "ModelInfo") -> float:
"""
Calculates the cost per web search request for a given model, prompt tokens, and completion tokens.
Uses ``search_context_cost_per_query`` from ``model_info`` when available
(keyed by ``search_context_size_medium`` as the default tier). Falls back
to the legacy $0.035 hardcode for models that haven't been updated yet.
"""
from litellm.types.utils import PromptTokensDetailsWrapper
# cost per web search request
cost_per_web_search_request = 35e-3
# Resolve per-request cost from model_info, fallback to legacy default
_DEFAULT_COST = 35e-3
search_costs = model_info.get("search_context_cost_per_query") or {}
_cost = search_costs.get("search_context_size_medium", _DEFAULT_COST)
number_of_web_search_requests = 0
# Get number of web search requests
@ -47,10 +53,8 @@ def cost_per_web_search_request(usage: "Usage", model_info: "ModelInfo") -> floa
and usage.prompt_tokens_details.web_search_requests is not None
):
number_of_web_search_requests = usage.prompt_tokens_details.web_search_requests
else:
number_of_web_search_requests = 0
# Calculate total cost
total_cost = cost_per_web_search_request * number_of_web_search_requests
total_cost = _cost * number_of_web_search_requests
return total_cost

View file

@ -13963,7 +13963,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 8000000
"tpm": 8000000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini-2.5-flash-lite-preview-06-17": {
"deprecation_date": "2025-11-18",
@ -14552,7 +14557,12 @@
"supports_vision": true,
"supports_web_search": true,
"tpm": 250000,
"rpm": 10
"rpm": 10,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini-2.5-computer-use-preview-10-2025": {
"input_cost_per_token": 1.25e-06,
@ -14604,17 +14614,14 @@
"uses_embed_content": true
},
"vertex_ai/gemini-embedding-2-preview": {
"input_cost_per_audio_per_second": 0.00016,
"input_cost_per_image": 0.00012,
"input_cost_per_token": 2e-07,
"input_cost_per_video_per_second": 0.00079,
"input_cost_per_token": 1.5e-07,
"litellm_provider": "vertex_ai",
"max_input_tokens": 8192,
"max_tokens": 8192,
"mode": "embedding",
"output_cost_per_token": 0,
"output_vector_size": 3072,
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing",
"source": "https://ai.google.dev/gemini-api/docs/embeddings#multimodal",
"supports_multimodal": true,
"uses_embed_content": true
},
@ -14631,18 +14638,6 @@
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing",
"uses_embed_content": true
},
"vertex_ai/gemini-embedding-2-preview": {
"input_cost_per_token": 1.5e-07,
"litellm_provider": "vertex_ai",
"max_input_tokens": 8192,
"max_tokens": 8192,
"mode": "embedding",
"output_cost_per_token": 0,
"output_vector_size": 3072,
"source": "https://ai.google.dev/gemini-api/docs/embeddings#multimodal",
"supports_multimodal": true,
"uses_embed_content": true
},
"gemini/gemini-embedding-001": {
"input_cost_per_token": 1.5e-07,
"litellm_provider": "gemini",
@ -14725,7 +14720,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 10000000
"tpm": 10000000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.0-flash-001": {
"cache_read_input_token_cost": 2.5e-08,
@ -14764,7 +14764,12 @@
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 10000000
"tpm": 10000000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.0-flash-lite": {
"cache_read_input_token_cost": 1.875e-08,
@ -14801,7 +14806,12 @@
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 4000000
"tpm": 4000000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.5-flash": {
"cache_read_input_token_cost": 3e-08,
@ -14848,7 +14858,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 8000000
"tpm": 8000000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.5-flash-image": {
"cache_read_input_token_cost": 3e-08,
@ -14898,7 +14913,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 8000000
"tpm": 8000000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-3-pro-image-preview": {
"input_cost_per_image": 0.0011,
@ -14934,7 +14954,12 @@
"supports_response_schema": true,
"supports_system_messages": true,
"supports_vision": true,
"supports_web_search": true
"supports_web_search": true,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
"search_context_size_high": 0.014
}
},
"gemini/gemini-3.1-flash-image-preview": {
"input_cost_per_token": 2.5e-07,
@ -14970,7 +14995,12 @@
"supports_response_schema": true,
"supports_system_messages": true,
"supports_vision": true,
"supports_web_search": true
"supports_web_search": true,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
"search_context_size_high": 0.014
}
},
"gemini/deep-research-pro-preview-12-2025": {
"input_cost_per_image": 0.0011,
@ -15006,7 +15036,12 @@
"supports_response_schema": true,
"supports_system_messages": true,
"supports_vision": true,
"supports_web_search": true
"supports_web_search": true,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.5-flash-lite": {
"cache_read_input_token_cost": 1e-08,
@ -15053,7 +15088,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 250000
"tpm": 250000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.5-flash-lite-preview-09-2025": {
"cache_read_input_token_cost": 1e-08,
@ -15100,7 +15140,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 250000
"tpm": 250000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.5-flash-preview-09-2025": {
"cache_read_input_token_cost": 7.5e-08,
@ -15147,7 +15192,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 250000
"tpm": 250000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-flash-latest": {
"cache_read_input_token_cost": 7.5e-08,
@ -15194,7 +15244,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 250000
"tpm": 250000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-flash-lite-latest": {
"cache_read_input_token_cost": 2.5e-08,
@ -15241,7 +15296,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 250000
"tpm": 250000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.5-flash-lite-preview-06-17": {
"deprecation_date": "2025-11-18",
@ -15289,7 +15349,12 @@
"supports_url_context": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 250000
"tpm": 250000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.5-flash-preview-tts": {
"input_cost_per_token": 3e-07,
@ -15352,7 +15417,12 @@
"supports_video_input": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 800000
"tpm": 800000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-2.5-computer-use-preview-10-2025": {
"input_cost_per_token": 1.25e-06,
@ -15440,7 +15510,12 @@
"output_cost_per_token_above_200k_tokens_priority": 3.24e-05,
"cache_read_input_token_cost_priority": 3.6e-07,
"cache_read_input_token_cost_above_200k_tokens_priority": 7.2e-07,
"supports_service_tier": true
"supports_service_tier": true,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
"search_context_size_high": 0.014
}
},
"gemini/gemini-3.1-flash-lite-preview": {
"cache_read_input_token_cost": 2.5e-08,
@ -15493,7 +15568,12 @@
"supports_vision": true,
"supports_web_search": true,
"supports_native_streaming": true,
"tpm": 250000
"tpm": 250000,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
"search_context_size_high": 0.014
}
},
"gemini/gemini-3-flash-preview": {
"cache_read_input_token_cost": 5e-08,
@ -15546,7 +15626,12 @@
"input_cost_per_audio_token_priority": 1.8e-06,
"output_cost_per_token_priority": 5.4e-06,
"cache_read_input_token_cost_priority": 9e-08,
"supports_service_tier": true
"supports_service_tier": true,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
"search_context_size_high": 0.014
}
},
"gemini/gemini-3.1-pro-preview": {
"cache_read_input_token_cost": 2e-07,
@ -15604,7 +15689,12 @@
"output_cost_per_token_above_200k_tokens_priority": 3.24e-05,
"cache_read_input_token_cost_priority": 3.6e-07,
"cache_read_input_token_cost_above_200k_tokens_priority": 7.2e-07,
"supports_service_tier": true
"supports_service_tier": true,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
"search_context_size_high": 0.014
}
},
"gemini/gemini-3.1-pro-preview-customtools": {
"cache_read_input_token_cost": 2e-07,
@ -15662,7 +15752,12 @@
"output_cost_per_token_above_200k_tokens_priority": 3.24e-05,
"cache_read_input_token_cost_priority": 3.6e-07,
"cache_read_input_token_cost_above_200k_tokens_priority": 7.2e-07,
"supports_service_tier": true
"supports_service_tier": true,
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
"search_context_size_high": 0.014
}
},
"gemini-3-flash-preview": {
"cache_read_input_token_cost": 5e-08,
@ -15750,7 +15845,12 @@
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 10000000
"tpm": 10000000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini/gemini-exp-1114": {
"input_cost_per_token": 0,
@ -31147,7 +31247,9 @@
"mode": "chat",
"output_cost_per_token": 3.2e-06,
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing#glm-models",
"supported_regions": ["global"],
"supported_regions": [
"global"
],
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
@ -36756,7 +36858,12 @@
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 4000000
"tpm": 4000000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini-2.5-flash-native-audio-latest": {
"input_cost_per_audio_token": 1e-06,
@ -37102,7 +37209,12 @@
"supports_video_input": true,
"supports_vision": true,
"supports_web_search": true,
"tpm": 800000
"tpm": 800000,
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
},
"gemini-exp-1206": {
"cache_read_input_token_cost": 3e-08,

View file

View file

@ -0,0 +1,61 @@
import os
import sys
import pytest
sys.path.insert(0, os.path.abspath("../../../.."))
from litellm.llms.gemini.cost_calculator import cost_per_web_search_request
from litellm.types.utils import PromptTokensDetailsWrapper, Usage
def _make_usage(web_search_requests: int) -> Usage:
return Usage(
prompt_tokens=100,
completion_tokens=50,
total_tokens=150,
prompt_tokens_details=PromptTokensDetailsWrapper(
web_search_requests=web_search_requests,
),
)
def test_web_search_cost_from_model_info():
"""Cost should come from model_info when search_context_cost_per_query is set."""
model_info = {
"key": "gemini/gemini-3-flash-preview",
"search_context_cost_per_query": {
"search_context_size_low": 0.014,
"search_context_size_medium": 0.014,
"search_context_size_high": 0.014,
},
}
cost = cost_per_web_search_request(usage=_make_usage(3), model_info=model_info)
assert cost == pytest.approx(0.014 * 3)
def test_web_search_cost_legacy_fallback():
"""Without search_context_cost_per_query, should fallback to $0.035."""
model_info = {"key": "gemini/gemini-2.0-flash"}
cost = cost_per_web_search_request(usage=_make_usage(2), model_info=model_info)
assert cost == pytest.approx(0.035 * 2)
def test_web_search_cost_zero_requests():
"""Zero web search requests should return zero cost."""
model_info = {
"key": "gemini/gemini-3-flash-preview",
"search_context_cost_per_query": {
"search_context_size_medium": 0.014,
},
}
cost = cost_per_web_search_request(usage=_make_usage(0), model_info=model_info)
assert cost == 0.0
def test_web_search_cost_no_usage_details():
"""Missing prompt_tokens_details should return zero cost."""
model_info = {"key": "gemini/gemini-3-flash-preview"}
usage = Usage(prompt_tokens=100, completion_tokens=50, total_tokens=150)
cost = cost_per_web_search_request(usage=usage, model_info=model_info)
assert cost == 0.0