kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: add @agentic/brave-search package
rodzic
0d8f59eef8
commit
4d85f2211d
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
title: Brave Search
|
||||
description: Brave web search and local places search.
|
||||
---
|
||||
|
||||
- package: `@agentic/brave-search`
|
||||
- exports: `class BraveSearchClient`, `namespace braveSearch`
|
||||
- env vars: `BRAVE_SEARCH_API_KEY`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/brave-search/src/brave-search-client.ts)
|
||||
- [brave search docs](https://brave.com/search/api)
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/brave-search
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/brave-search
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/brave-search
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { BraveSearchClient } from '@agentic/brave-search'
|
||||
|
||||
const braveSearch = new BraveSearchClient()
|
||||
const res = await braveSearch.search('latest news about openai')
|
||||
```
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "@agentic/brave-search",
|
||||
"version": "0.1.0",
|
||||
"description": "Agentic SDK for the Brave search engine.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/brave-search"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"ky": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@agentic/tsconfig": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<p align="center">
|
||||
<a href="https://agentic.so">
|
||||
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
|
||||
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
|
||||
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
|
||||
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
|
||||
</p>
|
||||
|
||||
# Agentic
|
||||
|
||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1,154 @@
|
|||
import {
|
||||
aiFunction,
|
||||
AIFunctionsProvider,
|
||||
assert,
|
||||
getEnv,
|
||||
sanitizeSearchParams
|
||||
} from '@agentic/core'
|
||||
import defaultKy, { type KyInstance } from 'ky'
|
||||
|
||||
import { bravesearch } from './brave-search'
|
||||
|
||||
/**
|
||||
* Agentic client for the Brave search engine.
|
||||
*
|
||||
* @see https://brave.com/search/api
|
||||
*/
|
||||
export class BraveSearchClient extends AIFunctionsProvider {
|
||||
protected readonly ky: KyInstance
|
||||
protected readonly apiKey: string
|
||||
protected readonly apiBaseUrl: string
|
||||
|
||||
constructor({
|
||||
apiKey = getEnv('BRAVE_SEARCH_API_KEY'),
|
||||
apiBaseUrl = bravesearch.apiBaseUrl,
|
||||
ky = defaultKy
|
||||
}: {
|
||||
apiKey?: string
|
||||
apiBaseUrl?: string
|
||||
ky?: KyInstance
|
||||
} = {}) {
|
||||
assert(
|
||||
apiKey,
|
||||
'BraveSearchClient missing required "apiKey" (defaults to "BRAVE_SEARCH_API_KEY")'
|
||||
)
|
||||
super()
|
||||
|
||||
this.apiKey = apiKey
|
||||
this.apiBaseUrl = apiBaseUrl
|
||||
|
||||
this.ky = ky.extend({
|
||||
prefixUrl: apiBaseUrl,
|
||||
headers: {
|
||||
'X-Subscription-Token': apiKey
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Brave web search.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'brave_search',
|
||||
description:
|
||||
'Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. ' +
|
||||
'Use this for broad information gathering, recent events, or when you need diverse web sources. ' +
|
||||
'Supports pagination, content filtering, and freshness controls. ' +
|
||||
'Maximum 20 results per request, with offset for pagination. ',
|
||||
inputSchema: bravesearch.SearchParamsSchema
|
||||
})
|
||||
async search(
|
||||
queryOrParams: string | bravesearch.SearchParams
|
||||
): Promise<bravesearch.SearchResponse> {
|
||||
const { query: q, ...params } =
|
||||
typeof queryOrParams === 'string'
|
||||
? { query: queryOrParams }
|
||||
: queryOrParams
|
||||
|
||||
return this.ky
|
||||
.get('res/v1/web/search', {
|
||||
searchParams: sanitizeSearchParams({
|
||||
...params,
|
||||
q
|
||||
})
|
||||
})
|
||||
.json<bravesearch.SearchResponse>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Brave local search for businesses and places.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'brave_local_search',
|
||||
description:
|
||||
"Searches for local businesses and places using Brave's Local Search API. " +
|
||||
'Best for queries related to physical locations, businesses, restaurants, services, etc. ' +
|
||||
'Returns detailed information including:\n' +
|
||||
'- Business names and addresses\n' +
|
||||
'- Ratings and review counts\n' +
|
||||
'- Phone numbers and opening hours\n' +
|
||||
"Use this when the query implies 'near me' or mentions specific locations. " +
|
||||
'Automatically falls back to web search if no local results are found.',
|
||||
inputSchema: bravesearch.LocalSearchParamsSchema
|
||||
})
|
||||
async localSearch(
|
||||
queryOrParams: string | bravesearch.LocalSearchParams
|
||||
): Promise<bravesearch.LocalSearchResponse | bravesearch.SearchResponse> {
|
||||
const { query: q, ...params } =
|
||||
typeof queryOrParams === 'string'
|
||||
? { query: queryOrParams }
|
||||
: queryOrParams
|
||||
|
||||
const webData = await this.ky
|
||||
.get('res/v1/web/search', {
|
||||
searchParams: sanitizeSearchParams({
|
||||
search_lang: 'en',
|
||||
result_filter: 'locations',
|
||||
...params,
|
||||
q
|
||||
})
|
||||
})
|
||||
.json<bravesearch.SearchResponse>()
|
||||
|
||||
const locationIds = webData.locations?.results
|
||||
?.filter((r) => !!r.id)
|
||||
.map((r) => r.id)
|
||||
|
||||
if (!locationIds?.length) {
|
||||
return this.search(queryOrParams)
|
||||
}
|
||||
|
||||
// Get POI details and descriptions in parallel
|
||||
const [pois, descriptions] = await Promise.all([
|
||||
this.getPoisData(locationIds),
|
||||
this.getDescriptionsData(locationIds)
|
||||
])
|
||||
|
||||
const desc = descriptions.descriptions
|
||||
|
||||
return Object.entries(desc).map(([id, description]) => ({
|
||||
description,
|
||||
...pois.results.find((r) => r.id === id)!
|
||||
}))
|
||||
}
|
||||
|
||||
async getPoisData(ids: string[]): Promise<bravesearch.PoiResponse> {
|
||||
return this.ky
|
||||
.get('res/v1/local/pois', {
|
||||
searchParams: sanitizeSearchParams({
|
||||
ids: ids.filter(Boolean)
|
||||
})
|
||||
})
|
||||
.json<bravesearch.PoiResponse>()
|
||||
}
|
||||
|
||||
async getDescriptionsData(ids: string[]): Promise<bravesearch.Description> {
|
||||
return this.ky
|
||||
.get('res/v1/local/descriptions', {
|
||||
searchParams: sanitizeSearchParams({
|
||||
ids: ids.filter(Boolean)
|
||||
})
|
||||
})
|
||||
.json<bravesearch.Description>()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
export namespace bravesearch {
|
||||
export const apiBaseUrl = 'https://api.search.brave.com'
|
||||
|
||||
export const SearchParamsSchema = z.object({
|
||||
query: z
|
||||
.string()
|
||||
.describe('Search query (max 400 chars, 50 words)')
|
||||
.optional(),
|
||||
count: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(20)
|
||||
.describe('Number of results (1-20, default 10)')
|
||||
.optional(),
|
||||
offset: z
|
||||
.number()
|
||||
.describe('Pagination offset (max 9, default 0)')
|
||||
.optional()
|
||||
})
|
||||
export type SearchParams = z.infer<typeof SearchParamsSchema>
|
||||
|
||||
export const LocalSearchParamsSchema = z.object({
|
||||
query: z
|
||||
.string()
|
||||
.describe('Search query (max 400 chars, 50 words)')
|
||||
.optional(),
|
||||
count: z
|
||||
.number()
|
||||
.describe('Number of results (1-20, default 10)')
|
||||
.int()
|
||||
.min(1)
|
||||
.max(20)
|
||||
.optional()
|
||||
})
|
||||
export type LocalSearchParams = z.infer<typeof LocalSearchParamsSchema>
|
||||
|
||||
export interface SearchResponse {
|
||||
web?: {
|
||||
results?: Array<{
|
||||
title: string
|
||||
description: string
|
||||
url: string
|
||||
language?: string
|
||||
published?: string
|
||||
rank?: number
|
||||
}>
|
||||
}
|
||||
locations?: {
|
||||
results?: Array<{
|
||||
id: string // Required by API
|
||||
title?: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
id: string
|
||||
name: string
|
||||
address: {
|
||||
streetAddress?: string
|
||||
addressLocality?: string
|
||||
addressRegion?: string
|
||||
postalCode?: string
|
||||
}
|
||||
coordinates?: {
|
||||
latitude: number
|
||||
longitude: number
|
||||
}
|
||||
phone?: string
|
||||
rating?: {
|
||||
ratingValue?: number
|
||||
ratingCount?: number
|
||||
}
|
||||
openingHours?: string[]
|
||||
priceRange?: string
|
||||
}
|
||||
|
||||
export interface PoiResponse {
|
||||
results: Location[]
|
||||
}
|
||||
|
||||
export interface Description {
|
||||
descriptions: { [id: string]: string }
|
||||
}
|
||||
|
||||
export type LocalSearchResponse = Array<Location & { description: string }>
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './brave-search'
|
||||
export * from './brave-search-client'
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@agentic/tsconfig/base.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
Ładowanie…
Reference in New Issue