chatgpt-api/stdlib/serper/src/serper-client.ts

304 wiersze
7.1 KiB
TypeScript

import {
aiFunction,
AIFunctionsProvider,
assert,
getEnv,
omit
} from '@agentic/core'
import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod'
export namespace serper {
export const API_BASE_URL = 'https://google.serper.dev'
export const SearchParamsSchema = z.object({
q: z.string().describe('search query'),
autocorrect: z.boolean().default(true).optional(),
gl: z.string().default('us').optional(),
hl: z.string().default('en').optional(),
page: z.number().int().default(1).optional(),
num: z
.number()
.int()
.default(10)
.optional()
.describe('number of results to return')
})
export type SearchParams = z.infer<typeof SearchParamsSchema>
export const GeneralSearchSchema = SearchParamsSchema.extend({
type: z
.enum(['search', 'images', 'videos', 'places', 'news', 'shopping'])
.default('search')
.optional()
.describe('Type of Google search to perform')
})
export type GeneralSearchParams = z.infer<typeof GeneralSearchSchema>
export interface SearchResponse {
searchParameters: SearchParameters & { type: 'search' }
organic: Organic[]
answerBox?: AnswerBox
knowledgeGraph?: KnowledgeGraph
topStories?: TopStory[]
peopleAlsoAsk?: PeopleAlsoAsk[]
relatedSearches?: RelatedSearch[]
}
export interface SearchImagesResponse {
searchParameters: SearchParameters & { type: 'images' }
images: Image[]
}
export interface SearchVideosResponse {
searchParameters: SearchParameters & { type: 'videos' }
videos: Video[]
}
export interface SearchPlacesResponse {
searchParameters: SearchParameters & { type: 'places' }
places: Place[]
}
export interface SearchNewsResponse {
searchParameters: SearchParameters & { type: 'news' }
news: News[]
}
export interface SearchShoppingResponse {
searchParameters: SearchParameters & { type: 'shopping' }
shopping: Shopping[]
}
export type Response =
| SearchResponse
| SearchImagesResponse
| SearchVideosResponse
| SearchPlacesResponse
| SearchNewsResponse
| SearchShoppingResponse
export interface KnowledgeGraph {
title: string
type: string
website: string
imageUrl: string
description: string
descriptionSource: string
descriptionLink: string
attributes: Record<string, string>
}
export interface Organic {
title: string
link: string
snippet: string
position: number
imageUrl?: string
sitelinks?: SiteLink[]
}
export interface AnswerBox {
snippet: string
snippetHighlighted?: string[]
title: string
link: string
date?: string
position?: number
}
export interface SiteLink {
title: string
link: string
}
export interface PeopleAlsoAsk {
question: string
snippet: string
title: string
link: string
}
export interface RelatedSearch {
query: string
}
export interface SearchParameters {
q: string
gl: string
hl: string
num: number
autocorrect: boolean
page: number
type: string
engine: string
}
export interface TopStory {
title: string
link: string
source: string
date: string
imageUrl: string
}
export interface Image {
title: string
imageUrl: string
imageWidth: number
imageHeight: number
thumbnailUrl: string
thumbnailWidth: number
thumbnailHeight: number
source: string
domain: string
link: string
googleUrl: string
position: number
}
export interface Video {
title: string
link: string
snippet: string
date: string
imageUrl: string
position: number
}
export interface Place {
position: number
title: string
address: string
latitude: number
longitude: number
category: string
phoneNumber?: string
website: string
cid: string
rating?: number
ratingCount?: number
}
export interface News {
title: string
link: string
snippet: string
date: string
source: string
imageUrl: string
position: number
}
export interface Shopping {
title: string
source: string
link: string
price: string
imageUrl: string
delivery?: Record<string, string>
rating?: number
ratingCount?: number
offers?: string
productId?: string
position: number
}
export type ClientParams = Partial<Omit<SearchParams, 'q'>>
}
/**
* Lightweight wrapper around Serper for Google search.
*
* @see https://serper.dev
*/
export class SerperClient extends AIFunctionsProvider {
protected readonly ky: KyInstance
protected readonly apiKey: string
protected readonly apiBaseUrl: string
protected readonly params: serper.ClientParams
constructor({
apiKey = getEnv('SERPER_API_KEY'),
apiBaseUrl = serper.API_BASE_URL,
ky = defaultKy,
...params
}: {
apiKey?: string
apiBaseUrl?: string
ky?: KyInstance
} & serper.ClientParams = {}) {
assert(
apiKey,
'SerperClient missing required "apiKey" (defaults to "SERPER_API_KEY")'
)
super()
this.apiKey = apiKey
this.apiBaseUrl = apiBaseUrl
this.params = params
this.ky = ky.extend({
prefixUrl: this.apiBaseUrl,
headers: {
'x-api-key': this.apiKey
}
})
}
/**
* Uses Google Search to return the most relevant web pages for a given query. Useful for finding up-to-date news and information about any topic.
*/
@aiFunction({
name: 'serper_google_search',
description:
'Uses Google Search to return the most relevant web pages for a given query. Useful for finding up-to-date news and information about any topic.',
inputSchema: serper.GeneralSearchSchema.pick({
q: true,
num: true,
type: true
})
})
async search(queryOrOpts: string | serper.GeneralSearchParams) {
const searchType =
typeof queryOrOpts === 'string' ? 'search' : queryOrOpts.type || 'search'
return this._fetch<serper.SearchResponse>(
searchType,
typeof queryOrOpts === 'string' ? queryOrOpts : omit(queryOrOpts, 'type')
)
}
async searchImages(queryOrOpts: string | serper.SearchParams) {
return this._fetch<serper.SearchImagesResponse>('images', queryOrOpts)
}
async searchVideos(queryOrOpts: string | serper.SearchParams) {
return this._fetch<serper.SearchVideosResponse>('videos', queryOrOpts)
}
async searchPlaces(queryOrOpts: string | serper.SearchParams) {
return this._fetch<serper.SearchPlacesResponse>('places', queryOrOpts)
}
async searchNews(queryOrOpts: string | serper.SearchParams) {
return this._fetch<serper.SearchNewsResponse>('news', queryOrOpts)
}
async searchProducts(queryOrOpts: string | serper.SearchParams) {
return this._fetch<serper.SearchShoppingResponse>('shopping', queryOrOpts)
}
protected async _fetch<T extends serper.Response>(
endpoint: string,
queryOrOpts: string | serper.SearchParams
): Promise<T> {
const params = {
...this.params,
...(typeof queryOrOpts === 'string' ? { q: queryOrOpts } : queryOrOpts)
}
return this.ky.post(endpoint, { json: params }).json<T>()
}
}