diff --git a/litellm/llms/exa_ai/search/cost_calculator.py b/litellm/llms/exa_ai/search/cost_calculator.py index a77e676f959..4b1d561cf47 100644 --- a/litellm/llms/exa_ai/search/cost_calculator.py +++ b/litellm/llms/exa_ai/search/cost_calculator.py @@ -9,6 +9,7 @@ EXA_AI_ADDITIONAL_RESULT_COST: Final = 0.001 EXA_AI_STANDARD_SEARCH_MODEL: Final = "exa_ai/search" EXA_AI_PRICING_MODEL_BY_TYPE: Final[Mapping[str, str]] = MappingProxyType( { + "lite": "exa_ai/search-lite", "instant": "exa_ai/search-instant", "deep-lite": "exa_ai/search-deep", "deep": "exa_ai/search-deep", diff --git a/litellm/llms/exa_ai/search/types.py b/litellm/llms/exa_ai/search/types.py index 4e02325b3b9..3fd1379701f 100644 --- a/litellm/llms/exa_ai/search/types.py +++ b/litellm/llms/exa_ai/search/types.py @@ -5,7 +5,7 @@ from typing import Literal, TypeAlias from typing_extensions import ReadOnly, Required, TypedDict -ExaSearchMode: TypeAlias = Literal["instant", "fast", "auto", "deep-lite", "deep", "deep-reasoning"] +ExaSearchMode: TypeAlias = Literal["lite", "instant", "fast", "auto", "deep-lite", "deep", "deep-reasoning"] ExaPageSection: TypeAlias = Literal["header", "navigation", "banner", "body", "sidebar", "footer", "metadata"] diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 6c80b4b50e9..c44fc2d97c2 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -24437,6 +24437,14 @@ }, "mode": "search" }, + "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", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 6c80b4b50e9..c44fc2d97c2 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -24437,6 +24437,14 @@ }, "mode": "search" }, + "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", diff --git a/tests/unit/llms/exa_ai/search/test_transformation.py b/tests/unit/llms/exa_ai/search/test_transformation.py index 08243a7be84..0e65832d22f 100644 --- a/tests/unit/llms/exa_ai/search/test_transformation.py +++ b/tests/unit/llms/exa_ai/search/test_transformation.py @@ -519,6 +519,7 @@ def test_exa_estimate_prices_one_request_however_many_queries_it_joins() -> None @pytest.mark.parametrize( ("search_type", "pricing_model"), [ + ("lite", "exa_ai/search-lite"), ("instant", "exa_ai/search-instant"), ("auto", "exa_ai/search"), ("fast", "exa_ai/search"), @@ -557,3 +558,21 @@ def test_exa_estimate_ignores_invalid_result_counts(count: object) -> None: assert search_provider_cost_per_query( "exa_ai/search", "exa_ai", optional_params={"numResults": count} ) == search_provider_cost_per_query("exa_ai/search", "exa_ai") + + +@pytest.mark.parametrize("asynchronous", [False, True]) +@pytest.mark.usefixtures("exa_api_key") +@pytest.mark.asyncio +async def test_lite_search_preserves_mode_and_highlights(respx_mock: respx.MockRouter, asynchronous: bool) -> None: + route: Final = respx_mock.post(EXA_SEARCH_URL).respond(json=FULL_RESPONSE) + response: Final = ( + await litellm.asearch(query="question", search_provider="exa_ai", type="lite", contents={"highlights": True}) + if asynchronous + else litellm.search(query="question", search_provider="exa_ai", type="lite", contents={"highlights": True}) + ) + assert json.loads(route.calls.last.request.content) == { + "query": "question", + "type": "lite", + "contents": {"highlights": True}, + } + assert response.results[0].snippet == "\n\n".join(FULL_RESULT["highlights"])