fix(gemini): read web search cost from model_info instead of hardcode - #24372
Conversation
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 BerriAI#24369
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes Gemini web search cost calculation by reading
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/llms/gemini/cost_calculator.py | Refactored to read pricing and billing mode from model_info instead of hardcoding $0.035; logic is correct, but web_search_billing_unit is missing from the ModelInfo TypedDict. |
| litellm/llms/vertex_ai/gemini/cost_calculator.py | Simplified to delegate to the shared gemini/cost_calculator.py, eliminating duplicated hardcoded cost logic; clean refactor. |
| litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py | Adds the missing _calculate_web_search_requests call in the non-streaming path to populate web_search_requests on usage.prompt_tokens_details; safe since prompt_tokens_details is always initialized by _calculate_usage. |
| tests/test_litellm/llms/gemini/test_cost_calculator.py | Five focused unit tests covering per-query billing, per-prompt billing, default billing mode, zero requests, and missing usage details — all mocked, no network calls, correct location. |
| model_prices_and_context_window.json | Adds search_context_cost_per_query and web_search_billing_unit to all 25 affected Gemini models; Gemini 3.x correctly uses $0.014 with per_query, Gemini 2.x uses $0.035 with default per_prompt. One vertex_ai-language-models alias for gemini-3-flash-preview has $0.035 instead of $0.014 (noted in previous review thread). |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[API Response received] --> B{Streaming?}
B -- Yes --> C[_calculate_usage]
B -- No --> D[_calculate_usage]
C --> E[_calculate_web_search_requests\ngrounding_metadata]
D --> F[_calculate_web_search_requests\ngrounding_metadata\n NEW in this PR]
E --> G[set usage.prompt_tokens_details\n.web_search_requests]
F --> G
G --> H[cost_per_web_search_request\nusage, model_info]
H --> I{search_context_cost_per_query\nin model_info?}
I -- Yes --> J[_cost = search_context_cost_per_query\n.search_context_size_medium]
I -- No --> K[_cost = 0.035 fallback]
J --> L{web_search_billing_unit}
K --> L
L -- per_query --> M[total = _cost × n_requests]
L -- per_prompt default --> N[total = _cost × 1\nflat fee per grounded call]
Comments Outside Diff (1)
-
litellm/llms/gemini/cost_calculator.py, line 61 (link)web_search_billing_unitmissing fromModelInfoTypedDictweb_search_billing_unitis read here via.get()but is not declared in theModelInfoTypedDict, whereas the sibling fieldsearch_context_cost_per_queryis declared there (inModelInfoBaseatlitellm/types/utils.py:235). This inconsistency means:- Static type checkers (mypy/pyright) will not flag misspellings of the key.
- IDEs won't autocomplete the field when working with
ModelInfoobjects.
To be consistent with
search_context_cost_per_query,web_search_billing_unitshould be added toModelInfoBase(orModelInfo) inlitellm/types/utils.py:web_search_billing_unit: Optional[str] # "per_query" | "per_prompt"
Reviews (5): Last reviewed commit: "fix: remove web_search_billing_unit from..." | Re-trigger Greptile
- Gemini 2.x charges per grounded prompt (flat $0.035), clamped to 1 regardless of internal query count - Gemini 3.x charges per search query ($0.014 each) - Extract web_search_requests from groundingMetadata in non-streaming responses (parity with streaming path) - Add search_context_cost_per_query to vertex_ai and base Gemini entries - Move tests to tests/test_litellm/ (CI directory)
Document how each provider bills for web search, the search_context_cost_per_query field in model_prices JSON, how to override pricing via proxy config, and how LiteLLM extracts web_search_requests from each provider's response.
…ded model name check Replace _is_gemini_3_model() substring check with a web_search_billing_unit field in model_prices JSON: - "per_query": each search query billed individually (Gemini 3.x) - "per_prompt" (default): flat fee per grounded API call (Gemini 2.x) Add web_search_billing_unit to 23 Gemini 3.x model entries. Update docs and tests accordingly.
The vertex_ai cost calculator hardcoded $0.035 and charged for every call with a PromptTokensDetailsWrapper (not just web search calls). Delegate to the shared Gemini calculator which reads pricing and billing unit from model_info, fixing both issues for vertex_ai models.
These providers have their own web search systems and pricing, independent of Google's grounding billing model.
d233d66
into
BerriAI:litellm_staging_03_22_2026
- streaming_iterator.py: adopted main's more defensive version of the tool-arg queueing check (.get() instead of [], isinstance guard) — same logic, same behavior, lower crash surface - model_prices_and_context_window.json + backup: combined staging's search_context_cost_per_query fields (PR #24372) with main's new supports_service_tier field — both are independent additions to the same Gemini model entries - test_streaming_handler.py: kept Azure streaming regression test (PR #24354) and added main's two new Gemini legacy vertex finish_reason normalization tests - test_gemini_batch_embeddings.py: kept staging's unsupported-params filtering tests (PR #24370) and added main's index/order test
fix(gemini): read web search cost from model_info instead of hardcode
- streaming_iterator.py: adopted main's more defensive version of the tool-arg queueing check (.get() instead of [], isinstance guard) — same logic, same behavior, lower crash surface - model_prices_and_context_window.json + backup: combined staging's search_context_cost_per_query fields (PR BerriAI#24372) with main's new supports_service_tier field — both are independent additions to the same Gemini model entries - test_streaming_handler.py: kept Azure streaming regression test (PR BerriAI#24354) and added main's two new Gemini legacy vertex finish_reason normalization tests - test_gemini_batch_embeddings.py: kept staging's unsupported-params filtering tests (PR BerriAI#24370) and added main's index/order test
Relevant issues
Fixes #24369
Pre-Submission checklist
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewType
🐛 Bug Fix
Changes
The Gemini web search cost calculator hardcoded $0.035 per request, which is only correct for Gemini 2.x. Gemini 3.x charges $0.014 per request (pricing docs).
Read from
search_context_cost_per_queryin model_info (same field already used by Anthropic, OpenAI, and Perplexity) with fallback to the legacy $0.035 for models not yet in the JSON.Also add
search_context_cost_per_queryto all 25 Gemini models that support web search inmodel_prices_and_context_window.json.Tests added
test_web_search_cost_from_model_info— reads $0.014 from model_infotest_web_search_cost_legacy_fallback— falls back to $0.035test_web_search_cost_zero_requests— zero requests = zero costtest_web_search_cost_no_usage_details— missing usage = zero cost