From e9727d2738a03e27b03da2db12755bbd6e450f8c Mon Sep 17 00:00:00 2001 From: Aaron Rodrigues Date: Wed, 28 Oct 2026 22:13:56 -0700 Subject: [PATCH] feat(exa): reuse provider list-price fallback Adapted the fallback estimator and Exa catalog entries from #46525. Follow-up refinements by Kes and Mateo retain native count precedence, summary charges, Lite pricing, and compatibility tiers. Source commits: d0a37501fc337d2ea8381157873ee60f15cb376c, d150fb91db109f0a6e9571db87bf204f00302102, 8185114b5bcd9323c871a6075fe9c743040d7e18, 78acd12678af67b0cf2967f9aa1492e8ae6bc632, f691d27d5fc85619111132a40ecb4371565f7c64, 43c59c2cd713bc6221d2df375a4997cdfb60d54f, 96a2b14005e7032734b7453eaa228663143a2fe2 Co-authored-by: kesku Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com> --- litellm/llms/exa_ai/search/cost_calculator.py | 46 +++++++++++++++++++ ...odel_prices_and_context_window_backup.json | 44 ++++++++++++++++++ model_prices_and_context_window.json | 44 ++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/litellm/llms/exa_ai/search/cost_calculator.py b/litellm/llms/exa_ai/search/cost_calculator.py index 5d6e38d5bad..2207ccecbb0 100644 --- a/litellm/llms/exa_ai/search/cost_calculator.py +++ b/litellm/llms/exa_ai/search/cost_calculator.py @@ -59,3 +59,49 @@ def exa_ai_price_set(entry: object) -> bool: fields: Final = _ENTRY.validate_python(entry) tiers: Final = fields.get("tiered_pricing") return fields.get("input_cost_per_query") is not None or (tiers is not None and not _legacy_tiers(tiers)) + + +EXA_AI_INCLUDED_RESULTS: Final = 10 +EXA_AI_ADDITIONAL_RESULT_COST: Final = 0.001 +EXA_AI_SUMMARY_COST_PER_PAGE: Final = 0.001 +EXA_AI_TYPES_WITHOUT_SUMMARIES: Final = frozenset({"lite"}) +_CONTENTS: Final = TypeAdapter(Mapping[str, object]) + + +def _non_negative_int(value: object) -> int | None: + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + return None + return value + + +def _requested_results(optional_params: Mapping[str, object]) -> int: + for key in ("max_results", "numResults"): + requested: Final = _non_negative_int(optional_params.get(key)) + if requested is not None: + return requested + return EXA_AI_INCLUDED_RESULTS + + +def _requests_summaries(optional_params: Mapping[str, object]) -> bool: + if optional_params.get("type") in EXA_AI_TYPES_WITHOUT_SUMMARIES: + return False + contents: Final = optional_params.get("contents") + return isinstance(contents, Mapping) and _CONTENTS.validate_python(contents).get("summary") not in (None, False) + + +def exa_ai_list_price(base_price: float, optional_params: Mapping[str, object]) -> float: + """ + List price of one Exa /search request, for responses that carry no `costDollars.total`. + + Multi-query searches are joined into a single request, so this prices one request: + the search type's base price, a per-result charge above the results that price includes, + and a per-page charge for each requested result when summaries are requested on a type that offers them. + """ + results: Final = _requested_results(optional_params) + additional_results: Final = max(results - EXA_AI_INCLUDED_RESULTS, 0) + summarized_pages: Final = results if _requests_summaries(optional_params) else 0 + return ( + base_price + + additional_results * EXA_AI_ADDITIONAL_RESULT_COST + + summarized_pages * EXA_AI_SUMMARY_COST_PER_PAGE + ) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index dc043dc4d26..e000b3543bc 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -24315,7 +24315,11 @@ "output_vector_size": 2560 }, "exa_ai/search": { + "input_cost_per_query": 0.007, "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request, covering up to 10 results. Each result above 10 adds $0.001, and each summarized page adds $0.001. Exa's reported costDollars.total takes precedence when present, unless an operator priced this entry. tiered_pricing keeps what older releases bill for this entry. Source: https://exa.ai/pricing (2026-10-15)" + }, "mode": "search", "tiered_pricing": [ { @@ -24334,6 +24338,46 @@ } ] }, + "exa_ai/search-lite": { + "input_cost_per_query": 0.004, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type lite, covering up to 10 results. Source: https://exa.ai/pricing (2026-10-22)" + }, + "mode": "search" + }, + "exa_ai/search-instant": { + "input_cost_per_query": 0.004, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type instant, covering up to 10 results. Source: https://exa.ai/pricing (2026-10-15)" + }, + "mode": "search" + }, + "exa_ai/search-deep": { + "input_cost_per_query": 0.012, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type deep, covering up to 10 results. Source: https://exa.ai/pricing (2026-10-15)" + }, + "mode": "search" + }, + "exa_ai/search-deep-lite": { + "input_cost_per_query": 0.012, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type deep-lite, covering up to 10 results. Source: https://exa.ai/docs/reference/pricing (2026-10-27)" + }, + "mode": "search" + }, + "exa_ai/search-deep-reasoning": { + "input_cost_per_query": 0.015, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type deep-reasoning, covering up to 10 results. Source: https://exa.ai/pricing (2026-10-15)" + }, + "mode": "search" + }, "firecrawl/search": { "litellm_provider": "firecrawl", "mode": "search", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index dc043dc4d26..e000b3543bc 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -24315,7 +24315,11 @@ "output_vector_size": 2560 }, "exa_ai/search": { + "input_cost_per_query": 0.007, "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request, covering up to 10 results. Each result above 10 adds $0.001, and each summarized page adds $0.001. Exa's reported costDollars.total takes precedence when present, unless an operator priced this entry. tiered_pricing keeps what older releases bill for this entry. Source: https://exa.ai/pricing (2026-10-15)" + }, "mode": "search", "tiered_pricing": [ { @@ -24334,6 +24338,46 @@ } ] }, + "exa_ai/search-lite": { + "input_cost_per_query": 0.004, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type lite, covering up to 10 results. Source: https://exa.ai/pricing (2026-10-22)" + }, + "mode": "search" + }, + "exa_ai/search-instant": { + "input_cost_per_query": 0.004, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type instant, covering up to 10 results. Source: https://exa.ai/pricing (2026-10-15)" + }, + "mode": "search" + }, + "exa_ai/search-deep": { + "input_cost_per_query": 0.012, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type deep, covering up to 10 results. Source: https://exa.ai/pricing (2026-10-15)" + }, + "mode": "search" + }, + "exa_ai/search-deep-lite": { + "input_cost_per_query": 0.012, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type deep-lite, covering up to 10 results. Source: https://exa.ai/docs/reference/pricing (2026-10-27)" + }, + "mode": "search" + }, + "exa_ai/search-deep-reasoning": { + "input_cost_per_query": 0.015, + "litellm_provider": "exa_ai", + "metadata": { + "notes": "Base price per /search request with type deep-reasoning, covering up to 10 results. Source: https://exa.ai/pricing (2026-10-15)" + }, + "mode": "search" + }, "firecrawl/search": { "litellm_provider": "firecrawl", "mode": "search",