Conversation
…ect#327) Add Tavily as the third backend for the gateway-owned web_search tool (Phase 3 of vllm-project#291). Each query is one typed POST /search with a bearer token; domain filters are forwarded natively and re-checked client-side, count is clamped to 20, freshness maps to time_range or a date range widened by one day on each side because Tavily's bounds are exclusive, language keeps Tavily's documented compound tags and otherwise reduces to the primary subtag, and 401/403/429/432/433 fail the web_search_call without retry or credential leakage. Move the shared response helpers into provider.rs to keep mod.rs within the file-size policy, and make WebSearchProviderKind::ALL a static slice so new providers do not change its type. Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Zheng-Lu
force-pushed
the
feat/327-tavily-provider
branch
from
September 18, 2026 00:59
3952199 to
0b6b9e3
Compare
Zheng-Lu
marked this pull request as ready for review
September 18, 2026 01:04
Zheng-Lu
requested review from
bbrowning,
franciscojavierarceo,
haoshan98,
jiahuei,
leseb,
maralbahari,
noobHappylife,
qandrew and
tjtanaa
as code owners
September 18, 2026 01:04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #327 (Part of #291).
Summary
Adds Tavily as the third backend for the gateway-owned
web_searchtool (Phase 3 of #291, closes #327), following the typed provider contract from #293/#294 and the Brave pattern from #324.WebSearchProviderKind::Tavily(config.rs):config_name = "tavily",TAVILY_API_KEY, default endpointhttps://api.tavily.com, no provider-specific concurrency ceiling (Tavily plans are metered per minute, so it inherits the gateway limit).ALL,FromStr, and the "expected one of" message list all three providers. No public signature changes: the enum andWebSearchProviderConfigare#[non_exhaustive], andWebSearchHandler::from_configgains a match arm.WebSearchProviderKind::ALLis now a&'static [Self]slice so adding a provider no longer changes its type.tool/web_search/tavily.rs(new, 391 production lines):TavilySearchProviderposts a typedTavilySearchBodytoPOST {base_url}/searchwithContent-Type: application/json,Accept: application/json, andAuthorization: Bearer <key>. The key is never placed in the body andAccept-Encodingis never sent (corereqwesthas no gzip). Request shaping:count/search_context_size→max_results, clamped to 20 with a debug log;freshnessday/week/month/year →time_range;YYYY-MM-DDtoYYYY-MM-DD→start_date/end_datewidened by one day on each side (checkedpred_opt/succ_opt), because the gateway's range is inclusive while Tavily documents its bounds as "after" / "before";allowed_domains/blocked_domains(request config wins) or the model'sinclude_domains/exclude_domains→ nativeinclude_domains/exclude_domains, and re-applied client-side throughDomainFilteras defense in depth; the shared include+exclude/boost conflict rule is preserved;languagekeeps Tavily's documented compound tags lowercased (zh-CN→zh-cn) and otherwise reduces to the primary subtag (en-GB→en);safesearch→ booleansafe_search(off→ omitted);include_published_date: truesopage_ageis populated for thegeneraltopic;country(Tavily wants full country names, not ISO codes) and the You.com-specificlivecrawl*,crawl_timeout,boost_domainsare dropped with one debug log.topic: "general"request per query: every hit lands inresults.web,results.newsstays empty. A secondnewsrequest per query would double credit spend; this is documented.#[serde(default)]structs readresults[] { title, url, content, published_date }plusresponse_timeandrequest_id;content→description,published_date→page_age.metadata[]carries"provider": "tavily",request_idassearch_uuid, andresponse_timeaslatency(existingskip_serializing_ifrules, You.com output unchanged).TAVILY_API_KEYwithout reading the body; 429 is not retried and surfacesRetry-Afterverbatim (or says none was provided); Tavily's non-standard 432/433 plan-limit statuses are reported without retry; anything else reports status + size-bounded body.mod.rssize: adding the module pushedmod.rsto 507 production lines, so the sharednull_as_defaultandread_response_limitedhelpers moved toprovider.rs(where the provider contract lives). Both were and remain crate-private, so no public API changed.mod.rsis now 476 lines; no other file changed behavior.web_search_config.rs/config_file.rsare provider-generic already; only doc comments and tests changed.README.md(feature bullet, quick start,config.tomlcomments, provider table, Tavily section),docs/deploying/README.md,docs/deploying/kubernetes.md,ARCHITECTURE.md,CHANGELOG.md.No new dependencies; the request body is serialized with the existing
serde_jsonrather than enablingreqwest'sjsonfeature.Deliberately kept in parity with
brave.rs/you.rsrather than changed here (candidates for a cross-provider follow-up): generic non-2xx failures echo the size-bounded upstream body (401/403/429/432/433 never do),Retry-Afteris whitespace-trimmed, and the caller-suppliedreqwest::Clientcontract is gateway-wide.Test Plan
cargo fmt -- --checkcargo clippy --all-targets -- -D warningscargo test— 1606 passed, 0 failed (You.com and Brave suites untouched and green)pre-commit run --all-files— all hooks pass, including the Rust production file-size policy (mod.rs476,tavily.rs391,provider.rs144)crates/agentic-server-core/tests/web_search_tavily_test.rs(12 tests, Axum mock on127.0.0.1:0, nothing reaches the network):max_results,time_range,language,safe_search,exclude_domains, nocountry/livecrawl/api_key),Bearerauth,Content-Type, noAccept-Encoding, byte-exact model-facing output with"provider":"tavily"+search_uuid+latency,web_search_callpublic sourcesstart_date/end_datewidened by one day each side, nomax_resultswhen count unsetresults→{"web":[],"news":[]}with no invented sourcesexclude_domainsforwarded and enforcedTAVILY_API_KEY, never echoes the key or the upstream bodyRetry-Afterinteger and HTTP-date surfaced verbatim, absence reported, exactly one request (no retry)max_concurrent_queries = 1serializestavily.rs(13 tests) cover the private request/response types: payload rendering and omission of unset fields, clamping, freshness mapping including month/year boundary widening, language (zh-CN→zh-cn,zh-TW→zh,en-GB→en) and safe-search reduction, ignored arguments, config-over-args precedence, conflict rejection, envelope tolerance ({}, nulls, unknown keys), metadata serialization, defense-in-depth filteringconfig.rs,web_search_config.rs,config_file.rstests extended for the third provider (labels, parsing, env/file resolution,api_key_envoverride, provider switching without pinned credentials, TOML round trip)