feat: Add Serply as a selectable web_search provider
I maintainer di solito rispondono entro 2 giorni
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 38/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- rust
- Ambito
- api, backend, documentation, testing
Direzione di ricerca
Start with the existing web-search provider factory and configuration in tool/web_search/mod.rs and config.rs, then compare the planned implementation in tool/web_search/serply.rs with tests/web_search_serply_test.rs and the deployment documentation files. Done means the mock tests cover query mapping, filtering, errors, metadata, and concurrency configuration, while cargo fmt and clippy pass without regressions for You.com and Brave.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Problem statement / motivation
Part of #291
Status: proposal, sequenced after #329 so the second alternative provider reuses the same configuration plumbing
Scope: Serply provider integration, configuration plumbing, and tests
The providers #291 lines up cover three backend shapes: an independent index (Brave), an agent-oriented answer/snippet service (Tavily), and a self-hosted metasearch (SearXNG). None of them returns Google's own organic ranking. For a gateway-executed web_search that matters in two situations I keep hitting:
- Operator queries.
site:,-site:,inurl:,filetype:andintitle:"..."are the vocabulary models already know from training data, and they only behave as expected against Google. Brave and Tavily each honour a subset, differently. - Ranking parity. When an eval or a user report says "Google shows X first", a backend that returns Google's SERP is the only way to reproduce it.
Serply is a Google SERP API: GET /v1/search returns Google's organic results as JSON, with the operators above passing through untouched, country targeting via gl, recency via tbs, and news via tbm=nws in the same result shape. It is prepaid credits rather than a subscription (2,500 free credits on signup, no card), which speaks to the barrier-to-entry point in #291.
Disclosure: I work on Serply and this is the Serply team account. I am proposing the contract here first, per #291, rather than opening a PR cold; details below are measured against the live API, not read off the docs.
Proposed solution
A. Provider implementation (tool/web_search/serply.rs)
- Endpoint:
GET {base_url}/v1/searchwith query parameters. Base URL defaulthttps://api.serply.io. - Authentication:
X-Api-Key: {api_key}header, never a query parameter, so the key never lands in a logged URL. Key resolved fromSERPLY_API_KEY. - Identify the caller:
User-Agent: agentic-apion every request. - Deserialization: forward-compatible structs with
#[serde(default)]over the enveloperesults: [{ title, link, description, position, result_type, metadata: { display_url } }], mapped ontoWebSearchResultasurl <- link,title <- title,description <- description. Every row inresults[]isresult_type: "organic"; ads, answers, places and knowledge panels live in their own top-level arrays and are ignored. - Errors: a bad or missing key is HTTP 401 with a
{"detail": "..."}body, noterror. - Request:
GET /v1/search?q=<query>&num=10[&gl=<country>][&tbs=qdr:d|w|m|y]
X-Api-Key: <SERPLY_API_KEY>
User-Agent: agentic-api
B. Input adaptation policy matrix
| Parameter / input | Serply behaviour | Gateway adaptation policy |
|---|---|---|
include_domains / exclude_domains |
No server-side filter parameter; site: / -site: operators inside q do work |
Client-side DomainFilter post-filter, same as Brave. Rewriting q with site: operators stays out of scope, matching the #291 non-goal. |
count |
num is an upper bound with a ceiling of 10; num=20 returns the same page as num=10 |
Clamp to 10 with tracing::debug!, and trim the response to the requested count client-side, because a page can carry 9 or 11 rows at num=10. |
freshness |
tbs=qdr:d / qdr:w / qdr:m / qdr:y pass through to Google (measured: 1/9 overlap with an unfiltered baseline, same-day articles) |
Freshness::Day -> qdr:d, Week -> qdr:w, Month -> qdr:m, Year -> qdr:y. Freshness::Range is omitted with a debug log rather than forwarded: Google's cdr: form does not pass through, and sending it would name a filter the results do not honour. |
country |
gl=<code>, any ISO country code, documented |
Forwarded as gl. Without it the exit region is whatever Google assigns, so the docs should not describe the default as worldwide. |
language, safesearch |
Not verified against the live API | Ignored with tracing::debug! in the first cut, called out in the docs. |
You.com specifics (livecrawl, livecrawl_formats, crawl_timeout, boost_domains) |
No equivalent | Ignored with tracing::debug!, as Brave does. |
| News | tbm=nws returns news in the same results[] shape, num honoured |
Out of scope for the first cut (it is a second request per query); news stays empty. |
C. Concurrency and rate limiting
- Concurrency ceiling: no published per-second cap; inherit the gateway limit, configurable via
AGENTIC_WEB_SEARCH_MAX_CONCURRENT_QUERIESas in #327. - 429: fail
web_search_callwithout retry, surfacingRetry-Afterverbatim. - 401 / 403: fail naming
SERPLY_API_KEYwithout echoing the credential or the response body.
D. Configuration and precedence matrix
| Setting | Environment variable | config.toml key |
Default |
|---|---|---|---|
| Provider | AGENTIC_WEB_SEARCH_PROVIDER |
[web_search].provider |
"you" ("serply" selects this provider) |
| API key | named by api_key_env |
[web_search].api_key_env |
"SERPLY_API_KEY" |
| Base URL | AGENTIC_WEB_SEARCH_BASE_URL |
[web_search].base_url |
https://api.serply.io |
| Max concurrency | AGENTIC_WEB_SEARCH_MAX_CONCURRENT_QUERIES |
[web_search].max_concurrent_queries |
inherits gateway limit |
Adds WebSearchProviderKind::Serply (ALL grows to include it), default_api_key_env, default_base_url, the mod.rs factory arm, and the operator-facing docs rows in docs/deploying/README.md and docs/deploying/kubernetes.md beside Brave.
Scope boundaries
| In scope | Explicitly out of scope (deferred) |
|---|---|
SerplySearchProvider (GET /v1/search) |
News via tbm=nws and the Scholar vertical |
| Client-side domain post-filter, count clamp and trim, freshness and country mapping | site: query rewriting for domain filters |
Axum mock HTTP tests (tests/web_search_serply_test.rs) |
language / safesearch forwarding until measured |
Verification checklist
- 200 OK: query parameters shape correctly and
results[]maps toWebSearchResult, asserting the actual HTTP query string in the mock. - Count:
count=25sendsnum=10and returns at most 10; an 11-row page atcount=5returns 5. - Domain filtering:
include_domains/exclude_domainsare applied throughDomainFilter. - Freshness:
Daysendstbs=qdr:d; a range sends notbs. - 401 / 403: fails
web_search_call, referencesSERPLY_API_KEY, does not leak the credential. - 429: surfaces
Retry-Afterverbatim without retrying. - Metadata: echoes
"provider": "serply"inmetadata[]. - Zero regressions: You.com and Brave remain byte-identical.
- Hygiene:
cargo fmt,cargo clippy --all-targets -- -D warnings, file size limits.
Alternatives considered
- Reaching Google through SearXNG (#326) with its Google engine enabled. That works for self-hosted deployments but inherits SearXNG's scraping fragility and rate limits on shared IPs, and it strips the operator pass-through and
gltargeting that make the SERP shape useful. - Emulating domain filters by rewriting
qwithsite:operators. Serply would honour it, but #291 rules query rewriting out, and the client-sideDomainFilteralready gives the same guarantee.
Additional context
- API reference: https://serply.io/docs
- I can implement this against the #324 contract myself and open the PR once #329 lands, so the configuration plumbing is written once and the two providers do not conflict in
config.rsandmod.rs. If you would rather keep the provider series in one hand, this issue is the contract for whoever picks it up; either way, happy to answer questions about the API here.
- Lingua principale
- Rust
- Stelle
- 323
- Fork
- 87
- Merge medio
- 3g 2h
- PR unite (30g)
- 66
Preparare l'ambiente
- Include un Dockerfile o un file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di vllm-project/agentic-api
-
Fix codex-seed-model-cache.shForse già presa Una pull request collegata a questa issue è aperta o già unita. Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
vllm-project/agentic-api#358 · 6 commenti ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
vllm-project/agentic-api#452 ·
I maintainer di solito rispondono entro 2 giorni
-
feat(telemetry): trace and time gateway-executed built-in tools on the Messages pathForse già presa @Zheng-Lu l’ha presa 1 giorno fa. Apertaenhancement
Difficoltà 3/5 1-2 giorni Idoneità per principianti 55/100
vllm-project/agentic-api#449 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
Support gateway-executed (hosted) tool_search over deferred functions, namespaces, and MCP serversForse già presa @haoshan98 l’ha presa 2 giorni fa. Apertaenhancement
vllm-project/agentic-api#448 · 1 assegnatario ·
I maintainer di solito rispondono entro 2 giorni
-
feat: support OpenAI-compatible reasoning visibility and reasoning.contextForse già presa @maralbahari l’ha presa 1 giorno fa. Apertaenhancement
Difficoltà 5/5 Più di una settimana Idoneità per principianti 8/100
vllm-project/agentic-api#444 · 1 assegnatario ·
I maintainer di solito rispondono entro 2 giorni
Tutte le issue di vllm-project/agentic-api
Issue simili
-
test: sim_zero_omega_tests race on shared temp-file names, so two pairs of tests fail at randomAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
pop-os/cosmic-comp#2956 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
area:release criticality:p3 documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
registrystack/registry-stack#2085 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
libp2p/rust-libp2p#6694 ·
I maintainer di solito rispondono entro 1 giorno