diff --git a/docs/my-website/docs/completion/web_search.md b/docs/my-website/docs/completion/web_search.md index 375e9a6375..dc8f72025f 100644 --- a/docs/my-website/docs/completion/web_search.md +++ b/docs/my-website/docs/completion/web_search.md @@ -613,11 +613,15 @@ LiteLLM tracks web search costs automatically based on provider-specific billing ### Pricing configuration -Web search costs are defined in `model_prices_and_context_window.json` using the `search_context_cost_per_query` field: +Web search costs are defined in `model_prices_and_context_window.json` using two fields: + +- **`search_context_cost_per_query`**: the cost per billable unit (per search context size tier). +- **`web_search_billing_unit`**: `"per_query"` (each search query is billed individually) or `"per_prompt"` (default — flat fee per API call that uses search). ```json { "gemini/gemini-3-flash-preview": { + "web_search_billing_unit": "per_query", "search_context_cost_per_query": { "search_context_size_low": 0.014, "search_context_size_medium": 0.014, @@ -634,7 +638,11 @@ Web search costs are defined in `model_prices_and_context_window.json` using the } ``` -You can override these costs in your proxy config using `model_info`: +:::info +Models without `web_search_billing_unit` default to `"per_prompt"` — one flat charge per API call that uses web search, regardless of how many internal queries the model executes. +::: + +You can override these in your proxy config using `model_info`: ```yaml model_list: @@ -642,6 +650,7 @@ model_list: litellm_params: model: gemini/gemini-3-flash-preview model_info: + web_search_billing_unit: per_query search_context_cost_per_query: search_context_size_low: 0.014 search_context_size_medium: 0.014 diff --git a/litellm/llms/gemini/cost_calculator.py b/litellm/llms/gemini/cost_calculator.py index 23ec6ad7c1..cd536b8bd3 100644 --- a/litellm/llms/gemini/cost_calculator.py +++ b/litellm/llms/gemini/cost_calculator.py @@ -28,19 +28,13 @@ def cost_per_token( ) -def _is_gemini_3_model(model_info: "ModelInfo") -> bool: - """Check if the model is a Gemini 3.x variant based on its key.""" - key = model_info.get("key", "") - return "gemini-3" in key - - def cost_per_web_search_request(usage: "Usage", model_info: "ModelInfo") -> float: """ Calculates the cost of web search (grounding with Google Search). - Billing differs by model family: - - Gemini 3.x: charged per individual search query ($0.014 default). - - Gemini 2.x and older: charged per grounded prompt ($0.035 default), + Billing mode is determined by ``web_search_billing_unit`` in model_info: + - ``"per_query"``: charged per individual search query (Gemini 3.x). + - ``"per_prompt"`` (default): charged per grounded prompt (Gemini 2.x), regardless of how many queries were executed internally. Reads the per-request cost from ``search_context_cost_per_query`` in @@ -63,8 +57,9 @@ def cost_per_web_search_request(usage: "Usage", model_info: "ModelInfo") -> floa ): number_of_web_search_requests = usage.prompt_tokens_details.web_search_requests - # Gemini 2.x charges per grounded prompt (flat 1), not per query - if number_of_web_search_requests > 0 and not _is_gemini_3_model(model_info): + # per_prompt billing: clamp to 1 (flat fee per grounded API call) + billing_mode = model_info.get("web_search_billing_unit", "per_prompt") + if number_of_web_search_requests > 0 and billing_mode == "per_prompt": number_of_web_search_requests = 1 return _cost * number_of_web_search_requests diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 540ec03476..e0f5658106 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -13651,7 +13651,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3.1-flash-image-preview": { "input_cost_per_image": 0.00056, @@ -13688,7 +13689,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3.1-flash-lite-preview": { "cache_read_input_token_cost": 2.5e-08, @@ -13744,7 +13746,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "deep-research-pro-preview-12-2025": { "input_cost_per_image": 0.0011, @@ -14191,7 +14194,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3.1-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -14254,7 +14258,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3.1-pro-preview-customtools": { "cache_read_input_token_cost": 2e-07, @@ -14310,7 +14315,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/gemini-3-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -14371,7 +14377,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/gemini-3-flash-preview": { "cache_read_input_token_cost": 5e-08, @@ -14425,7 +14432,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/gemini-3.1-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -14488,7 +14496,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/gemini-3.1-pro-preview-customtools": { "cache_read_input_token_cost": 2e-07, @@ -14551,7 +14560,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-2.5-pro-preview-tts": { "cache_read_input_token_cost": 1.25e-07, @@ -15069,7 +15079,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3.1-flash-image-preview": { "input_cost_per_token": 2.5e-07, @@ -15110,7 +15121,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/deep-research-pro-preview-12-2025": { "input_cost_per_image": 0.0011, @@ -15625,7 +15637,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3.1-flash-lite-preview": { "cache_read_input_token_cost": 2.5e-08, @@ -15683,7 +15696,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3-flash-preview": { "cache_read_input_token_cost": 5e-08, @@ -15741,7 +15755,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3.1-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -15804,7 +15819,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3.1-pro-preview-customtools": { "cache_read_input_token_cost": 2e-07, @@ -15867,7 +15883,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3-flash-preview": { "cache_read_input_token_cost": 5e-08, @@ -15923,7 +15940,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-2.5-pro-preview-tts": { "cache_read_input_token_cost": 1.25e-07, @@ -24692,7 +24710,8 @@ "supports_tool_choice": true, "supports_video_input": true, "supports_vision": true, - "supports_web_search": true + "supports_web_search": true, + "web_search_billing_unit": "per_query" }, "openrouter/google/gemini-3-flash-preview": { "cache_read_input_token_cost": 5e-08, @@ -24739,7 +24758,8 @@ "supports_url_context": true, "supports_vision": true, "supports_web_search": true, - "tpm": 800000 + "tpm": 800000, + "web_search_billing_unit": "per_query" }, "openrouter/google/gemini-3.1-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -26286,14 +26306,16 @@ "mode": "responses", "supports_web_search": true, "supports_reasoning": false, - "supports_function_calling": true + "supports_function_calling": true, + "web_search_billing_unit": "per_query" }, "perplexity/google/gemini-3-flash-preview": { "litellm_provider": "perplexity", "mode": "responses", "supports_web_search": true, "supports_reasoning": false, - "supports_function_calling": true + "supports_function_calling": true, + "web_search_billing_unit": "per_query" }, "perplexity/google/gemini-2.5-pro": { "litellm_provider": "perplexity", @@ -31029,7 +31051,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/deep-research-pro-preview-12-2025": { "input_cost_per_image": 0.0011, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 540ec03476..e0f5658106 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -13651,7 +13651,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3.1-flash-image-preview": { "input_cost_per_image": 0.00056, @@ -13688,7 +13689,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3.1-flash-lite-preview": { "cache_read_input_token_cost": 2.5e-08, @@ -13744,7 +13746,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "deep-research-pro-preview-12-2025": { "input_cost_per_image": 0.0011, @@ -14191,7 +14194,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3.1-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -14254,7 +14258,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3.1-pro-preview-customtools": { "cache_read_input_token_cost": 2e-07, @@ -14310,7 +14315,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/gemini-3-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -14371,7 +14377,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/gemini-3-flash-preview": { "cache_read_input_token_cost": 5e-08, @@ -14425,7 +14432,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/gemini-3.1-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -14488,7 +14496,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/gemini-3.1-pro-preview-customtools": { "cache_read_input_token_cost": 2e-07, @@ -14551,7 +14560,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-2.5-pro-preview-tts": { "cache_read_input_token_cost": 1.25e-07, @@ -15069,7 +15079,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3.1-flash-image-preview": { "input_cost_per_token": 2.5e-07, @@ -15110,7 +15121,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/deep-research-pro-preview-12-2025": { "input_cost_per_image": 0.0011, @@ -15625,7 +15637,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3.1-flash-lite-preview": { "cache_read_input_token_cost": 2.5e-08, @@ -15683,7 +15696,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3-flash-preview": { "cache_read_input_token_cost": 5e-08, @@ -15741,7 +15755,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3.1-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -15804,7 +15819,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-3.1-pro-preview-customtools": { "cache_read_input_token_cost": 2e-07, @@ -15867,7 +15883,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini-3-flash-preview": { "cache_read_input_token_cost": 5e-08, @@ -15923,7 +15940,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "gemini/gemini-2.5-pro-preview-tts": { "cache_read_input_token_cost": 1.25e-07, @@ -24692,7 +24710,8 @@ "supports_tool_choice": true, "supports_video_input": true, "supports_vision": true, - "supports_web_search": true + "supports_web_search": true, + "web_search_billing_unit": "per_query" }, "openrouter/google/gemini-3-flash-preview": { "cache_read_input_token_cost": 5e-08, @@ -24739,7 +24758,8 @@ "supports_url_context": true, "supports_vision": true, "supports_web_search": true, - "tpm": 800000 + "tpm": 800000, + "web_search_billing_unit": "per_query" }, "openrouter/google/gemini-3.1-pro-preview": { "cache_read_input_token_cost": 2e-07, @@ -26286,14 +26306,16 @@ "mode": "responses", "supports_web_search": true, "supports_reasoning": false, - "supports_function_calling": true + "supports_function_calling": true, + "web_search_billing_unit": "per_query" }, "perplexity/google/gemini-3-flash-preview": { "litellm_provider": "perplexity", "mode": "responses", "supports_web_search": true, "supports_reasoning": false, - "supports_function_calling": true + "supports_function_calling": true, + "web_search_billing_unit": "per_query" }, "perplexity/google/gemini-2.5-pro": { "litellm_provider": "perplexity", @@ -31029,7 +31051,8 @@ "search_context_size_low": 0.014, "search_context_size_medium": 0.014, "search_context_size_high": 0.014 - } + }, + "web_search_billing_unit": "per_query" }, "vertex_ai/deep-research-pro-preview-12-2025": { "input_cost_per_image": 0.0011, diff --git a/tests/test_litellm/llms/gemini/test_cost_calculator.py b/tests/test_litellm/llms/gemini/test_cost_calculator.py index baa3aab318..9bb83aa7cf 100644 --- a/tests/test_litellm/llms/gemini/test_cost_calculator.py +++ b/tests/test_litellm/llms/gemini/test_cost_calculator.py @@ -1,9 +1,6 @@ import pytest -from litellm.llms.gemini.cost_calculator import ( - _is_gemini_3_model, - cost_per_web_search_request, -) +from litellm.llms.gemini.cost_calculator import cost_per_web_search_request from litellm.types.utils import PromptTokensDetailsWrapper, Usage @@ -18,22 +15,21 @@ def _make_usage(web_search_requests: int) -> Usage: ) -def test_gemini3_charged_per_query(): - """Gemini 3.x should charge per search query at $0.014.""" +def test_per_query_billing(): + """web_search_billing_unit=per_query charges per search query.""" model_info = { "key": "gemini/gemini-3-flash-preview", + "web_search_billing_unit": "per_query", "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_gemini2_charged_per_prompt(): - """Gemini 2.x should charge 1 grounded prompt regardless of query count.""" +def test_per_prompt_billing(): + """web_search_billing_unit=per_prompt (default) clamps to 1.""" model_info = { "key": "gemini/gemini-2.5-flash", "search_context_cost_per_query": { @@ -44,8 +40,8 @@ def test_gemini2_charged_per_prompt(): assert cost == pytest.approx(0.035 * 1) -def test_legacy_fallback(): - """Without search_context_cost_per_query, should fallback to $0.035 × 1.""" +def test_default_billing_unit_is_per_prompt(): + """Without web_search_billing_unit, defaults to per_prompt (clamp to 1).""" 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 * 1) @@ -53,7 +49,10 @@ def test_legacy_fallback(): def test_zero_requests(): """Zero web search requests should return zero cost.""" - model_info = {"key": "gemini/gemini-3-flash-preview"} + model_info = { + "key": "gemini/gemini-3-flash-preview", + "web_search_billing_unit": "per_query", + } cost = cost_per_web_search_request(usage=_make_usage(0), model_info=model_info) assert cost == 0.0 @@ -64,10 +63,3 @@ def test_no_usage_details(): 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 - - -def test_is_gemini_3_model(): - assert _is_gemini_3_model({"key": "gemini/gemini-3-flash-preview"}) - assert _is_gemini_3_model({"key": "gemini/gemini-3.1-pro-preview"}) - assert not _is_gemini_3_model({"key": "gemini/gemini-2.5-flash"}) - assert not _is_gemini_3_model({"key": "gemini/gemini-2.0-flash"})