feat(search): add you_com as a search provider#28370
Conversation
Registers You.com Search API as a first-class `search_provider` in the `search_tools` registry, alongside Tavily, Exa, Perplexity, etc. - New adapter: litellm/llms/you_com/search/transformation.py - POSTs to https://ydc-index.io/v1/search - Auth: X-API-Key from YOUCOM_API_KEY (or explicit api_key) - Maps Perplexity unified spec: max_results -> count, search_domain_filter -> include_domains, country -> country - Flattens results.web + results.news into a single SearchResult list; snippet prefers snippets[0], falls back to description; page_age -> date - Registry: SearchProviders.YOU_COM in litellm/types/utils.py and wired into ProviderConfigManager.get_provider_search_config() - Pricing entry: model_prices_and_context_window.json (placeholder $0.0; happy to adjust to maintainers' preferred public number) - Docs: example router config snippet and example proxy yaml updated - Tests: tests/search_tests/test_you_com_search.py - 5 mocked tests (payload shape, domain filter mapping, snippet fallback, news flattening, missing-api-key error) Refs upstream expansion signal: BerriAI#15942
Greptile SummaryThis PR registers You.com as a new
Confidence Score: 4/5Safe to merge; the changes are additive and self-contained, touching only the new provider module and its registry hookup. The core wiring and response normalisation are correct and mirror existing providers closely. Two small issues in the adapter itself are worth addressing before shipping: the URL normalisation logic can double-append /v1/search if a custom base URL has a trailing slash, and the country parameter is not lowercased while every other provider that supports it does lowercase it. Tests also mutate the process environment without cleanup, which could silently affect unrelated test runs. litellm/llms/you_com/search/transformation.py (URL construction and country normalisation) and tests/search_tests/test_you_com_search.py (env var cleanup).
|
| Filename | Overview |
|---|---|
| litellm/llms/you_com/search/transformation.py | New You.com search adapter: transforms queries, maps params, normalises responses. Minor URL-path construction edge case and country case inconsistency. |
| litellm/types/utils.py | Adds YOU_COM = "you_com" to the SearchProviders enum; straightforward and consistent with other entries. |
| litellm/utils.py | Wires YouComSearchConfig into ProviderConfigManager; mirrors the pattern used by all other search providers. |
| tests/search_tests/test_you_com_search.py | Five mock-based tests covering core paths. Sets YOUCOM_API_KEY directly in os.environ without fixture-based cleanup, which can pollute subsequent tests. |
| model_prices_and_context_window.json | Adds you_com/search entry at 0.0 input_cost_per_query; placeholder pricing, correctly structured. |
| litellm/integrations/websearch_interception/ARCHITECTURE.md | Adds you_com example to the provider snippet; documentation change that should be in the litellm-docs repo per project policy. |
| litellm/proxy/example_config_yaml/websearch_interception_config.yaml | Adds commented-out you_com example; harmless documentation comment. |
Reviews (1): Last reviewed commit: "feat(search): add you_com as a search pr..." | Re-trigger Greptile
| if not api_base.endswith("/v1/search"): | ||
| api_base = f"{api_base.rstrip('/')}/v1/search" |
There was a problem hiding this comment.
The trailing-slash guard and the path-append are applied in the wrong order.
endswith("/v1/search") is evaluated on the raw string, so a custom base ending in /v1/search/ fails the check, then rstrip('/') removes only the slash, and the result is …/v1/search/v1/search. The base should be normalised before the check.
| if not api_base.endswith("/v1/search"): | |
| api_base = f"{api_base.rstrip('/')}/v1/search" | |
| api_base = api_base.rstrip("/") | |
| if not api_base.endswith("/v1/search"): | |
| api_base = f"{api_base}/v1/search" |
| if "country" in optional_params: | ||
| request_data["country"] = optional_params["country"] |
There was a problem hiding this comment.
TavilySearchConfig.transform_search_request normalises the country value with .lower() before sending it upstream (see litellm/llms/tavily/search/transformation.py:141). The You.com adapter passes the value through as-is, so a caller using country="US" (uppercase, which is the idiomatic form in the unified spec) may receive different behaviour depending on how You.com's API validates it. Consider applying .lower() here for consistency or documenting the accepted case.
| if "country" in optional_params: | |
| request_data["country"] = optional_params["country"] | |
| if "country" in optional_params: | |
| request_data["country"] = optional_params["country"].lower() |
| Validate the You.com search request payload structure without real API calls. | ||
| """ |
There was a problem hiding this comment.
Environment variable leaks between tests
Each async test opens by writing os.environ["YOUCOM_API_KEY"] = "test-api-key" directly and never removes it; test_you_com_search_raises_without_api_key then calls os.environ.pop("YOUCOM_API_KEY", None) to simulate a missing key, but that mutation is permanent for the rest of the session. If test ordering changes or tests are parallelised, this creates false positives or false negatives. Prefer monkeypatch.setenv / monkeypatch.delenv (or a pytest.fixture with yield) so env state is restored automatically after each test.
| - search_tool_name: "my-tavily-tool" | ||
| litellm_params: | ||
| search_provider: "tavily" | ||
| - search_tool_name: "my-you-com-tool" | ||
| litellm_params: | ||
| search_provider: "you_com" | ||
| ``` |
There was a problem hiding this comment.
Documentation changes should live in the litellm-docs repo
Per project policy, documentation additions belong in the external docs repo rather than here. This snippet addition and the corresponding change to litellm/proxy/example_config_yaml/websearch_interception_config.yaml appear to be documentation-only content that should be tracked separately.
Rule Used: Prevent documentation from being added - needs to ... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…o test Addresses Greptile inline review comments on BerriAI#28370: - get_complete_url: strip trailing slashes from api_base *before* the endswith("/v1/search") check, so a custom base like ".../v1/search/" doesn't become ".../v1/search/v1/search". - transform_search_request: .lower() country before sending, matching Tavily's convention so callers using the unified spec form ("US") get consistent behavior across providers. - Tests: replace direct os.environ writes with an autouse monkeypatch fixture so YOUCOM_API_KEY is set per-test and removed afterwards. The missing-key test now uses monkeypatch.delenv. New test asserts the trailing-slash normalization above. Reverts the ARCHITECTURE.md / example yaml edits per the reviewer note that documentation changes belong in the litellm-docs repo.
|
Thanks for the review! Pushed fixups in 903e44e:
All 6 tests pass locally. |
You.com offers an IP-throttled keyless endpoint that returns the same response shape as the keyed one (~100 queries/day, no signup). This is a significant onboarding lever - mirrors the keyless DuckDuckGo/SearXNG providers already in the search_tools registry. Behavior: - YOUCOM_API_KEY set -> keyed: POST https://ydc-index.io/v1/search (X-API-Key header) - no key -> free: POST https://api.you.com/v1/agents/search (no auth) - YOUCOM_API_BASE override -> honored as-is Tests: - New: test_you_com_search_keyless_free_tier - asserts URL + absence of X-API-Key when no key is configured. - New: test_you_com_search_validate_environment_keyless - asserts the config no longer raises when the key is absent. - Removed: test_you_com_search_raises_without_api_key (the precondition no longer holds). - Existing payload/domain-filter/etc tests still cover keyed mode via the autouse YOUCOM_API_KEY fixture. Verified both endpoints accept POST + return identical JSON shape: results.web[] / results.news[] with title, url, snippets, description, page_age.
|
Added keyless free-tier support in 6eb1912. If
Both endpoints return the same PR description updated to reflect the dual-tier behavior. |
Adding `litellm/llms/you_com/` requires a corresponding entry in
provider_endpoints_support.json or the
code-quality/check_provider_folders_documented CI check fails.
Follows the compact tavily/serper pattern - endpoints: { search: true }.
Local run of the check now reports "All 114 provider folders are documented".
|
Merge Confidence: 3/5 ❌ BLOCKED Score docked for: 1 PR-related CI failure (codecov/patch). Drill-down PR-related failures |
|
please include a video of this working as expected |
|
you should also file an adjacent pr on litellm-docs so people know this exists |
The litellm CI workflows scope unit tests to `tests/test_litellm/...` (see test-unit-llm-providers.yml: `tests/test_litellm/llms` path), so tests living under `tests/search_tests/` are never run in CI - which is why codecov reports 0% patch coverage for the new adapter even though the unit tests exist and pass locally. Move test_you_com_search.py into `tests/test_litellm/llms/you_com/` so the test-unit-llm-providers job picks it up. 7/7 tests still pass at the new location. (Sibling search-only providers - tavily, exa_ai, brave, etc. - still live only in `tests/search_tests/` and would benefit from the same move, but that is out of scope for this PR.)
|
Thanks @krrish-berri-2 — addressing your feedback: 1. codecov/patch blocker — root cause was that 2. Adjacent docs PR — filed at BerriAI/litellm-docs#182 (new 3. Demo video — recording in progress, will attach to this PR shortly. FYI on (1): every other search-only provider in the registry ( |
The keyless free-tier endpoint (api.you.com/v1/agents/search) advertises Content-Encoding: gzip but returns a body that httpx's decoder rejects with `zlib.error: Error -3 while decompressing data: incorrect header check`, surfacing as litellm.APIConnectionError in user code. curl works because it doesn't request compression by default. Pin Accept-Encoding: identity in validate_environment so the upstream server skips compression entirely. Harmless on the keyed endpoint (ydc-index.io/v1/search) which negotiates content-encoding correctly. The header uses setdefault so a caller-supplied Accept-Encoding still takes precedence. (Server-side bug has been flagged to the You.com team separately - once fixed there, this workaround can be removed.) New unit test: test_you_com_search_pins_identity_accept_encoding.
Summary
Registers You.com as a first-class
search_providerin thesearch_toolsregistry, alongside Tavily, Exa AI, Perplexity, Parallel AI, Brave, Google PSE, DataForSEO, Firecrawl, SearXNG, Linkup, DuckDuckGo, SearchAPI, and Serper.Once registered,
you_comworks transparently with thelitellm_web_searchinterception layer across all 25+ supported LLM providers (OpenAI, Anthropic, Vertex, Bedrock, etc.).Keyless free tier by default. If
YOUCOM_API_KEYis not set, the adapter falls through to You.com's keyless endpoint (api.you.com/v1/agents/search, ~100 queries/day, IP-throttled, no signup). This matches the existing keyless-default pattern used byduckduckgoandsearxngand means LiteLLM users can try this provider with zero configuration. SettingYOUCOM_API_KEYupgrades to the keyed endpoint (ydc-index.io/v1/search) with higher rate limits.Refs upstream expansion signal: #15942.
What's in here
New adapter —
litellm/llms/you_com/search/transformation.pyYOUCOM_API_KEYset →POST https://ydc-index.io/v1/searchwithX-API-KeyheaderPOST https://api.you.com/v1/agents/search(keyless free tier)YOUCOM_API_BASEoverride honored as-ismax_results→countsearch_domain_filter→include_domainscountry→country(lowercased to match Tavily's convention)max_tokens_per_page→ not applicable, ignoredresults.web+results.newsinto a singleSearchResultlistsnippetpreferssnippets[0], falls back todescriptionpage_age→dateRegistry wiring
SearchProviders.YOU_COM = "you_com"inlitellm/types/utils.pyYouComSearchConfigwired intoProviderConfigManager.get_provider_search_config()inlitellm/utils.pyPricing
you_com/searchentry inmodel_prices_and_context_window.jsonat0.0per query. Happy to update to the public pricing number you'd prefer — let me know.Tests —
tests/search_tests/test_you_com_search.py(7 tests, all pass locally)test_you_com_search_request_payload— keyed URL,X-API-Keyheader,countmapping, response normalizationtest_you_com_search_domain_filter_and_country—include_domains/ lowercasedcountrymapping; unified-spec param names don't leak throughtest_you_com_search_snippet_fallback_to_description— snippet falls back whensnippetsis emptytest_you_com_search_news_results_appended— news results flatten in after webtest_you_com_search_complete_url_handles_trailing_slash— normalize trailing slashes on customapi_basetest_you_com_search_keyless_free_tier— keyless URL + absence ofX-API-Keywhen no key configuredtest_you_com_search_validate_environment_keyless— config doesn't raise on missing key (keyless is valid)Usage
Zero-config (keyless free tier):
With API key (higher limits):
Router YAML:
Test plan
pytest tests/search_tests/test_you_com_search.py -v— 7/7 pass locallytests/search_tests/test_tavily_search.py— passes (adjacent regression sanity)ydc-index.io/v1/searchandapi.you.com/v1/agents/searchreturn matching JSON shapes