feat(web-search): switch Perplexity to native Search API (#33822)

* feat: Add Perplexity Search API as web_search provider

* docs fixes

* domain_filter validation

* address comments

* provider-specific options in cache key

* add validation for unsupported date filters

* legacy fields

* unsupported_language guard

* cache key matches the request's precedence order

* conflicting_time_filters guard

* unsupported_country guard

* invalid_date_range guard

* pplx validate for ISO 639-1 format

* docs: add Perplexity Search API changelog entry

* unsupported_domain_filter guard

---------

Co-authored-by: Shadow <hi@shadowing.dev>
This commit is contained in:
Kesku
2026-03-04 04:57:19 +00:00
committed by GitHub
parent d5a7a32826
commit 230fea1ca6
14 changed files with 874 additions and 643 deletions

View File

@@ -52,7 +52,7 @@ export const CONFIGURE_SECTION_OPTIONS: Array<{
}> = [
{ value: "workspace", label: "Workspace", hint: "Set workspace + sessions" },
{ value: "model", label: "Model", hint: "Pick provider + credentials" },
{ value: "web", label: "Web tools", hint: "Configure Brave search + fetch" },
{ value: "web", label: "Web tools", hint: "Configure web search (Perplexity/Brave) + fetch" },
{ value: "gateway", label: "Gateway", hint: "Port, bind, auth, tailscale" },
{
value: "daemon",

View File

@@ -137,12 +137,18 @@ async function promptWebToolsConfig(
): Promise<OpenClawConfig> {
const existingSearch = nextConfig.tools?.web?.search;
const existingFetch = nextConfig.tools?.web?.fetch;
const hasSearchKey = Boolean(existingSearch?.apiKey);
const existingProvider = existingSearch?.provider ?? "brave";
const hasPerplexityKey = Boolean(
existingSearch?.perplexity?.apiKey || process.env.PERPLEXITY_API_KEY,
);
const hasBraveKey = Boolean(existingSearch?.apiKey || process.env.BRAVE_API_KEY);
const hasSearchKey = existingProvider === "perplexity" ? hasPerplexityKey : hasBraveKey;
note(
[
"Web search lets your agent look things up online using the `web_search` tool.",
"It requires a Brave Search API key (you can store it in the config or set BRAVE_API_KEY in the Gateway environment).",
"Choose a provider: Perplexity Search (recommended) or Brave Search.",
"Both return structured results (title, URL, snippet) for fast research.",
"Docs: https://docs.openclaw.ai/tools/web",
].join("\n"),
"Web search",
@@ -150,7 +156,7 @@ async function promptWebToolsConfig(
const enableSearch = guardCancel(
await confirm({
message: "Enable web_search (Brave Search)?",
message: "Enable web_search?",
initialValue: existingSearch?.enabled ?? hasSearchKey,
}),
runtime,
@@ -162,27 +168,79 @@ async function promptWebToolsConfig(
};
if (enableSearch) {
const keyInput = guardCancel(
await text({
message: hasSearchKey
? "Brave Search API key (leave blank to keep current or use BRAVE_API_KEY)"
: "Brave Search API key (paste it here; leave blank to use BRAVE_API_KEY)",
placeholder: hasSearchKey ? "Leave blank to keep current" : "BSA...",
const providerChoice = guardCancel(
await select({
message: "Choose web search provider",
options: [
{
value: "perplexity",
label: "Perplexity Search",
},
{
value: "brave",
label: "Brave Search",
},
],
initialValue: existingProvider,
}),
runtime,
);
const key = String(keyInput ?? "").trim();
if (key) {
nextSearch = { ...nextSearch, apiKey: key };
} else if (!hasSearchKey) {
note(
[
"No key stored yet, so web_search will stay unavailable.",
"Store a key here or set BRAVE_API_KEY in the Gateway environment.",
"Docs: https://docs.openclaw.ai/tools/web",
].join("\n"),
"Web search",
nextSearch = { ...nextSearch, provider: providerChoice };
if (providerChoice === "perplexity") {
const hasKey = Boolean(existingSearch?.perplexity?.apiKey);
const keyInput = guardCancel(
await text({
message: hasKey
? "Perplexity API key (leave blank to keep current or use PERPLEXITY_API_KEY)"
: "Perplexity API key (paste it here; leave blank to use PERPLEXITY_API_KEY)",
placeholder: hasKey ? "Leave blank to keep current" : "pplx-...",
}),
runtime,
);
const key = String(keyInput ?? "").trim();
if (key) {
nextSearch = {
...nextSearch,
perplexity: { ...existingSearch?.perplexity, apiKey: key },
};
} else if (!hasKey && !process.env.PERPLEXITY_API_KEY) {
note(
[
"No key stored yet, so web_search will stay unavailable.",
"Store a key here or set PERPLEXITY_API_KEY in the Gateway environment.",
"Get your API key at: https://www.perplexity.ai/settings/api",
"Docs: https://docs.openclaw.ai/tools/web",
].join("\n"),
"Web search",
);
}
} else {
const hasKey = Boolean(existingSearch?.apiKey);
const keyInput = guardCancel(
await text({
message: hasKey
? "Brave Search API key (leave blank to keep current or use BRAVE_API_KEY)"
: "Brave Search API key (paste it here; leave blank to use BRAVE_API_KEY)",
placeholder: hasKey ? "Leave blank to keep current" : "BSA...",
}),
runtime,
);
const key = String(keyInput ?? "").trim();
if (key) {
nextSearch = { ...nextSearch, apiKey: key };
} else if (!hasKey && !process.env.BRAVE_API_KEY) {
note(
[
"No key stored yet, so web_search will stay unavailable.",
"Store a key here or set BRAVE_API_KEY in the Gateway environment.",
"Get your API key at: https://brave.com/search/api/",
"Docs: https://docs.openclaw.ai/tools/web",
].join("\n"),
"Web search",
);
}
}
}