Skip to content
Merged
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
84 changes: 84 additions & 0 deletions docs/my-website/docs/completion/web_search.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,3 +596,87 @@ Expected Response

</TabItem>
</Tabs>

## Web Search Cost Tracking

LiteLLM tracks web search costs automatically based on provider-specific billing models. The cost is added on top of the standard token-based pricing.

### How providers charge for web search

| Provider | Billing Unit | How it works |
|----------|-------------|--------------|
| **Gemini 3.x** (3-flash, 3-pro, 3.1-*) | Per search query | Each internal search query is billed individually. One prompt may trigger multiple queries. |
| **Gemini 2.x** (2.0-flash, 2.5-flash, 2.5-pro) | Per grounded prompt | Flat fee per API call that uses grounding, regardless of how many queries are executed internally. |
| **OpenAI** (gpt-4o-search, gpt-5-search) | Per search context size | Cost varies by `search_context_size` (`low`, `medium`, `high`). |
| **Anthropic** (Claude with web search) | Per search request | Fixed cost per web search tool invocation. |
| **Perplexity** (sonar, sonar-pro) | Per search context size | Cost varies by `search_context_size`. |

### Pricing configuration

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`** *(on Gemini models)*: `"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,
"search_context_size_high": 0.014
}
},
"gemini/gemini-2.5-flash": {
"search_context_cost_per_query": {
"search_context_size_low": 0.035,
"search_context_size_medium": 0.035,
"search_context_size_high": 0.035
}
}
}
```

:::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:
- model_name: gemini-3-flash
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
search_context_size_high: 0.014
```

### How LiteLLM tracks search usage

The number of web search requests is stored in `usage.prompt_tokens_details.web_search_requests`. LiteLLM extracts this from each provider's response:

- **Gemini**: Extracted from `groundingMetadata.webSearchQueries` in the response. For Gemini 2.x, clamped to 1 (per-prompt billing).
- **OpenAI**: Reported directly in the usage metadata.
- **Anthropic**: Reported via `server_tool_use.web_search_requests`.
- **xAI**: Mapped from `num_sources_used` in the response.

```python
response = litellm.completion(
model="gemini/gemini-3-flash-preview",
messages=[{"role": "user", "content": "Latest tech news?"}],
web_search_options={"search_context_size": "medium"},
)

# Check web search usage
print(response.usage.prompt_tokens_details.web_search_requests) # e.g., 3

# Get total cost (includes token cost + web search cost)
cost = litellm.completion_cost(completion_response=response)
print(f"Total cost: ${cost}")
```
27 changes: 18 additions & 9 deletions litellm/llms/gemini/cost_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,24 @@ 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.
Calculates the cost of web search (grounding with Google Search).

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
``model_info`` when available, falling back to $0.035 for models not
yet updated in the pricing JSON.
"""
from litellm.types.utils import PromptTokensDetailsWrapper

# cost per web search request
cost_per_web_search_request = 35e-3
_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
if (
usage is not None
and usage.prompt_tokens_details is not None
Expand All @@ -47,10 +56,10 @@ 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
# 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 total_cost
return _cost * number_of_web_search_requests
39 changes: 11 additions & 28 deletions litellm/llms/vertex_ai/gemini/cost_calculator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""
Cost calculator for Vertex AI Gemini.

Used because there are differences in how Google AI Studio and Vertex AI Gemini handle web search requests.
Delegates to the shared Gemini cost calculator which reads pricing and
billing unit from model_info.
"""

from typing import TYPE_CHECKING
Expand All @@ -14,32 +15,14 @@ def cost_per_web_search_request(usage: "Usage", model_info: "ModelInfo") -> floa
"""
Calculate the cost of a web search request for Vertex AI Gemini.

Vertex AI charges $35/1000 prompts, independent of the number of web search requests.
Billing differs 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).

For a single call, this is $35e-3 USD.

Args:
usage: The usage object for the web search request.
model_info: The model info for the web search request.

Returns:
The cost of the web search request.
Delegates to the shared Gemini cost calculator.
"""
from litellm.types.utils import PromptTokensDetailsWrapper

# check if usage object has web search requests
cost_per_llm_call_with_web_search = 35e-3

makes_web_search_request = False
if (
usage is not None
and usage.prompt_tokens_details is not None
and isinstance(usage.prompt_tokens_details, PromptTokensDetailsWrapper)
):
makes_web_search_request = True

# Calculate total cost
if makes_web_search_request:
return cost_per_llm_call_with_web_search
else:
return 0.0
from litellm.llms.gemini.cost_calculator import (
cost_per_web_search_request as _gemini_cost,
)

return _gemini_cost(usage=usage, model_info=model_info)
Original file line number Diff line number Diff line change
Expand Up @@ -2378,6 +2378,15 @@ def _transform_google_generate_content_to_openai_model_response(
usage = VertexGeminiConfig._calculate_usage(
completion_response=completion_response
)

web_search_requests = VertexGeminiConfig._calculate_web_search_requests(
grounding_metadata
)
if web_search_requests is not None:
cast(
PromptTokensDetailsWrapper, usage.prompt_tokens_details
).web_search_requests = web_search_requests

setattr(model_response, "usage", usage)

## ADD METADATA TO RESPONSE ##
Expand Down
Loading
Loading