From b4a76884e360dbfafbd65219cdc863d85c3a1a0f Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 26 Jan 2026 15:52:01 +0800 Subject: [PATCH] Add Querit search api support. --- docs/querit.md | 42 ++++++++++ docs/tools/index.md | 4 +- docs/tools/web.md | 40 ++++++++- src/agents/tools/web-search.ts | 107 ++++++++++++++++++++++++- src/config/schema.ts | 3 +- src/config/types.tools.ts | 9 ++- src/config/zod-schema.agent-runtime.ts | 10 ++- 7 files changed, 202 insertions(+), 13 deletions(-) create mode 100644 docs/querit.md diff --git a/docs/querit.md b/docs/querit.md new file mode 100644 index 000000000..4754eec2a --- /dev/null +++ b/docs/querit.md @@ -0,0 +1,42 @@ +--- +summary: "Querit API setup for web_search" +read_when: + - You want to use Querit for web_search + - You need a QUERIT_API_KEY or plan details +--- + +# Querit + +Clawdbot supports Querit as an alternative provider for `web_search`. + +## Get an API key + +1) Create a Querit account at https://querit.ai/ +2) Generate an API key in your account dashboard. +3) Store the key in config (recommended) or set `QUERIT_API_KEY` in the Gateway environment. + +## Config example + +```json5 +{ + tools: { + web: { + search: { + provider: "querit", + querit: { + apiKey: "QUERIT_API_KEY_HERE" + }, + maxResults: 5, + timeoutSeconds: 30 + } + } + } +} +``` + +## Notes + +- Querit provides fast web search with structured results (title, URL, snippet). +- Check the Querit API portal for current limits and pricing. + +See [Web tools](/tools/web) for full web_search configuration. diff --git a/docs/tools/index.md b/docs/tools/index.md index 7c2274fb4..c34bb010b 100644 --- a/docs/tools/index.md +++ b/docs/tools/index.md @@ -205,14 +205,14 @@ Notes: - `process` is scoped per agent; sessions from other agents are not visible. ### `web_search` -Search the web using Brave Search API. +Search the web using Brave Search API (default), Perplexity Sonar, or [Querit](https://www.querit.ai). Core parameters: - `query` (required) - `count` (1–10; default from `tools.web.search.maxResults`) Notes: -- Requires a Brave API key (recommended: `moltbot configure --section web`, or set `BRAVE_API_KEY`). +- Requires an API key for your chosen provider (recommended: `moltbot configure --section web`, or set `BRAVE_API_KEY` / `PERPLEXITY_API_KEY` / `OPENROUTER_API_KEY` / `QUERIT_API_KEY`). - Enable via `tools.web.search.enabled`. - Responses are cached (default 15 min). - See [Web tools](/tools/web) for setup. diff --git a/docs/tools/web.md b/docs/tools/web.md index be2a57f9e..ac8cd0135 100644 --- a/docs/tools/web.md +++ b/docs/tools/web.md @@ -1,16 +1,17 @@ --- -summary: "Web search + fetch tools (Brave Search API, Perplexity direct/OpenRouter)" +summary: "Web search + fetch tools (Brave Search API, Perplexity direct/OpenRouter, Querit)" read_when: - You want to enable web_search or web_fetch - You need Brave Search API key setup - You want to use Perplexity Sonar for web search + - You want to use Querit for web search --- # Web tools Moltbot ships two lightweight web tools: -- `web_search` — Search the web via Brave Search API (default) or Perplexity Sonar (direct or via OpenRouter). +- `web_search` — Search the web via Brave Search API (default), Perplexity Sonar (direct or via OpenRouter), or [Querit](https://www.querit.ai). - `web_fetch` — HTTP fetch + readable extraction (HTML → markdown/text). These are **not** browser automation. For JS-heavy sites or logins, use the @@ -21,6 +22,7 @@ These are **not** browser automation. For JS-heavy sites or logins, use the - `web_search` calls your configured provider and returns results. - **Brave** (default): returns structured results (title, URL, snippet). - **Perplexity**: returns AI-synthesized answers with citations from real-time web search. + - **Querit**: returns structured results (title, URL, snippet). - Results are cached by query for 15 minutes (configurable). - `web_fetch` does a plain HTTP GET and extracts readable content (HTML → markdown/text). It does **not** execute JavaScript. @@ -32,8 +34,9 @@ These are **not** browser automation. For JS-heavy sites or logins, use the |----------|------|------|---------| | **Brave** (default) | Fast, structured results, free tier | Traditional search results | `BRAVE_API_KEY` | | **Perplexity** | AI-synthesized answers, citations, real-time | Requires Perplexity or OpenRouter access | `OPENROUTER_API_KEY` or `PERPLEXITY_API_KEY` | +| **Querit** | Fast, structured results | Traditional search results | `QUERIT_API_KEY` | -See [Brave Search setup](/brave-search) and [Perplexity Sonar](/perplexity) for provider-specific details. +See [Brave Search setup](/brave-search), [Perplexity Sonar](/perplexity), and [Querit](/querit) for provider-specific details. Set the provider in config: @@ -42,7 +45,7 @@ Set the provider in config: tools: { web: { search: { - provider: "brave" // or "perplexity" + provider: "brave" // or "perplexity" or "querit" } } } @@ -138,6 +141,34 @@ If no base URL is set, Moltbot chooses a default based on the API key source: | `perplexity/sonar-pro` (default) | Multi-step reasoning with web search | Complex questions | | `perplexity/sonar-reasoning-pro` | Chain-of-thought analysis | Deep research | +## Using Querit + +Querit provides fast web search with structured results. + +### Getting a Querit API key + +1) Create an account at https://www.querit.ai/ +2) Generate an API key in your account settings. + +### Setting up Querit search + +```json5 +{ + tools: { + web: { + search: { + provider: "querit", + querit: { + apiKey: "QUERIT_API_KEY_HERE" + } + } + } + } +} +``` + +**Environment alternative:** set `QUERIT_API_KEY` in the Gateway environment. For a gateway install, put it in `~/.clawdbot/.env`. + ## web_search Search the web using your configured provider. @@ -148,6 +179,7 @@ Search the web using your configured provider. - 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` + - **Querit**: `QUERIT_API_KEY` or `tools.web.search.querit.apiKey` ### Config diff --git a/src/agents/tools/web-search.ts b/src/agents/tools/web-search.ts index 1d87676e8..d32ae1a8f 100644 --- a/src/agents/tools/web-search.ts +++ b/src/agents/tools/web-search.ts @@ -17,11 +17,12 @@ import { writeCache, } from "./web-shared.js"; -const SEARCH_PROVIDERS = ["brave", "perplexity"] as const; +const SEARCH_PROVIDERS = ["brave", "perplexity", "querit"] as const; const DEFAULT_SEARCH_COUNT = 5; const MAX_SEARCH_COUNT = 10; const BRAVE_SEARCH_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"; +const QUERIT_SEARCH_ENDPOINT = "https://api.querit.ai/v1/search"; const DEFAULT_PERPLEXITY_BASE_URL = "https://openrouter.ai/api/v1"; const PERPLEXITY_DIRECT_BASE_URL = "https://api.perplexity.ai"; const DEFAULT_PERPLEXITY_MODEL = "perplexity/sonar-pro"; @@ -103,6 +104,20 @@ type PerplexitySearchResponse = { type PerplexityBaseUrlHint = "direct" | "openrouter"; +type QueritSearchResult = { + title?: string; + url?: string; + snippet?: string; +}; + +type QueritSearchResponse = { + error_code?: number; + error?: string; + results?: { + result?: QueritSearchResult[]; + }; +}; + function resolveSearchConfig(cfg?: MoltbotConfig): WebSearchConfig { const search = cfg?.tools?.web?.search; if (!search || typeof search !== "object") return undefined; @@ -131,6 +146,13 @@ function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) { docs: "https://docs.molt.bot/tools/web", }; } + if (provider === "querit") { + return { + error: "missing_querit_api_key", + message: `web_search (querit) needs an API key. Run \`${formatCliCommand("clawdbot configure --section web")}\` to store it, or set QUERIT_API_KEY in the Gateway environment.`, + docs: "https://docs.clawd.bot/tools/web", + }; + } return { error: "missing_brave_api_key", message: `web_search needs a Brave Search API key. Run \`${formatCliCommand("moltbot configure --section web")}\` to store it, or set BRAVE_API_KEY in the Gateway environment.`, @@ -144,6 +166,7 @@ function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDE ? search.provider.trim().toLowerCase() : ""; if (raw === "perplexity") return "perplexity"; + if (raw === "querit") return "querit"; if (raw === "brave") return "brave"; return "brave"; } @@ -193,6 +216,16 @@ function inferPerplexityBaseUrlFromApiKey(apiKey?: string): PerplexityBaseUrlHin return undefined; } +function resolveQueritApiKey(search?: WebSearchConfig): string | undefined { + if (!search || typeof search !== "object") return undefined; + const querit = "querit" in search ? search.querit : undefined; + if (!querit || typeof querit !== "object") return undefined; + const fromConfig = + "apiKey" in querit && typeof querit.apiKey === "string" ? querit.apiKey.trim() : ""; + const fromEnv = (process.env.QUERIT_API_KEY ?? "").trim(); + return fromConfig || fromEnv || undefined; +} + function resolvePerplexityBaseUrl( perplexity?: PerplexityConfig, apiKeySource: PerplexityApiKeySource = "none", @@ -306,6 +339,43 @@ async function runPerplexitySearch(params: { return { content, citations }; } +async function runQueritSearch(params: { + query: string; + apiKey: string; + timeoutSeconds: number; +}): Promise> { + const res = await fetch(QUERIT_SEARCH_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${params.apiKey}`, + }, + body: JSON.stringify({ query: params.query }), + signal: withTimeout(undefined, params.timeoutSeconds * 1000), + }); + + if (!res.ok) { + const detail = await readResponseText(res); + throw new Error(`Querit API error (${res.status}): ${detail || res.statusText}`); + } + + const data = (await res.json()) as QueritSearchResponse; + + if (data.error_code && data.error_code !== 200) { + throw new Error(`Querit API error (${data.error_code}): ${data.error ?? "Unknown error"}`); + } + + if (!data.results?.result || !Array.isArray(data.results.result)) { + return []; + } + + return data.results.result.map((entry) => ({ + title: entry.title ?? "", + url: entry.url ?? "", + snippet: entry.snippet ?? "", + })); +} + async function runWebSearch(params: { query: string; count: number; @@ -351,6 +421,31 @@ async function runWebSearch(params: { return payload; } + if (params.provider === "querit") { + const results = await runQueritSearch({ + query: params.query, + apiKey: params.apiKey, + timeoutSeconds: params.timeoutSeconds, + }); + + const mapped = results.slice(0, params.count).map((entry) => ({ + title: entry.title, + url: entry.url, + description: entry.snippet, + siteName: resolveSiteName(entry.url), + })); + + const payload = { + query: params.query, + provider: params.provider, + count: mapped.length, + tookMs: Date.now() - start, + results: mapped, + }; + writeCache(SEARCH_CACHE, cacheKey, payload, params.cacheTtlMs); + return payload; + } + if (params.provider !== "brave") { throw new Error("Unsupported web search provider."); } @@ -419,7 +514,9 @@ export function createWebSearchTool(options?: { const description = provider === "perplexity" ? "Search the web using Perplexity Sonar (direct 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."; + : provider === "querit" + ? "Search the web using Querit search API. Returns titles, URLs, and snippets for fast research." + : "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 { label: "Web Search", @@ -430,7 +527,11 @@ export function createWebSearchTool(options?: { const perplexityAuth = provider === "perplexity" ? resolvePerplexityApiKey(perplexityConfig) : undefined; const apiKey = - provider === "perplexity" ? perplexityAuth?.apiKey : resolveSearchApiKey(search); + provider === "perplexity" + ? perplexityAuth?.apiKey + : provider === "querit" + ? resolveQueritApiKey(search) + : resolveSearchApiKey(search); if (!apiKey) { return jsonResult(missingSearchKeyPayload(provider)); diff --git a/src/config/schema.ts b/src/config/schema.ts index 9b5ad8be6..8e34e1b13 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -434,7 +434,7 @@ const FIELD_HELP: Record = { '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 a provider API key).", - "tools.web.search.provider": 'Search provider ("brave" or "perplexity").', + "tools.web.search.provider": 'Search provider ("brave", "perplexity", or "querit").', "tools.web.search.apiKey": "Brave Search API key (fallback: BRAVE_API_KEY env var).", "tools.web.search.maxResults": "Default number of results to return (1-10).", "tools.web.search.timeoutSeconds": "Timeout in seconds for web_search requests.", @@ -445,6 +445,7 @@ const FIELD_HELP: Record = { "Perplexity base URL override (default: https://openrouter.ai/api/v1 or https://api.perplexity.ai).", "tools.web.search.perplexity.model": 'Perplexity model override (default: "perplexity/sonar-pro").', + "tools.web.search.querit.apiKey": "Querit API key (fallback: QUERIT_API_KEY env var).", "tools.web.fetch.enabled": "Enable the web_fetch tool (lightweight HTTP fetch).", "tools.web.fetch.maxChars": "Max characters returned by web_fetch (truncated).", "tools.web.fetch.timeoutSeconds": "Timeout in seconds for web_fetch requests.", diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index bb1d45bf0..0bbcabd18 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -334,8 +334,8 @@ export type ToolsConfig = { search?: { /** Enable web search tool (default: true when API key is present). */ enabled?: boolean; - /** Search provider ("brave" or "perplexity"). */ - provider?: "brave" | "perplexity"; + /** Search provider ("brave", "perplexity", or "querit"). */ + provider?: "brave" | "perplexity" | "querit"; /** Brave Search API key (optional; defaults to BRAVE_API_KEY env var). */ apiKey?: string; /** Default search results count (1-10). */ @@ -353,6 +353,11 @@ export type ToolsConfig = { /** Model to use (defaults to "perplexity/sonar-pro"). */ model?: string; }; + /** Querit-specific configuration (used when provider="querit"). */ + querit?: { + /** API key for Querit (defaults to QUERIT_API_KEY env var). */ + apiKey?: string; + }; }; fetch?: { /** Enable web fetch tool (default: true). */ diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index 7a63e307d..b8e880771 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -165,7 +165,9 @@ export const ToolPolicySchema = ToolPolicyBaseSchema.superRefine((value, ctx) => export const ToolsWebSearchSchema = z .object({ enabled: z.boolean().optional(), - provider: z.union([z.literal("brave"), z.literal("perplexity")]).optional(), + provider: z + .union([z.literal("brave"), z.literal("perplexity"), z.literal("querit")]) + .optional(), apiKey: z.string().optional(), maxResults: z.number().int().positive().optional(), timeoutSeconds: z.number().int().positive().optional(), @@ -178,6 +180,12 @@ export const ToolsWebSearchSchema = z }) .strict() .optional(), + querit: z + .object({ + apiKey: z.string().optional(), + }) + .strict() + .optional(), }) .strict() .optional();