A Rust web server that dynamically generates entire websites on the fly using LLMs.
Complete reference for the web_search tool available to the LLM during page generation.
Contents
The web_search tool gives the LLM the ability to query a live web search engine
during page generation. When enabled, the model can issue search queries and receive back
structured results — titles, URLs, and snippets — that it can incorporate into generated HTML.
This makes generated pages significantly more factual, current, and detailed compared to generation without tool access. For example, a page about a software project can pull in the current version number; a page about a city can include accurate population figures; a news-oriented page can surface recent headlines.
Web tools (both web_search and web_fetch)
are enabled by default. Pass --no-web-tools at startup to disable them.
See Section 11.
web_search
multiple times. See Rate Limits & Cost.
Rabbithole registers the web_search tool with the Anthropic API using the following
JSON schema. This is passed verbatim in the tools array of every Claude API request
when web tools are enabled.
{
"name": "web_search",
"description": "Search the web for current information. Returns a list of results
with titles, URLs, and text snippets. Use this to find real, up-to-date facts,
images, documentation, or any information you need to produce an accurate and
detailed page. Prefer specific, targeted queries over broad ones.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query string. Should be concise and specific.
1 to 6 words typically works best. Avoid special operators unless required."
}
},
"required": ["query"]
}
}
The tool accepts the following input fields:
| Field | Type | Required | Description |
|---|---|---|---|
query |
string | REQUIRED | The search query to execute. The model constructs this string based on what information it needs. Optimal queries are 1–6 words. Longer queries reduce relevance of returned results. The query is passed directly to the configured search provider's API. |
num_results parameter: The number of results returned is
determined by the server-side configuration (see Configuration).
The LLM cannot request more or fewer results per call.
The tool returns a JSON array of search result objects. Each object contains the following fields:
| Field | Type | Always present? | Description |
|---|---|---|---|
title |
string | Yes | The title of the web page as indexed by the search engine. |
url |
string | Yes | The canonical URL of the result. Can be passed to web_fetch for full content. |
snippet |
string | Yes | A short excerpt or description from the page, as provided by the search engine. Typically 1–4 sentences. May contain HTML entities depending on the provider. |
published_date |
string | null | No |
ISO 8601 date string indicating when the page was published or last indexed,
if the provider exposes this. null when unavailable.
|
Results are returned as a top-level JSON array. If no results are found, an empty array
[] is returned. If the search provider returns an error, the tool returns a
JSON object with an error field:
{ "error": "Search provider returned HTTP 429: rate limit exceeded" }
The LLM is instructed to handle both empty results and error responses gracefully by falling back to its training knowledge.
Rabbithole's web_search implementation is provider-agnostic at the server level.
The search backend is selected via environment variable or CLI flag. The following providers
are supported:
| Provider | Env Variable | Notes |
|---|---|---|
| Brave Search API | BRAVE_API_KEY |
Default provider when a key is present. Independent index, privacy-focused. Free tier available (2,000 queries/month). Paid plans scale from $3/1,000 queries. brave.com/search/api |
| SerpAPI | SERPAPI_KEY |
Wraps Google, Bing, and other engines. High quality results. Free tier is 100 searches/month; paid plans from $50/month for 5,000 searches. serpapi.com |
| Anthropic built-in | (none required) |
When no provider key is configured, Rabbithole falls back to the
web_search tool built into the Anthropic API (available on
Claude models that support it natively). No additional key required, but
billed as part of API token usage.
|
BRAVE_API_KEY >
SERPAPI_KEY > Anthropic built-in. If you set both Brave and SerpAPI keys,
Brave is used. To force a specific provider, unset the higher-priority keys.
The following environment variables control the behavior of web_search at runtime.
All are optional unless noted.
| Variable | Default | Description |
|---|---|---|
BRAVE_API_KEY |
unset | API key for Brave Search. Enables Brave as the search provider when set. |
SERPAPI_KEY |
unset | API key for SerpAPI. Used as fallback if Brave key is not set. |
RABBITHOLE_SEARCH_RESULTS |
5 |
Number of results to request from the search provider per query. Increasing this gives the LLM more context but uses more tokens. Recommended range: 3–10. |
RABBITHOLE_SEARCH_TIMEOUT_SECS |
10 |
HTTP timeout in seconds for search provider requests. If exceeded, the tool returns an error object and the LLM falls back to training data. |
These can also be set via a .env file in the working directory.
CLI flag --no-web-tools disables all web tools regardless of which
keys are set. See Configuration reference for full details.
The cost per search query depends on the provider:
| Provider | Free Tier | Paid Rate |
|---|---|---|
| Brave Search API | 2,000 queries/month | ~$0.003 per query (Data for Search plan) |
| SerpAPI | 100 searches/month | ~$0.01 per search (Hobby plan) |
| Anthropic built-in | N/A | Billed via Claude API tokens (varies by model) |
Each web_search call introduces additional tokens into the conversation context:
Because tool results are injected into the context window, pages generated with
web_search will consistently use more tokens than non-tool pages.
Plan accordingly when setting --max-cost.
--max-cost <dollars> to set a total
spend limit across all page generations. Once the cumulative cost (Claude API + any direct
provider costs tracked by Rabbithole) exceeds the cap, uncached pages redirect to 404
instead of generating. This is the recommended safeguard for public-facing deployments.
See Configuration.
Rabbithole does not implement its own request queue for search calls — if multiple pages
are being generated concurrently (parallel visitors), each may independently issue
web_search calls. On low-volume free tiers this can exhaust quotas quickly.
For production deployments with sustained traffic, configure a paid provider tier or
set --no-web-tools to eliminate provider rate limit risk.
The following is an example of what the Claude API sends Rabbithole when the model
decides to invoke web_search during generation of a page about the
Rust programming language:
{
"type": "tool_use",
"id": "toolu_01XzQpLm7Kn2bRfDgA4cPwEj",
"name": "web_search",
"input": {
"query": "Rust programming language latest stable version 2025"
}
}
Rabbithole intercepts this, executes the query against the configured search provider,
and returns the results to the model as a tool_result block in the next
API message:
{
"type": "tool_result",
"tool_use_id": "toolu_01XzQpLm7Kn2bRfDgA4cPwEj",
"content": "[ ... search results JSON ... ]"
}
The model then continues generating the HTML page with this information incorporated.
The following is a representative example of the JSON array returned by the tool:
[
{
"title": "Rust 1.84.0 Released - The Rust Programming Language Blog",
"url": "https://blog.rust-lang.org/2025/01/09/Rust-1.84.0.html",
"snippet": "The Rust team is happy to announce a new version of Rust, 1.84.0.
Rust is a programming language empowering everyone to build reliable and
efficient software.",
"published_date": "2025-01-09"
},
{
"title": "Rust (programming language) - Wikipedia",
"url": "https://en.wikipedia.org/wiki/Rust_(programming_language)",
"snippet": "Rust is a multi-paradigm, general-purpose programming language that
emphasizes performance, type safety, and concurrency. It enforces memory safety,
meaning that all references point to valid memory.",
"published_date": null
},
{
"title": "Install Rust - rust-lang.org",
"url": "https://www.rust-lang.org/tools/install",
"snippet": "Get started with Rust using rustup. The recommended installation
method for Rust on Linux and macOS is to use rustup, the official Rust toolchain
installer.",
"published_date": null
},
{
"title": "Releases · rust-lang/rust - GitHub",
"url": "https://github.com/rust-lang/rust/releases",
"snippet": "Release notes and changelogs for all stable, beta, and nightly
versions of the Rust compiler.",
"published_date": "2025-01-09"
},
{
"title": "The Rust Reference - rust-lang.org",
"url": "https://doc.rust-lang.org/reference/",
"snippet": "The Reference is not a formal specification of Rust's semantics but
is more detailed and comprehensive than the book.",
"published_date": null
}
]
The url field in each result can be passed directly to
web_fetch to retrieve the full page content if the model
needs more detail than the snippet provides.
When writing seed prompts or linked-page prompts for Rabbithole, you can influence how
the LLM uses web_search by being explicit about what information you want
it to look up. The following guidelines help get better results:
Rather than saying "include information about the project", say "search for the current GitHub star count and latest release version of the project." Explicit guidance prevents the model from skipping searches or issuing overly broad queries.
If you want information from a specific source (e.g., Wikipedia, official docs, a news outlet), say so: "search for X on the official Rust documentation site." The model will construct a more targeted query.
If you want real images hotlinked into the page, instruct the model to search for them:
"search for a high-quality image of X and embed it with an <img> tag."
The model can search for images and use the result URLs directly in src attributes.
For rapidly changing data (prices, versions, statistics), add: "use web_search to find the most current figure available, and note the date in the page." This pushes the model to prioritize recent search results and attribute them.
You generally do not need to write the exact query string in your prompt. The model is good at constructing search queries from high-level intent. Trust it to reformulate: "find recent benchmark comparisons" works better than prescribing the exact query string character-for-character.
For pages where factual accuracy matters most (reference docs, product specs, biographies), include: "verify all factual claims with web_search before including them." For purely creative/fictional pages, you may instruct: "do not use web_search; invent all content."
Here is an example prompt excerpt demonstrating these principles:
Generate a reference page for the SQLite database engine. Use web_search to find: - The current stable version of SQLite and its release date - The official SQLite website URL for linking - A one-paragraph description from the official SQLite about page - The current file size of the SQLite amalgamation source file Embed the SQLite logo by searching for its image URL and hotlinking it. Note the date of any statistics you include.
To disable web_search (and web_fetch) entirely, start
Rabbithole with the --no-web-tools flag:
cargo run -- --seed "My homepage" --no-web-tools
Reasons you might disable web tools:
See also: Web Tools overview for information on both
web_search and web_fetch, and how they interact.