From 15e6ee9a91340125d4433df49c4da5b3a74f611a Mon Sep 17 00:00:00 2001 From: erik Date: Sun, 21 Jul 2024 06:37:37 +0400 Subject: [PATCH] feat: enhance search functionality and add polling options - Improved README documentation with corrected title, description, and added Polling Options section. - Updated example code in README to demonstrate new query usage with getSummarizedAnswer method. - Bumped version to 0.6.5 in package.json and deno.json. - Added support for configurable polling options in BraveSearch class. - Enhanced webSearch, getSummarizedAnswer, localPoiSearch, and localDescriptionsSearch methods to accept optional AbortSignal for request cancellation. - Improved error handling in API requests for more detailed error messages. - Added PollingOptions interface for defining polling configuration. --- README.md | 38 +++++++++++++++---------- deno.json | 2 +- package.json | 2 +- src/braveSearch.ts | 70 +++++++++++++++++++++++++++++++++------------- src/types.ts | 43 +++++++++++++++++++++++----- 5 files changed, 112 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index c8250ba..0d76aea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Brave Search API wrapper +# Brave Search API Wrapper -A fully typed Brave Search API wrapper, providing easy access to web search, local POI search, and automatic polling for AI generated web search summary feature. +A fully typed Brave Search API wrapper, providing easy access to web search, local POI search, and automatic polling for AI-generated web search summary feature. ## Installation @@ -57,7 +57,7 @@ import { BraveSearch } from "@tyr/brave-search/"; Perform a web search using the `webSearch` method. Your IDE will provide type hints for the parameters and return types. ```typescript -const webSearchResults = await braveSearch.webSearch("TypeScript tutorial", { +const webSearchResults = await braveSearch.webSearch("How many stars there are in our galaxy?", { count: 5, safesearch: "off", search_lang: "en", @@ -72,18 +72,15 @@ console.log(webSearchResults); Get a summarized answer for a query using the `getSummarizedAnswer` method. This method returns an object containing `summary` and `webSearch` promises. ```typescript -const { summary, webSearch } = braveSearch.getSummarizedAnswer( - "How many stars there are in our galaxy?", - { - count: 5, // Number of search results to return - safesearch: "off", // Optional: Filter for adult content (default is "moderate") - search_lang: "en", // Optional: Language of the search results (default is "en") - country: "US", // Optional: Country for the search results (default is "us") - text_decorations: false, // Optional: Whether to include text decorations (default is true) - spellcheck: false, // Optional: Whether to enable spellcheck (default is true) - extra_snippets: true, // Optional: Whether to include extra snippets (default is false) - }, -); +const { summary, webSearch } = braveSearch.getSummarizedAnswer("How many stars there are in our galaxy?", { + count: 5, // Number of search results to return + safesearch: "off", // Optional: Filter for adult content (default is "moderate") + search_lang: "en", // Optional: Language of the search results (default is "en") + country: "US", // Optional: Country for the search results (default is "us") + text_decorations: false, // Optional: Whether to include text decorations (default is true) + spellcheck: false, // Optional: Whether to enable spellcheck (default is true) + extra_snippets: true, // Optional: Whether to include extra snippets (default is false) +}); // Wait for the web search results (almost immediately) const webSearchResponse = await webSearch; @@ -116,6 +113,17 @@ console.log(descriptionResults); The library supports various search options as defined in the Brave Search API documentation. For a complete list of options and their descriptions, please refer to the [Brave Search API Documentation](https://api.search.brave.com/app/documentation/web-search/). +### Polling Options + +The `BraveSearch` class supports configurable polling options for summarization. You can customize the polling interval and the maximum number of polling attempts by passing a `PollingOptions` object to the constructor. + +```typescript +const braveSearch = new BraveSearch(BRAVE_API_KEY, { + pollInterval: 1000, // (default is 500ms) + maxPollAttempts: 30, // (default is 20) +}); +``` + ## Important Notes - The `summary` option and `getSummarizedAnswer` method are only available with the Brave "Data for AI pro" plan. diff --git a/deno.json b/deno.json index 281e20e..779c25a 100644 --- a/deno.json +++ b/deno.json @@ -1,5 +1,5 @@ { "name": "@tyr/brave-search", - "version": "0.6.2", + "version": "0.6.5", "exports": "./src/braveSearch.ts" } diff --git a/package.json b/package.json index 708d32d..96d0f13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "brave-search", - "version": "0.6.2", + "version": "0.6.5", "description": "A fully typed Brave Search API wrapper, providing easy access to web search, local POI search, and automatic polling for web search summary feature.", "scripts": { "build": "tsc", diff --git a/src/braveSearch.ts b/src/braveSearch.ts index faaf9e4..e459950 100644 --- a/src/braveSearch.ts +++ b/src/braveSearch.ts @@ -15,12 +15,14 @@ import axios from "axios"; -const DEFAULT_POLLING_TIMEOUT = 20000; +const DEFAULT_POLLING_INTERVAL = 500; +const DEFAULT_MAX_POLL_ATTEMPTS = 20; import { BraveSearchOptions, LocalDescriptionsSearchApiResponse, LocalPoiSearchApiResponse, + PollingOptions, SummarizerOptions, SummarizerSearchApiResponse, WebSearchApiResponse, @@ -46,28 +48,24 @@ class BraveSearchError extends Error { } /** - * The main class for interacting with the Brave Search API. + * The main class for interacting with the Brave Search API, holding API key for all the requests made with it. * It provides methods for web search, local POI search, and summarization. */ class BraveSearch { private apiKey: string; private baseUrl = "https://api.search.brave.com/res/v1"; - private pollInterval = 500; - private maxPollAttempts = DEFAULT_POLLING_TIMEOUT / this.pollInterval; + private pollInterval: number; + private maxPollAttempts: number; /** * Initializes a new instance of the BraveSearch class. * @param apiKey The API key for accessing the Brave Search API. - * @param options Optional settings to configure the search behavior. - * - maxPollAttempts: Maximum number of attempts to poll for a summary. - * - pollInterval: Interval in milliseconds between polling attempts. + * @param options */ - constructor(apiKey: string, options?: { maxPollAttempts?: number; pollInterval?: number }) { + constructor(apiKey: string, options?: PollingOptions) { this.apiKey = apiKey; - if (options) { - this.maxPollAttempts = options.maxPollAttempts ?? this.maxPollAttempts; - this.pollInterval = options.pollInterval ?? this.pollInterval; - } + this.pollInterval = options?.pollInterval ?? DEFAULT_POLLING_INTERVAL; + this.maxPollAttempts = options?.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS; } /** @@ -76,7 +74,11 @@ class BraveSearch { * @param options Optional settings to configure the search behavior. * @returns A promise that resolves to the search results. */ - async webSearch(query: string, options: BraveSearchOptions = {}): Promise { + async webSearch( + query: string, + options: BraveSearchOptions = {}, + signal?: AbortSignal, + ): Promise { const params = new URLSearchParams({ q: query, ...this.formatOptions(options), @@ -87,6 +89,7 @@ class BraveSearch { `${this.baseUrl}/web/search?${params.toString()}`, { headers: this.getHeaders(), + signal, }, ); return response.data; @@ -97,7 +100,9 @@ class BraveSearch { } /** - * Executes a web search for the provided query and polls for a summary if available. + * Executes a web search for the provided query and polls for a summary + * if the query is eligible for a summary and summarizer key is provided in the web search response. + * The summary is usually ready within 2 seconds after the original web search response is received. * @param query The search query string. * @param options Optional settings to configure the search behavior. * @param summarizerOptions Optional settings specific to summarization. @@ -107,17 +112,18 @@ class BraveSearch { query: string, options: Omit = {}, summarizerOptions: SummarizerOptions = {}, + signal?: AbortSignal, ): { summary: Promise; webSearch: Promise; } { try { - const webSearchResponse = this.webSearch(query, options); + const webSearchResponse = this.webSearch(query, options, signal); const summaryPromise = webSearchResponse.then(async (webSearchResponse) => { const summarizerKey = webSearchResponse.summarizer?.key; if (summarizerKey) { - return await this.pollForSummary(summarizerKey, summarizerOptions); + return await this.pollForSummary(summarizerKey, summarizerOptions, signal); } return undefined; @@ -134,7 +140,7 @@ class BraveSearch { * @param ids The IDs of the local points of interest. * @returns A promise that resolves to the search results. */ - async localPoiSearch(ids: string[]): Promise { + async localPoiSearch(ids: string[], signal?: AbortSignal): Promise { const params = new URLSearchParams({ ids: ids.join(","), }); @@ -144,6 +150,7 @@ class BraveSearch { `${this.baseUrl}/local/pois?${params.toString()}`, { headers: this.getHeaders(), + signal, }, ); return response.data; @@ -157,7 +164,10 @@ class BraveSearch { * @param ids The IDs of the local points of interest. * @returns A promise that resolves to the search results. */ - async localDescriptionsSearch(ids: string[]): Promise { + async localDescriptionsSearch( + ids: string[], + signal?: AbortSignal, + ): Promise { const params = new URLSearchParams({ ids: ids.join(","), }); @@ -167,6 +177,7 @@ class BraveSearch { `${this.baseUrl}/local/descriptions?${params.toString()}`, { headers: this.getHeaders(), + signal, }, ); return response.data; @@ -175,12 +186,31 @@ class BraveSearch { } } + /** + * Polls for a summary response after a web search request. This method is suggested by the Brave Search API documentation + * as the way to retrieve a summary after initiating a web search. + * + * @param key The key identifying the summary request. + * @param options Optional settings specific to summarization. + * @param signal Optional AbortSignal to cancel the request. + * @returns A promise that resolves to the summary response if available, or undefined if the summary is not ready. + * @throws {BraveSearchError} If the summary generation fails or if the summary is not available after maximum polling attempts. + * + * **Polling Behavior:** + * - The method will make up to 20 attempts to fetch the summary by default. + * - Each attempt is spaced 500ms apart. + * - If the summary is not ready after 20 attempts, a BraveSearchError is thrown. + * + * **Configuration:** + * - The number of attempts and the interval between attempts can be configured through the class constructor options. + */ private async pollForSummary( key: string, options: SummarizerOptions, + signal?: AbortSignal, ): Promise { for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) { - const summaryResponse = await this.summarizerSearch(key, options); + const summaryResponse = await this.summarizerSearch(key, options, signal); if (summaryResponse.status === "complete" && summaryResponse.summary) { return summaryResponse; @@ -197,6 +227,7 @@ class BraveSearch { private async summarizerSearch( key: string, options: SummarizerOptions, + signal?: AbortSignal, ): Promise { const params = new URLSearchParams({ key, @@ -208,6 +239,7 @@ class BraveSearch { `${this.baseUrl}/summarizer/search?${params.toString()}`, { headers: this.getHeaders(), + signal, }, ); return response.data; diff --git a/src/types.ts b/src/types.ts index 784d39c..edf8589 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,7 +11,7 @@ export type ResultFilter = string; /** - * Options for configuring a web search request to the Brave Search API. + * Options for configuring a web search requests to the Brave Search API. */ export interface BraveSearchOptions { /** @@ -42,12 +42,23 @@ export interface BraveSearchOptions { * Filters search results by when they were discovered. * Can be a predefined option from the FreshnessOption enum or a custom date range string. * @type {Freshness} - * @example - * // Using a predefined freshness option - * const options = { freshness: FreshnessOption.PastWeek }; - * @example - * // Using a custom date range string - * const customOptions = { freshness: "2022-01-01to2022-12-31" }; + * @example Using predefined timeranges + * import { BraveSearch, FreshnessOption } + * const braveSearch = new BraveSearch('your-api-key', { + * freshness: FreshnessOption.PastWeek + * }); + * const resuls = await braveSearch.webSearch('some query', { + * freshness: FreshnessOption.PastWeek + * }); + * + * @example Using custom date range + * import { BraveSearch, FreshnessOption } + * const braveSearch = new BraveSearch('your-api-key', { + * freshness: FreshnessOption.PastWeek + * }); + * const resuls = await braveSearch.webSearch('some query', { + * freshness: '2022-01-01to2022-12-31' + * }); */ freshness?: Freshness; /** @@ -104,6 +115,24 @@ export interface BraveSearchOptions { summary?: boolean; } +/** + * Options for configuring polling behavior when waiting for summary results from the Brave Search API. + */ +export interface PollingOptions { + /** + * Interval in milliseconds between polling attempts for summary. + * @type {number} + * @default 500 + */ + pollInterval?: number; + /** + * Maximum number of attempts to poll for a summary. + * @type {number} + * @default 20 + */ + maxPollAttempts?: number; +} + /** * Options for configuring a AI summary answer content. */