diff --git a/litellm/llms/you_com/search/transformation.py b/litellm/llms/you_com/search/transformation.py index 0c7916e4c05..7608691a79a 100644 --- a/litellm/llms/you_com/search/transformation.py +++ b/litellm/llms/you_com/search/transformation.py @@ -43,7 +43,7 @@ class YouComSearchConfig(BaseSearchConfig): # Keyed tier (higher rate limits): authenticate with X-API-Key. YOU_COM_API_BASE = "https://ydc-index.io" # Keyless free tier: IP-throttled (100 queries/day) and requires no auth. - # Used automatically when YOUCOM_API_KEY is not set. + # Used automatically when no API key (YDC_API_KEY / YOUCOM_API_KEY) is set. YOU_COM_FREE_API_BASE = "https://api.you.com/v1/agents/search" @staticmethod @@ -60,14 +60,15 @@ class YouComSearchConfig(BaseSearchConfig): """ Set headers for the You.com Search API. - If YOUCOM_API_KEY (or an explicit api_key) is present, use the keyed - endpoint with the `X-API-Key` header. Otherwise fall through to the - keyless free tier; no auth header is required. + If an API key is present (YDC_API_KEY, the legacy YOUCOM_API_KEY, or an + explicit api_key), use the keyed endpoint with the `X-API-Key` header. + Otherwise fall through to the keyless free tier; no auth header is + required. """ api_key = self.resolve_server_api_key( caller_api_key=api_key, caller_api_base=api_base, - key_env_vars=("YOUCOM_API_KEY",), + key_env_vars=("YDC_API_KEY", "YOUCOM_API_KEY"), base_env_var="YOUCOM_API_BASE", default_api_base=self.YOU_COM_API_BASE, ) @@ -92,14 +93,18 @@ class YouComSearchConfig(BaseSearchConfig): Pick the endpoint based on whether an API key is configured. - api_base explicit override -> use it as-is (normalized) - - YOUCOM_API_KEY set -> keyed endpoint (ydc-index.io/v1/search) + - YDC_API_KEY / YOUCOM_API_KEY -> keyed endpoint (ydc-index.io/v1/search) - no key -> keyless free tier (api.you.com/v1/agents/search) """ if api_base is None: api_base = get_secret_str("YOUCOM_API_BASE") if api_base is None: - api_key = kwargs.get("api_key") or get_secret_str("YOUCOM_API_KEY") + api_key = ( + kwargs.get("api_key") + or get_secret_str("YDC_API_KEY") + or get_secret_str("YOUCOM_API_KEY") + ) if api_key: api_base = self.YOU_COM_API_BASE else: diff --git a/litellm/proxy/example_config_yaml/websearch_interception_config.yaml b/litellm/proxy/example_config_yaml/websearch_interception_config.yaml index 3b9e4e9ef50..bf6be24299e 100644 --- a/litellm/proxy/example_config_yaml/websearch_interception_config.yaml +++ b/litellm/proxy/example_config_yaml/websearch_interception_config.yaml @@ -8,7 +8,7 @@ search_tools: - search_tool_name: "my-perplexity-search" litellm_params: search_provider: "perplexity" - # Alternative provider example (requires YOUCOM_API_KEY): + # Alternative provider example (requires YDC_API_KEY): # - search_tool_name: "my-you-com-search" # litellm_params: # search_provider: "you_com" diff --git a/tests/test_litellm/llms/you_com/test_you_com_search.py b/tests/test_litellm/llms/you_com/test_you_com_search.py index eacc495cede..74577edafe5 100644 --- a/tests/test_litellm/llms/you_com/test_you_com_search.py +++ b/tests/test_litellm/llms/you_com/test_you_com_search.py @@ -20,9 +20,11 @@ class TestYouComSearch: @pytest.fixture(autouse=True) def _set_api_key(self, monkeypatch): """ - Default fixture: YOUCOM_API_KEY is set, scoped to this test. - Tests that need the key absent should call `monkeypatch.delenv` themselves. + Default fixture: the canonical YDC_API_KEY is cleared and the legacy + YOUCOM_API_KEY is set, scoped to this test. Tests that need the key + absent should call `monkeypatch.delenv` themselves. """ + monkeypatch.delenv("YDC_API_KEY", raising=False) monkeypatch.setenv("YOUCOM_API_KEY", "test-api-key") @pytest.mark.asyncio @@ -328,6 +330,71 @@ class TestYouComSearch: headers = call_args.kwargs.get("headers", {}) assert headers["X-API-Key"] == "my-programmatic-key" + @pytest.mark.asyncio + async def test_you_com_search_prefers_ydc_api_key_over_legacy(self, monkeypatch): + """ + YDC_API_KEY is the canonical env var and must take precedence over the + legacy YOUCOM_API_KEY when both are set: the keyed endpoint is selected + and X-API-Key carries the YDC_API_KEY value. + """ + monkeypatch.setenv("YDC_API_KEY", "ydc-key") + monkeypatch.setenv("YOUCOM_API_KEY", "legacy-key") + + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "results": {"web": [], "news": []}, + "metadata": {}, + } + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_post: + mock_post.return_value = mock_response + + await litellm.asearch( + query="anything", + search_provider="you_com", + ) + + call_args = mock_post.call_args + assert call_args.kwargs["url"] == "https://ydc-index.io/v1/search" + headers = call_args.kwargs.get("headers", {}) + assert headers["X-API-Key"] == "ydc-key" + + @pytest.mark.asyncio + async def test_you_com_search_legacy_youcom_api_key_still_works(self, monkeypatch): + """ + Backwards compatibility: with only the legacy YOUCOM_API_KEY set (and + YDC_API_KEY absent), the keyed endpoint is still selected. + """ + monkeypatch.delenv("YDC_API_KEY", raising=False) + monkeypatch.setenv("YOUCOM_API_KEY", "legacy-key") + + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "results": {"web": [], "news": []}, + "metadata": {}, + } + + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_post: + mock_post.return_value = mock_response + + await litellm.asearch( + query="anything", + search_provider="you_com", + ) + + call_args = mock_post.call_args + assert call_args.kwargs["url"] == "https://ydc-index.io/v1/search" + headers = call_args.kwargs.get("headers", {}) + assert headers["X-API-Key"] == "legacy-key" + def test_you_com_search_complete_url_uses_programmatic_api_key(self, monkeypatch): """ get_complete_url selects the keyed endpoint from a forwarded api_key even