Skip to content

Commit

Permalink
feat: enhance search functionality and add polling options
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
erik-balfe committed Jul 23, 2024
1 parent af0b6c0 commit 15e6ee9
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 43 deletions.
38 changes: 23 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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",
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@tyr/brave-search",
"version": "0.6.2",
"version": "0.6.5",
"exports": "./src/braveSearch.ts"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
70 changes: 51 additions & 19 deletions src/braveSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}

/**
Expand All @@ -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<WebSearchApiResponse> {
async webSearch(
query: string,
options: BraveSearchOptions = {},
signal?: AbortSignal,
): Promise<WebSearchApiResponse> {
const params = new URLSearchParams({
q: query,
...this.formatOptions(options),
Expand All @@ -87,6 +89,7 @@ class BraveSearch {
`${this.baseUrl}/web/search?${params.toString()}`,
{
headers: this.getHeaders(),
signal,
},
);
return response.data;
Expand All @@ -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.
Expand All @@ -107,17 +112,18 @@ class BraveSearch {
query: string,
options: Omit<BraveSearchOptions, "summary"> = {},
summarizerOptions: SummarizerOptions = {},
signal?: AbortSignal,
): {
summary: Promise<SummarizerSearchApiResponse | undefined>;
webSearch: Promise<WebSearchApiResponse>;
} {
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;
Expand All @@ -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<LocalPoiSearchApiResponse> {
async localPoiSearch(ids: string[], signal?: AbortSignal): Promise<LocalPoiSearchApiResponse> {
const params = new URLSearchParams({
ids: ids.join(","),
});
Expand All @@ -144,6 +150,7 @@ class BraveSearch {
`${this.baseUrl}/local/pois?${params.toString()}`,
{
headers: this.getHeaders(),
signal,
},
);
return response.data;
Expand All @@ -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<LocalDescriptionsSearchApiResponse> {
async localDescriptionsSearch(
ids: string[],
signal?: AbortSignal,
): Promise<LocalDescriptionsSearchApiResponse> {
const params = new URLSearchParams({
ids: ids.join(","),
});
Expand All @@ -167,6 +177,7 @@ class BraveSearch {
`${this.baseUrl}/local/descriptions?${params.toString()}`,
{
headers: this.getHeaders(),
signal,
},
);
return response.data;
Expand All @@ -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<SummarizerSearchApiResponse | undefined> {
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;
Expand All @@ -197,6 +227,7 @@ class BraveSearch {
private async summarizerSearch(
key: string,
options: SummarizerOptions,
signal?: AbortSignal,
): Promise<SummarizerSearchApiResponse> {
const params = new URLSearchParams({
key,
Expand All @@ -208,6 +239,7 @@ class BraveSearch {
`${this.baseUrl}/summarizer/search?${params.toString()}`,
{
headers: this.getHeaders(),
signal,
},
);
return response.data;
Expand Down
43 changes: 36 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -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;
/**
Expand Down Expand Up @@ -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.
*/
Expand Down

0 comments on commit 15e6ee9

Please sign in to comment.