mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix: improve Perplexity web_search defaults (#1131) (thanks @CMLKevin)
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.clawd.bot
|
||||
|
||||
### Changes
|
||||
- Tools: allow `sessions_spawn` to override thinking level for sub-agent runs.
|
||||
- Tools: add Perplexity Sonar provider for `web_search` with direct Perplexity/OpenRouter support. (#1131) — thanks @CMLKevin.
|
||||
|
||||
### Fixes
|
||||
- Memory: apply OpenAI batch defaults even without explicit remote config.
|
||||
|
||||
55
docs/perplexity.md
Normal file
55
docs/perplexity.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
summary: "Perplexity Sonar setup for web_search"
|
||||
read_when:
|
||||
- You want to use Perplexity Sonar for web search
|
||||
- You need PERPLEXITY_API_KEY or OpenRouter setup
|
||||
---
|
||||
|
||||
# Perplexity Sonar
|
||||
|
||||
Clawdbot can use Perplexity Sonar for the `web_search` tool. You can connect
|
||||
through Perplexity’s direct API or via OpenRouter.
|
||||
|
||||
## API options
|
||||
|
||||
### Perplexity (direct)
|
||||
|
||||
- Base URL: https://api.perplexity.ai
|
||||
- Environment variable: `PERPLEXITY_API_KEY`
|
||||
|
||||
### OpenRouter (alternative)
|
||||
|
||||
- Base URL: https://openrouter.ai/api/v1
|
||||
- Environment variable: `OPENROUTER_API_KEY`
|
||||
- Supports prepaid/crypto credits.
|
||||
|
||||
## Config example
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
provider: "perplexity",
|
||||
perplexity: {
|
||||
apiKey: "pplx-...",
|
||||
baseUrl: "https://api.perplexity.ai",
|
||||
model: "perplexity/sonar-pro"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If both `PERPLEXITY_API_KEY` and `OPENROUTER_API_KEY` are set, set
|
||||
`tools.web.search.perplexity.baseUrl` (or `tools.web.search.perplexity.apiKey`)
|
||||
to disambiguate.
|
||||
|
||||
## Models
|
||||
|
||||
- `perplexity/sonar` — fast Q&A with web search
|
||||
- `perplexity/sonar-pro` (default) — multi-step reasoning + web search
|
||||
- `perplexity/sonar-reasoning-pro` — deep research
|
||||
|
||||
See [Web tools](/tools/web) for the full web_search configuration.
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "Web search + fetch tools (Brave Search API, Perplexity via OpenRouter)"
|
||||
summary: "Web search + fetch tools (Brave Search API, Perplexity Sonar)"
|
||||
read_when:
|
||||
- You want to enable web_search or web_fetch
|
||||
- You need Brave Search API key setup
|
||||
@@ -10,7 +10,7 @@ read_when:
|
||||
|
||||
Clawdbot ships two lightweight web tools:
|
||||
|
||||
- `web_search` — Search the web via Brave Search API (default) or Perplexity Sonar (via OpenRouter).
|
||||
- `web_search` — Search the web via Brave Search API (default) or Perplexity Sonar (direct or via OpenRouter).
|
||||
- `web_fetch` — HTTP fetch + readable extraction (HTML → markdown/text).
|
||||
|
||||
These are **not** browser automation. For JS-heavy sites or logins, use the
|
||||
@@ -28,10 +28,12 @@ These are **not** browser automation. For JS-heavy sites or logins, use the
|
||||
|
||||
## Choosing a search provider
|
||||
|
||||
Provider docs: [Brave Search API](https://brave.com/search/api/) and [Perplexity Sonar](/perplexity).
|
||||
|
||||
| Provider | Pros | Cons | API Key |
|
||||
|----------|------|------|---------|
|
||||
| **Brave** (default) | Fast, structured results, free tier | Traditional search results | `BRAVE_API_KEY` |
|
||||
| **Perplexity** | AI-synthesized answers, citations, real-time | Requires OpenRouter credits | `OPENROUTER_API_KEY` or `PERPLEXITY_API_KEY` |
|
||||
| **Perplexity** | AI-synthesized answers, citations, real-time | Requires Perplexity or OpenRouter credits | `PERPLEXITY_API_KEY` or `OPENROUTER_API_KEY` |
|
||||
|
||||
Set the provider in config:
|
||||
|
||||
@@ -65,13 +67,22 @@ current limits and pricing.
|
||||
environment. For a daemon install, put it in `~/.clawdbot/.env` (or your
|
||||
service environment). See [Env vars](/start/faq#how-does-clawdbot-load-environment-variables).
|
||||
|
||||
## Using Perplexity (via OpenRouter)
|
||||
## Using Perplexity Sonar
|
||||
|
||||
Perplexity Sonar models have built-in web search capabilities and return AI-synthesized
|
||||
answers with citations. You can use them via OpenRouter (no credit card required - supports
|
||||
crypto/prepaid).
|
||||
answers with citations. You can use them either directly via Perplexity’s API or via OpenRouter
|
||||
(supports crypto/prepaid).
|
||||
|
||||
### Getting an OpenRouter API key
|
||||
See [Perplexity Sonar](/perplexity) for a dedicated setup guide.
|
||||
|
||||
### Getting an API key
|
||||
|
||||
**Perplexity (direct):**
|
||||
|
||||
1) Create a Perplexity account and generate an API key.
|
||||
2) Set `PERPLEXITY_API_KEY` in the Gateway environment or store the key in config.
|
||||
|
||||
**OpenRouter (alternative):**
|
||||
|
||||
1) Create an account at https://openrouter.ai/
|
||||
2) Add credits (supports crypto, prepaid, or credit card)
|
||||
@@ -88,9 +99,11 @@ crypto/prepaid).
|
||||
provider: "perplexity",
|
||||
perplexity: {
|
||||
// API key (optional if OPENROUTER_API_KEY or PERPLEXITY_API_KEY is set)
|
||||
apiKey: "sk-or-v1-...",
|
||||
// Base URL (defaults to OpenRouter)
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
apiKey: "pplx-... or sk-or-v1-...",
|
||||
// Base URL:
|
||||
// - https://api.perplexity.ai (default when PERPLEXITY_API_KEY is set)
|
||||
// - https://openrouter.ai/api/v1 (default when only OPENROUTER_API_KEY is set)
|
||||
baseUrl: "https://api.perplexity.ai",
|
||||
// Model (defaults to perplexity/sonar-pro)
|
||||
model: "perplexity/sonar-pro"
|
||||
}
|
||||
@@ -100,8 +113,9 @@ crypto/prepaid).
|
||||
}
|
||||
```
|
||||
|
||||
**Environment alternative:** set `OPENROUTER_API_KEY` or `PERPLEXITY_API_KEY` in the Gateway
|
||||
environment. For a daemon install, put it in `~/.clawdbot/.env`.
|
||||
**Environment alternative:** set `PERPLEXITY_API_KEY` or `OPENROUTER_API_KEY` in the Gateway
|
||||
environment. For a daemon install, put it in `~/.clawdbot/.env`. If you set both keys, set
|
||||
`tools.web.search.perplexity.baseUrl` (or `tools.web.search.perplexity.apiKey`) to disambiguate.
|
||||
|
||||
### Available Perplexity models
|
||||
|
||||
@@ -120,7 +134,7 @@ Search the web using your configured provider.
|
||||
- `tools.web.search.enabled` must not be `false` (default: enabled)
|
||||
- API key for your chosen provider:
|
||||
- **Brave**: `BRAVE_API_KEY` or `tools.web.search.apiKey`
|
||||
- **Perplexity**: `OPENROUTER_API_KEY`, `PERPLEXITY_API_KEY`, or `tools.web.search.perplexity.apiKey`
|
||||
- **Perplexity**: `PERPLEXITY_API_KEY` (direct), `OPENROUTER_API_KEY` (OpenRouter), or `tools.web.search.perplexity.apiKey`
|
||||
|
||||
### Config
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
ls: "List directory contents",
|
||||
exec: "Run shell commands (pty available for TTY-required CLIs)",
|
||||
process: "Manage background exec sessions",
|
||||
web_search: "Search the web (Brave API)",
|
||||
web_search: "Search the web (Brave or Perplexity)",
|
||||
web_fetch: "Fetch and extract readable content from a URL",
|
||||
// Channel docking: add login tools here when a channel needs interactive linking.
|
||||
browser: "Control web browser",
|
||||
|
||||
@@ -89,3 +89,102 @@ describe("web_search country and language parameters", () => {
|
||||
expect(url.searchParams.get("ui_lang")).toBe("de");
|
||||
});
|
||||
});
|
||||
|
||||
describe("web_search Perplexity provider configuration", () => {
|
||||
const priorFetch = global.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubEnv("PERPLEXITY_API_KEY", "pplx-test");
|
||||
vi.stubEnv("OPENROUTER_API_KEY", "sk-or-test");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
// @ts-expect-error global fetch cleanup
|
||||
global.fetch = priorFetch;
|
||||
});
|
||||
|
||||
it("defaults to Perplexity base URL when PERPLEXITY_API_KEY is set", async () => {
|
||||
const mockFetch = vi.fn((_input: RequestInfo, _init?: RequestInit) =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
choices: [{ message: { content: "ok" } }],
|
||||
citations: [],
|
||||
}),
|
||||
} as Response),
|
||||
);
|
||||
// @ts-expect-error mock fetch
|
||||
global.fetch = mockFetch;
|
||||
|
||||
const tool = createWebSearchTool({
|
||||
config: { tools: { web: { search: { provider: "perplexity" } } } },
|
||||
sandboxed: true,
|
||||
});
|
||||
|
||||
await tool?.execute?.(1, { query: "test" });
|
||||
|
||||
expect(mockFetch).toHaveBeenCalled();
|
||||
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit?];
|
||||
expect(url).toBe("https://api.perplexity.ai/chat/completions");
|
||||
const headers = (init?.headers ?? {}) as Record<string, string>;
|
||||
expect(headers.Authorization).toBe("Bearer pplx-test");
|
||||
});
|
||||
|
||||
it("uses OpenRouter base URL when configured, even with PERPLEXITY_API_KEY set", async () => {
|
||||
const mockFetch = vi.fn((_input: RequestInfo, _init?: RequestInit) =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
choices: [{ message: { content: "ok" } }],
|
||||
citations: [],
|
||||
}),
|
||||
} as Response),
|
||||
);
|
||||
// @ts-expect-error mock fetch
|
||||
global.fetch = mockFetch;
|
||||
|
||||
const tool = createWebSearchTool({
|
||||
config: {
|
||||
tools: {
|
||||
web: {
|
||||
search: { provider: "perplexity", perplexity: { baseUrl: "https://openrouter.ai/api/v1" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
sandboxed: true,
|
||||
});
|
||||
|
||||
await tool?.execute?.(1, { query: "test" });
|
||||
|
||||
expect(mockFetch).toHaveBeenCalled();
|
||||
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit?];
|
||||
expect(url).toBe("https://openrouter.ai/api/v1/chat/completions");
|
||||
const headers = (init?.headers ?? {}) as Record<string, string>;
|
||||
expect(headers.Authorization).toBe("Bearer sk-or-test");
|
||||
});
|
||||
|
||||
it("returns missing key when OpenRouter base URL is configured without OPENROUTER_API_KEY", async () => {
|
||||
vi.stubEnv("OPENROUTER_API_KEY", "");
|
||||
const mockFetch = vi.fn();
|
||||
// @ts-expect-error mock fetch
|
||||
global.fetch = mockFetch;
|
||||
|
||||
const tool = createWebSearchTool({
|
||||
config: {
|
||||
tools: {
|
||||
web: {
|
||||
search: { provider: "perplexity", perplexity: { baseUrl: "https://openrouter.ai/api/v1" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
sandboxed: true,
|
||||
});
|
||||
|
||||
const result = await tool?.execute?.(1, { query: "test" });
|
||||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
expect(result?.details).toMatchObject({ error: "missing_openrouter_api_key" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,8 @@ const DEFAULT_FETCH_USER_AGENT =
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
|
||||
|
||||
const BRAVE_SEARCH_ENDPOINT = "https://api.search.brave.com/res/v1/web/search";
|
||||
const DEFAULT_PERPLEXITY_BASE_URL = "https://openrouter.ai/api/v1";
|
||||
const DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
||||
const DEFAULT_PERPLEXITY_BASE_URL = "https://api.perplexity.ai";
|
||||
const DEFAULT_PERPLEXITY_MODEL = "perplexity/sonar-pro";
|
||||
|
||||
type WebSearchConfig = NonNullable<ClawdbotConfig["tools"]>["web"] extends infer Web
|
||||
@@ -198,8 +199,27 @@ function resolveFirecrawlMaxAgeMsOrDefault(firecrawl?: FirecrawlFetchConfig): nu
|
||||
return DEFAULT_FIRECRAWL_MAX_AGE_MS;
|
||||
}
|
||||
|
||||
function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) {
|
||||
function missingSearchKeyPayload(
|
||||
provider: (typeof SEARCH_PROVIDERS)[number],
|
||||
perplexityBaseUrl?: string,
|
||||
) {
|
||||
if (provider === "perplexity") {
|
||||
if (perplexityBaseUrl && isOpenRouterBaseUrl(perplexityBaseUrl)) {
|
||||
return {
|
||||
error: "missing_openrouter_api_key",
|
||||
message:
|
||||
"web_search (perplexity via OpenRouter) needs an API key. Set OPENROUTER_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey.",
|
||||
docs: "https://docs.clawd.bot/tools/web",
|
||||
};
|
||||
}
|
||||
if (perplexityBaseUrl && isPerplexityBaseUrl(perplexityBaseUrl)) {
|
||||
return {
|
||||
error: "missing_perplexity_api_key",
|
||||
message:
|
||||
"web_search (perplexity direct) needs an API key. Set PERPLEXITY_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey.",
|
||||
docs: "https://docs.clawd.bot/tools/web",
|
||||
};
|
||||
}
|
||||
return {
|
||||
error: "missing_perplexity_api_key",
|
||||
message:
|
||||
@@ -231,6 +251,32 @@ type PerplexityConfig = {
|
||||
model?: string;
|
||||
};
|
||||
|
||||
function normalizeBaseUrl(value: string): string {
|
||||
return value.trim().replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function isPerplexityBaseUrl(value: string): boolean {
|
||||
try {
|
||||
return new URL(value).hostname === "api.perplexity.ai";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isOpenRouterBaseUrl(value: string): boolean {
|
||||
try {
|
||||
return new URL(value).hostname === "openrouter.ai";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function inferPerplexityBaseUrlFromKey(apiKey: string): string | undefined {
|
||||
if (apiKey.startsWith("pplx-")) return DEFAULT_PERPLEXITY_BASE_URL;
|
||||
if (apiKey.startsWith("sk-or-")) return DEFAULT_OPENROUTER_BASE_URL;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolvePerplexityConfig(search?: WebSearchConfig): PerplexityConfig {
|
||||
if (!search || typeof search !== "object") return {};
|
||||
const perplexity = "perplexity" in search ? search.perplexity : undefined;
|
||||
@@ -238,14 +284,24 @@ function resolvePerplexityConfig(search?: WebSearchConfig): PerplexityConfig {
|
||||
return perplexity as PerplexityConfig;
|
||||
}
|
||||
|
||||
function resolvePerplexityApiKey(perplexity?: PerplexityConfig): string | undefined {
|
||||
function resolvePerplexityApiKey(
|
||||
perplexity: PerplexityConfig | undefined,
|
||||
baseUrl: string,
|
||||
): string | undefined {
|
||||
const fromConfig =
|
||||
perplexity && "apiKey" in perplexity && typeof perplexity.apiKey === "string"
|
||||
? perplexity.apiKey.trim()
|
||||
: "";
|
||||
if (fromConfig) return fromConfig;
|
||||
const fromEnvPerplexity = (process.env.PERPLEXITY_API_KEY ?? "").trim();
|
||||
const fromEnvOpenRouter = (process.env.OPENROUTER_API_KEY ?? "").trim();
|
||||
return fromConfig || fromEnvPerplexity || fromEnvOpenRouter || undefined;
|
||||
if (isPerplexityBaseUrl(baseUrl)) {
|
||||
return fromEnvPerplexity || undefined;
|
||||
}
|
||||
if (isOpenRouterBaseUrl(baseUrl)) {
|
||||
return fromEnvOpenRouter || undefined;
|
||||
}
|
||||
return fromEnvPerplexity || fromEnvOpenRouter || undefined;
|
||||
}
|
||||
|
||||
function resolvePerplexityBaseUrl(perplexity?: PerplexityConfig): string {
|
||||
@@ -253,7 +309,20 @@ function resolvePerplexityBaseUrl(perplexity?: PerplexityConfig): string {
|
||||
perplexity && "baseUrl" in perplexity && typeof perplexity.baseUrl === "string"
|
||||
? perplexity.baseUrl.trim()
|
||||
: "";
|
||||
return fromConfig || DEFAULT_PERPLEXITY_BASE_URL;
|
||||
if (fromConfig) return normalizeBaseUrl(fromConfig);
|
||||
const fromConfigKey =
|
||||
perplexity && "apiKey" in perplexity && typeof perplexity.apiKey === "string"
|
||||
? perplexity.apiKey.trim()
|
||||
: "";
|
||||
const inferredFromConfigKey = fromConfigKey
|
||||
? inferPerplexityBaseUrlFromKey(fromConfigKey)
|
||||
: undefined;
|
||||
if (inferredFromConfigKey) return inferredFromConfigKey;
|
||||
const fromEnvPerplexity = (process.env.PERPLEXITY_API_KEY ?? "").trim();
|
||||
const fromEnvOpenRouter = (process.env.OPENROUTER_API_KEY ?? "").trim();
|
||||
if (fromEnvPerplexity) return DEFAULT_PERPLEXITY_BASE_URL;
|
||||
if (fromEnvOpenRouter) return DEFAULT_OPENROUTER_BASE_URL;
|
||||
return DEFAULT_OPENROUTER_BASE_URL;
|
||||
}
|
||||
|
||||
function resolvePerplexityModel(perplexity?: PerplexityConfig): string {
|
||||
@@ -545,6 +614,13 @@ type PerplexitySearchResponse = {
|
||||
citations?: string[];
|
||||
};
|
||||
|
||||
function resolvePerplexityEndpoint(baseUrl: string): string {
|
||||
const trimmed = normalizeBaseUrl(baseUrl);
|
||||
if (!trimmed) return `${DEFAULT_OPENROUTER_BASE_URL}/chat/completions`;
|
||||
if (trimmed.endsWith("/chat/completions")) return trimmed;
|
||||
return `${trimmed}/chat/completions`;
|
||||
}
|
||||
|
||||
async function runPerplexitySearch(params: {
|
||||
query: string;
|
||||
apiKey: string;
|
||||
@@ -552,7 +628,7 @@ async function runPerplexitySearch(params: {
|
||||
model: string;
|
||||
timeoutSeconds: number;
|
||||
}): Promise<{ content: string; citations: string[] }> {
|
||||
const endpoint = `${params.baseUrl.replace(/\/$/, "")}/chat/completions`;
|
||||
const endpoint = resolvePerplexityEndpoint(params.baseUrl);
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
@@ -599,8 +675,12 @@ async function runWebSearch(params: {
|
||||
perplexityBaseUrl?: string;
|
||||
perplexityModel?: string;
|
||||
}): Promise<Record<string, unknown>> {
|
||||
const providerKey =
|
||||
params.provider === "perplexity"
|
||||
? `${params.provider}:${params.perplexityModel ?? DEFAULT_PERPLEXITY_MODEL}:${params.perplexityBaseUrl ?? DEFAULT_OPENROUTER_BASE_URL}`
|
||||
: params.provider;
|
||||
const cacheKey = normalizeCacheKey(
|
||||
`${params.provider}:${params.query}:${params.count}:${params.country || "default"}:${params.search_lang || "default"}:${params.ui_lang || "default"}`,
|
||||
`${providerKey}:${params.query}:${params.count}:${params.country || "default"}:${params.search_lang || "default"}:${params.ui_lang || "default"}`,
|
||||
);
|
||||
const cached = readCache(SEARCH_CACHE, cacheKey);
|
||||
if (cached) return { ...cached.value, cached: true };
|
||||
@@ -899,11 +979,13 @@ export function createWebSearchTool(options?: {
|
||||
|
||||
const provider = resolveSearchProvider(search);
|
||||
const perplexityConfig = resolvePerplexityConfig(search);
|
||||
const perplexityBaseUrl = resolvePerplexityBaseUrl(perplexityConfig);
|
||||
const perplexityModel = resolvePerplexityModel(perplexityConfig);
|
||||
|
||||
// Determine description based on provider
|
||||
const description =
|
||||
provider === "perplexity"
|
||||
? "Search the web using Perplexity Sonar (via OpenRouter). Returns AI-synthesized answers with citations from real-time web search."
|
||||
? "Search the web using Perplexity Sonar (direct API or via OpenRouter). Returns AI-synthesized answers with citations from real-time web search."
|
||||
: "Search the web using Brave Search API. Supports region-specific and localized search via country and language parameters. Returns titles, URLs, and snippets for fast research.";
|
||||
|
||||
return {
|
||||
@@ -915,11 +997,11 @@ export function createWebSearchTool(options?: {
|
||||
// Resolve API key based on provider
|
||||
const apiKey =
|
||||
provider === "perplexity"
|
||||
? resolvePerplexityApiKey(perplexityConfig)
|
||||
? resolvePerplexityApiKey(perplexityConfig, perplexityBaseUrl)
|
||||
: resolveSearchApiKey(search);
|
||||
|
||||
if (!apiKey) {
|
||||
return jsonResult(missingSearchKeyPayload(provider));
|
||||
return jsonResult(missingSearchKeyPayload(provider, perplexityBaseUrl));
|
||||
}
|
||||
const params = args as Record<string, unknown>;
|
||||
const query = readStringParam(params, "query", { required: true });
|
||||
@@ -938,8 +1020,8 @@ export function createWebSearchTool(options?: {
|
||||
country,
|
||||
search_lang,
|
||||
ui_lang,
|
||||
perplexityBaseUrl: resolvePerplexityBaseUrl(perplexityConfig),
|
||||
perplexityModel: resolvePerplexityModel(perplexityConfig),
|
||||
perplexityBaseUrl,
|
||||
perplexityModel,
|
||||
});
|
||||
return jsonResult(result);
|
||||
},
|
||||
|
||||
@@ -97,12 +97,15 @@ async function promptWebToolsConfig(
|
||||
): Promise<ClawdbotConfig> {
|
||||
const existingSearch = nextConfig.tools?.web?.search;
|
||||
const existingFetch = nextConfig.tools?.web?.fetch;
|
||||
const hasSearchKey = Boolean(existingSearch?.apiKey);
|
||||
const existingProvider = existingSearch?.provider === "perplexity" ? "perplexity" : "brave";
|
||||
const hasBraveKey = Boolean(existingSearch?.apiKey);
|
||||
const hasPerplexityKey = Boolean(existingSearch?.perplexity?.apiKey);
|
||||
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 Brave (structured results) or Perplexity Sonar (answers + citations).",
|
||||
"Docs: https://docs.clawd.bot/tools/web",
|
||||
].join("\n"),
|
||||
"Web search",
|
||||
@@ -110,7 +113,7 @@ async function promptWebToolsConfig(
|
||||
|
||||
const enableSearch = guardCancel(
|
||||
await confirm({
|
||||
message: "Enable web_search (Brave Search)?",
|
||||
message: "Enable web_search?",
|
||||
initialValue: existingSearch?.enabled ?? hasSearchKey,
|
||||
}),
|
||||
runtime,
|
||||
@@ -122,27 +125,84 @@ 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 provider = guardCancel(
|
||||
await select<"brave" | "perplexity">({
|
||||
message: "Web search provider",
|
||||
options: [
|
||||
{ value: "brave", label: "Brave Search (structured results)" },
|
||||
{ value: "perplexity", label: "Perplexity Sonar (answers + citations)" },
|
||||
],
|
||||
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.clawd.bot/tools/web",
|
||||
].join("\n"),
|
||||
"Web search",
|
||||
|
||||
nextSearch = { ...nextSearch, provider };
|
||||
|
||||
if (provider === "brave") {
|
||||
const keyInput = guardCancel(
|
||||
await text({
|
||||
message: hasBraveKey
|
||||
? "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: hasBraveKey ? "Leave blank to keep current" : "BSA...",
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
const key = String(keyInput ?? "").trim();
|
||||
if (key) {
|
||||
nextSearch = { ...nextSearch, apiKey: key };
|
||||
} else if (!hasBraveKey) {
|
||||
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.clawd.bot/tools/web",
|
||||
].join("\n"),
|
||||
"Web search",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const existingPerplexity = existingSearch?.perplexity ?? {};
|
||||
const keyInput = guardCancel(
|
||||
await text({
|
||||
message: hasPerplexityKey
|
||||
? "Perplexity/OpenRouter API key (leave blank to keep current or use PERPLEXITY_API_KEY/OPENROUTER_API_KEY)"
|
||||
: "Perplexity/OpenRouter API key (paste it here; leave blank to use PERPLEXITY_API_KEY/OPENROUTER_API_KEY)",
|
||||
placeholder: hasPerplexityKey ? "Leave blank to keep current" : "pplx-... or sk-or-...",
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
const key = String(keyInput ?? "").trim();
|
||||
let nextPerplexity = { ...existingPerplexity };
|
||||
if (key) {
|
||||
nextPerplexity = { ...nextPerplexity, apiKey: key };
|
||||
}
|
||||
|
||||
const baseUrlInput = guardCancel(
|
||||
await text({
|
||||
message: "Perplexity base URL (optional; leave blank for default)",
|
||||
placeholder: existingPerplexity.baseUrl ?? "https://api.perplexity.ai",
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
const baseUrl = String(baseUrlInput ?? "").trim();
|
||||
if (baseUrl) {
|
||||
nextPerplexity = { ...nextPerplexity, baseUrl };
|
||||
}
|
||||
|
||||
nextSearch = { ...nextSearch, perplexity: nextPerplexity };
|
||||
|
||||
if (!key && !hasPerplexityKey) {
|
||||
note(
|
||||
[
|
||||
"No key stored yet, so web_search will stay unavailable.",
|
||||
"Store a key here or set PERPLEXITY_API_KEY/OPENROUTER_API_KEY in the Gateway environment.",
|
||||
"Docs: https://docs.clawd.bot/tools/web",
|
||||
].join("\n"),
|
||||
"Web search",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export async function runNonInteractiveOnboardingLocal(params: {
|
||||
|
||||
if (!opts.json) {
|
||||
runtime.log(
|
||||
"Tip: run `clawdbot configure --section web` to store your Brave API key for web_search. Docs: https://docs.clawd.bot/tools/web",
|
||||
"Tip: run `clawdbot configure --section web` to choose Brave or Perplexity and store your API key for web_search. Docs: https://docs.clawd.bot/tools/web",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export async function runNonInteractiveOnboardingRemote(params: {
|
||||
runtime.log(`Remote gateway: ${remoteUrl}`);
|
||||
runtime.log(`Auth: ${payload.auth}`);
|
||||
runtime.log(
|
||||
"Tip: run `clawdbot configure --section web` to store your Brave API key for web_search. Docs: https://docs.clawd.bot/tools/web",
|
||||
"Tip: run `clawdbot configure --section web` to choose Brave or Perplexity and store your API key for web_search. Docs: https://docs.clawd.bot/tools/web",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,9 +315,15 @@ const FIELD_HELP: Record<string, string> = {
|
||||
"tools.message.crossContext.marker.suffix":
|
||||
'Text suffix for cross-context markers (supports "{channel}").',
|
||||
"tools.message.broadcast.enabled": "Enable broadcast action (default: true).",
|
||||
"tools.web.search.enabled": "Enable the web_search tool (requires Brave API key).",
|
||||
"tools.web.search.provider": 'Search provider (only "brave" supported today).',
|
||||
"tools.web.search.enabled": "Enable the web_search tool (requires provider API key).",
|
||||
"tools.web.search.provider": 'Search provider ("brave" or "perplexity").',
|
||||
"tools.web.search.apiKey": "Brave Search API key (fallback: BRAVE_API_KEY env var).",
|
||||
"tools.web.search.perplexity.apiKey":
|
||||
"Perplexity/OpenRouter API key (fallback: PERPLEXITY_API_KEY or OPENROUTER_API_KEY env vars).",
|
||||
"tools.web.search.perplexity.baseUrl":
|
||||
"Perplexity/OpenRouter base URL for web_search (defaults to Perplexity when PERPLEXITY_API_KEY is set, otherwise OpenRouter).",
|
||||
"tools.web.search.perplexity.model":
|
||||
'Perplexity Sonar model to use for web_search (default: "perplexity/sonar-pro").',
|
||||
"tools.web.search.maxResults": "Default number of results to return (1-10).",
|
||||
"tools.web.search.timeoutSeconds": "Timeout in seconds for web_search requests.",
|
||||
"tools.web.search.cacheTtlMinutes": "Cache TTL in minutes for web_search results.",
|
||||
|
||||
@@ -236,7 +236,7 @@ export type ToolsConfig = {
|
||||
perplexity?: {
|
||||
/** API key for Perplexity or OpenRouter (defaults to PERPLEXITY_API_KEY or OPENROUTER_API_KEY env var). */
|
||||
apiKey?: string;
|
||||
/** Base URL for API requests (defaults to OpenRouter: https://openrouter.ai/api/v1). */
|
||||
/** Base URL for API requests (defaults to Perplexity if PERPLEXITY_API_KEY is set, otherwise OpenRouter). */
|
||||
baseUrl?: string;
|
||||
/** Model to use (defaults to "perplexity/sonar-pro"). */
|
||||
model?: string;
|
||||
|
||||
@@ -375,29 +375,52 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
|
||||
);
|
||||
}
|
||||
|
||||
const webSearchKey = (nextConfig.tools?.web?.search?.apiKey ?? "").trim();
|
||||
const webSearchEnv = (process.env.BRAVE_API_KEY ?? "").trim();
|
||||
const hasWebSearchKey = Boolean(webSearchKey || webSearchEnv);
|
||||
const webSearchProvider =
|
||||
nextConfig.tools?.web?.search?.provider === "perplexity" ? "perplexity" : "brave";
|
||||
const braveKey = (nextConfig.tools?.web?.search?.apiKey ?? "").trim();
|
||||
const braveEnv = (process.env.BRAVE_API_KEY ?? "").trim();
|
||||
const perplexityKey = (nextConfig.tools?.web?.search?.perplexity?.apiKey ?? "").trim();
|
||||
const perplexityEnv = (process.env.PERPLEXITY_API_KEY ?? "").trim();
|
||||
const openRouterEnv = (process.env.OPENROUTER_API_KEY ?? "").trim();
|
||||
const perplexityBaseUrl = (nextConfig.tools?.web?.search?.perplexity?.baseUrl ?? "").trim();
|
||||
const hasWebSearchKey =
|
||||
webSearchProvider === "perplexity"
|
||||
? Boolean(perplexityKey || perplexityEnv || openRouterEnv)
|
||||
: Boolean(braveKey || braveEnv);
|
||||
await prompter.note(
|
||||
hasWebSearchKey
|
||||
? [
|
||||
"Web search is enabled, so your agent can look things up online when needed.",
|
||||
"",
|
||||
webSearchKey
|
||||
? "API key: stored in config (tools.web.search.apiKey)."
|
||||
: "API key: provided via BRAVE_API_KEY env var (Gateway environment).",
|
||||
webSearchProvider === "perplexity"
|
||||
? perplexityKey
|
||||
? "API key: stored in config (tools.web.search.perplexity.apiKey)."
|
||||
: perplexityBaseUrl.includes("openrouter.ai")
|
||||
? "API key: provided via OPENROUTER_API_KEY env var (Gateway environment)."
|
||||
: perplexityBaseUrl.includes("api.perplexity.ai")
|
||||
? "API key: provided via PERPLEXITY_API_KEY env var (Gateway environment)."
|
||||
: perplexityEnv
|
||||
? "API key: provided via PERPLEXITY_API_KEY env var (Gateway environment)."
|
||||
: "API key: provided via OPENROUTER_API_KEY env var (Gateway environment)."
|
||||
: braveKey
|
||||
? "API key: stored in config (tools.web.search.apiKey)."
|
||||
: "API key: provided via BRAVE_API_KEY env var (Gateway environment).",
|
||||
"Docs: https://docs.clawd.bot/tools/web",
|
||||
].join("\n")
|
||||
: [
|
||||
"If you want your agent to be able to search the web, you’ll need an API key.",
|
||||
"",
|
||||
"Clawdbot uses Brave Search for the `web_search` tool. Without a Brave Search API key, web search won’t work.",
|
||||
webSearchProvider === "perplexity"
|
||||
? "Clawdbot can use Perplexity Sonar for the `web_search` tool. Without a Perplexity/OpenRouter API key, web search won’t work."
|
||||
: "Clawdbot uses Brave Search for the `web_search` tool. Without a Brave Search API key, web search won’t work.",
|
||||
"",
|
||||
"Set it up interactively:",
|
||||
"- Run: clawdbot configure --section web",
|
||||
"- Enable web_search and paste your Brave Search API key",
|
||||
"- Enable web_search and paste your API key",
|
||||
"",
|
||||
"Alternative: set BRAVE_API_KEY in the Gateway environment (no config changes).",
|
||||
webSearchProvider === "perplexity"
|
||||
? "Alternative: set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment (no config changes)."
|
||||
: "Alternative: set BRAVE_API_KEY in the Gateway environment (no config changes).",
|
||||
"Docs: https://docs.clawd.bot/tools/web",
|
||||
].join("\n"),
|
||||
"Web search (optional)",
|
||||
|
||||
Reference in New Issue
Block a user