pull/643/head^2
Travis Fischer 2024-06-02 19:12:22 -05:00
rodzic a5bf1736a9
commit 1a90a1e129
5 zmienionych plików z 67 dodań i 12 usunięć

Wyświetl plik

@ -29,7 +29,8 @@ async function main() {
const wikipedia = new WikipediaClient() const wikipedia = new WikipediaClient()
const res = await wikipedia.getPageSummary({ const res = await wikipedia.getPageSummary({
title: 'Naruto_(TV_series)' // title: 'Naruto_(TV_series)'
title: 'SpaceX'
}) })
console.log(JSON.stringify(res, null, 2)) console.log(JSON.stringify(res, null, 2))

Wyświetl plik

@ -1,6 +1,13 @@
import type * as types from './types.ts' import type * as types from './types.ts'
import { AIFunctionsProvider } from './fns.js' import { AIFunctionsProvider } from './fns.js'
/**
* A set of AI functions intended to make it easier to work with large sets of
* AI functions across different clients.
*
* This class mimics a built-in `Set<AIFunction>`, but with additional utility
* methods like `pick`, `omit`, and `map`.
*/
export class AIFunctionSet implements Iterable<types.AIFunction> { export class AIFunctionSet implements Iterable<types.AIFunction> {
protected readonly _map: Map<string, types.AIFunction> protected readonly _map: Map<string, types.AIFunction>

Wyświetl plik

@ -7,7 +7,7 @@ import { AIFunctionSet } from './ai-function-set.js'
import { createAIFunction } from './create-ai-function.js' import { createAIFunction } from './create-ai-function.js'
import { assert } from './utils.js' import { assert } from './utils.js'
export interface Invocable { export interface PrivateAIFunctionMetadata {
name: string name: string
description: string description: string
inputSchema: z.AnyZodObject inputSchema: z.AnyZodObject
@ -21,7 +21,8 @@ export abstract class AIFunctionsProvider {
if (!this._functions) { if (!this._functions) {
const metadata = this.constructor[Symbol.metadata] const metadata = this.constructor[Symbol.metadata]
assert(metadata) assert(metadata)
const invocables = (metadata?.invocables as Invocable[]) ?? [] const invocables =
(metadata?.invocables as PrivateAIFunctionMetadata[]) ?? []
// console.log({ metadata, invocables }) // console.log({ metadata, invocables })
const aiFunctions = invocables.map((invocable) => { const aiFunctions = invocables.map((invocable) => {
@ -71,7 +72,7 @@ export function aiFunction<
if (!context.metadata.invocables) { if (!context.metadata.invocables) {
context.metadata.invocables = [] context.metadata.invocables = []
} }
;(context.metadata.invocables as Invocable[]).push({ ;(context.metadata.invocables as PrivateAIFunctionMetadata[]).push({
name: name ?? methodName, name: name ?? methodName,
description, description,
inputSchema, inputSchema,

Wyświetl plik

@ -2,7 +2,7 @@ import defaultKy, { type KyInstance } from 'ky'
import { z } from 'zod' import { z } from 'zod'
import { aiFunction, AIFunctionsProvider } from '../fns.js' import { aiFunction, AIFunctionsProvider } from '../fns.js'
import { assert, getEnv } from '../utils.js' import { assert, getEnv, omit } from '../utils.js'
export namespace serper { export namespace serper {
export const BASE_URL = 'https://google.serper.dev' export const BASE_URL = 'https://google.serper.dev'
@ -13,10 +13,25 @@ export namespace serper {
gl: z.string().default('us').optional(), gl: z.string().default('us').optional(),
hl: z.string().default('en').optional(), hl: z.string().default('en').optional(),
page: z.number().int().positive().default(1).optional(), page: z.number().int().positive().default(1).optional(),
num: z.number().int().positive().default(10).optional() num: z
.number()
.int()
.positive()
.default(10)
.optional()
.describe('number of results to return')
}) })
export type SearchParams = z.infer<typeof SearchParamsSchema> 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 { export interface SearchResponse {
searchParameters: SearchParameters & { type: 'search' } searchParameters: SearchParameters & { type: 'search' }
organic: Organic[] organic: Organic[]
@ -233,12 +248,19 @@ export class SerperClient extends AIFunctionsProvider {
name: 'serper_google_search', name: 'serper_google_search',
description: description:
'Uses Google Search to return the most relevant web pages for a given query. Can also be used to find up-to-date news and information about many topics.', 'Uses Google Search to return the most relevant web pages for a given query. Can also be used to find up-to-date news and information about many topics.',
inputSchema: serper.SearchParamsSchema.pick({ inputSchema: serper.GeneralSearchSchema.pick({
q: true q: true,
num: true,
type: true
}) })
}) })
async search(queryOrOpts: string | serper.SearchParams) { async search(queryOrOpts: string | serper.GeneralSearchParams) {
return this._fetch<serper.SearchResponse>('search', queryOrOpts) 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) { async searchImages(queryOrOpts: string | serper.SearchParams) {

Wyświetl plik

@ -1,10 +1,12 @@
import defaultKy, { type KyInstance } from 'ky' import defaultKy, { type KyInstance } from 'ky'
import pThrottle from 'p-throttle' import pThrottle from 'p-throttle'
import { z } from 'zod'
import { aiFunction, AIFunctionsProvider } from '../fns.js'
import { assert, getEnv, throttleKy } from '../utils.js' import { assert, getEnv, throttleKy } from '../utils.js'
export namespace wikipedia { export namespace wikipedia {
// Only allow 200 requests per second // Only allow 200 requests per second by default.
export const throttle = pThrottle({ export const throttle = pThrottle({
limit: 200, limit: 200,
interval: 1000 interval: 1000
@ -94,7 +96,7 @@ export namespace wikipedia {
} }
} }
export class WikipediaClient { export class WikipediaClient extends AIFunctionsProvider {
readonly apiBaseUrl: string readonly apiBaseUrl: string
readonly apiUserAgent: string readonly apiUserAgent: string
readonly ky: KyInstance readonly ky: KyInstance
@ -114,6 +116,7 @@ export class WikipediaClient {
} = {}) { } = {}) {
assert(apiBaseUrl, 'WikipediaClient missing required "apiBaseUrl"') assert(apiBaseUrl, 'WikipediaClient missing required "apiBaseUrl"')
assert(apiUserAgent, 'WikipediaClient missing required "apiUserAgent"') assert(apiUserAgent, 'WikipediaClient missing required "apiUserAgent"')
super()
this.apiBaseUrl = apiBaseUrl this.apiBaseUrl = apiBaseUrl
this.apiUserAgent = apiUserAgent this.apiUserAgent = apiUserAgent
@ -127,6 +130,13 @@ export class WikipediaClient {
}) })
} }
@aiFunction({
name: 'wikipedia_search',
description: 'Searches Wikipedia for pages matching the given query.',
inputSchema: z.object({
query: z.string().describe('Search query')
})
})
async search({ query, ...opts }: wikipedia.SearchOptions) { async search({ query, ...opts }: wikipedia.SearchOptions) {
return ( return (
// https://www.mediawiki.org/wiki/API:REST_API // https://www.mediawiki.org/wiki/API:REST_API
@ -138,12 +148,26 @@ export class WikipediaClient {
) )
} }
@aiFunction({
name: 'wikipedia_get_page_summary',
description: 'Gets a summary of the given Wikipedia page.',
inputSchema: z.object({
title: z.string().describe('Wikipedia page title'),
acceptLanguage: z
.string()
.optional()
.default('en-us')
.describe('Locale code for the language to use.')
})
})
async getPageSummary({ async getPageSummary({
title, title,
acceptLanguage = 'en-us', acceptLanguage = 'en-us',
redirect = true, redirect = true,
...opts ...opts
}: wikipedia.PageSummaryOptions) { }: wikipedia.PageSummaryOptions) {
title = title.trim().replaceAll(' ', '_')
// https://en.wikipedia.org/api/rest_v1/ // https://en.wikipedia.org/api/rest_v1/
return this.ky return this.ky
.get(`page/summary/${title}`, { .get(`page/summary/${title}`, {