feat(you_com): prefer YDC_API_KEY env var (keep YOUCOM_API_KEY fallback)

YDC_API_KEY is You.com's canonical API key env var (used by its own docs,
SDK, and the LangChain/CrewAI/DSPy integrations). The you_com search provider
previously only read YOUCOM_API_KEY. This makes YDC_API_KEY the preferred
name while keeping YOUCOM_API_KEY as a backward-compatible fallback, so
existing configs keep working.

- resolve_server_api_key now reads ("YDC_API_KEY", "YOUCOM_API_KEY") in order
- get_complete_url checks YDC_API_KEY before YOUCOM_API_KEY
- example config comment updated to YDC_API_KEY
- tests: YDC takes precedence over legacy key; legacy key still works

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Brian Sparker 2026-06-24 21:49:46 -04:00
parent fa307fe9e5
commit df1bd1729b
3 changed files with 82 additions and 10 deletions

View file

@ -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:

View file

@ -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"

View file

@ -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