Skip to content

Web search

web_search is a built-in server tool: Routeplane runs the search itself, inside the tool-calling loop, and feeds the results back to the model. So any model routed through Routeplane gains a web search — even one with no native search of its own — and the engine behind it is one you bring a key for.

It’s the same family as Advisor, SubAgent, and Fusion: off by default, enabled in config, and advertised to the model only on requests that declare it.

A caller turns it on for a request by declaring it in the tools array:

{
"tools": [
{ "type": "routeplane:web_search", "args": { "backend": "exa", "max_results": 3 } }
]
}

args is optional — drop it to use the default backend and cap. From there the model calls web_search with a query; Routeplane runs it against a backend and returns a stable result shape:

{
"backend": "exa",
"answer": "",
"results": [
{ "url": "", "title": "", "snippet": "", "content": "", "published": "", "score": 0.9 }
]
}

Every per-result field except url is optional — no engine fills them all, so the contract is additive. answer is present only for the answer-engine native backend; the REST engines (Parallel, Exa, Firecrawl, Tavily) return results and no answer. The loop bounds (max_iterations, tool_timeout, …) and approval policy from server tools apply unchanged.

Declare the backends under server_tools.web_search. The list is a preference and failover order — the first backend whose key resolves is the default, and a failing backend falls over to the next:

routeplane.yaml
server_tools:
web_search:
max_results: 5 # optional default cap; a caller may lower it per request
backends: # preference + failover order
- kind: parallel # HTTP · key from api_key or PARALLEL_API_KEY
- kind: exa # HTTP · key from api_key or EXA_API_KEY
- kind: firecrawl # HTTP · key from api_key or FIRECRAWL_API_KEY
- kind: tavily # HTTP · key from api_key or TAVILY_API_KEY
- kind: native # reuse a provider's native search for every model
name: native # backend id a caller pins with `backend`
model: anthropic/claude-opus-4.8
tool: { type: "anthropic:web_search_20250305" }
**No key, no backend.** Each HTTP backend resolves its key from an explicit `api_key` (which supports `${VAR}`) or the conventional `*_API_KEY` env var; a backend with no resolvable key is silently skipped. If *nothing* resolves, Routeplane logs that `web_search` was configured but no backend came up — usually a missing key.
kind Type Key Returns
parallel HTTP (BYOK) PARALLEL_API_KEY results[]
exa HTTP (BYOK) EXA_API_KEY results[]
firecrawl HTTP (BYOK) FIRECRAWL_API_KEY results[]
tavily HTTP (BYOK) TAVILY_API_KEY results[]
native Nested completion a routable model + its native search tool answer
  • HTTP backends (parallel / exa / firecrawl / tavily) call the engine’s REST API directly. Each takes an optional api_key and api_base override; responses are parsed defensively, so a provider renaming a field degrades to a missing value rather than a hard error.
  • The native backend runs a nested model completion, so it needs a routable model behind a provider key. It forwards a provider’s own search tool (e.g. Anthropic’s web_search_20250305), making one provider’s native web search usable from any model — including those that don’t have one.

A caller overrides the default by naming a backend in the declaration’s args.backend (matched against each backend’s kind, or the name you set on native), and can lower the result cap with args.max_results:

{ "type": "routeplane:web_search", "args": { "backend": "tavily", "max_results": 8 } }
  • Server tools — the loop that runs web_search, plus Advisor, SubAgent, and Fusion.
  • Toolsets — how tools advertised on a request are bundled and namespaced.
  • OpenTelemetry — every nested search call shows up in your traces like any other.