From 20997cbe9eab81b307a51c141afe18866c917772 Mon Sep 17 00:00:00 2001 From: thucpn <thucsh2@gmail.com> Date: Mon, 17 Mar 2025 10:47:46 +0700 Subject: [PATCH] tool factory --- packages/tools/src/settings.ts | 22 ---- packages/tools/src/tools/code-generator.ts | 75 +++++++------ .../tools/src/tools/document-generator.ts | 5 +- packages/tools/src/tools/duckduckgo.ts | 74 ++++++------- packages/tools/src/tools/img-gen.ts | 102 ++++++++++++------ packages/tools/src/tools/interpreter.ts | 15 ++- packages/tools/src/tools/weather.ts | 62 +++++------ 7 files changed, 195 insertions(+), 160 deletions(-) delete mode 100644 packages/tools/src/settings.ts diff --git a/packages/tools/src/settings.ts b/packages/tools/src/settings.ts deleted file mode 100644 index d0d0080d4..000000000 --- a/packages/tools/src/settings.ts +++ /dev/null @@ -1,22 +0,0 @@ -class GlobalToolSettings { - private _outputDir: string = "output/tools"; - private _fileServerURLPrefix: string | undefined; - - set outputDir(outputDir: string) { - this._outputDir = outputDir; - } - - get outputDir() { - return this._outputDir; - } - - set fileServerURLPrefix(fileServerURLPrefix: string | undefined) { - this._fileServerURLPrefix = fileServerURLPrefix; - } - - get fileServerURLPrefix() { - return this._fileServerURLPrefix; - } -} - -export const ToolSettings = new GlobalToolSettings(); diff --git a/packages/tools/src/tools/code-generator.ts b/packages/tools/src/tools/code-generator.ts index e98a7e762..8e0302895 100644 --- a/packages/tools/src/tools/code-generator.ts +++ b/packages/tools/src/tools/code-generator.ts @@ -77,39 +77,44 @@ async function generateArtifact( } } -export const codeGenerator = tool({ - name: "artifact", - description: - "Generate a code artifact based on the input. Don't call this tool if the user has not asked for code generation. E.g. if the user asks to write a description or specification, don't call this tool.", - parameters: z.object({ - requirement: z - .string() - .describe("The description of the application you want to build."), - oldCode: z.string().optional().describe("The existing code to be modified"), - sandboxFiles: z - .array(z.string()) - .optional() - .describe( - "A list of sandbox file paths. Include these files if the code requires them.", - ), - }), - execute: async ({ - requirement, - oldCode, - sandboxFiles, - }): Promise<JSONValue> => { - try { - const artifact = await generateArtifact( - requirement, - oldCode, - sandboxFiles, // help the generated code use exact files - ); - if (sandboxFiles) { - artifact.files = sandboxFiles; +export const codeGenerator = () => { + return tool({ + name: "artifact", + description: + "Generate a code artifact based on the input. Don't call this tool if the user has not asked for code generation. E.g. if the user asks to write a description or specification, don't call this tool.", + parameters: z.object({ + requirement: z + .string() + .describe("The description of the application you want to build."), + oldCode: z + .string() + .optional() + .describe("The existing code to be modified"), + sandboxFiles: z + .array(z.string()) + .optional() + .describe( + "A list of sandbox file paths. Include these files if the code requires them.", + ), + }), + execute: async ({ + requirement, + oldCode, + sandboxFiles, + }): Promise<JSONValue> => { + try { + const artifact = await generateArtifact( + requirement, + oldCode, + sandboxFiles, // help the generated code use exact files + ); + if (sandboxFiles) { + artifact.files = sandboxFiles; + } + return artifact as JSONValue; + } catch (error) { + return { isError: true }; } - return artifact as JSONValue; - } catch (error) { - return { isError: true }; - } - }, -}); + }, + }); +}; diff --git a/packages/tools/src/tools/document-generator.ts b/packages/tools/src/tools/document-generator.ts index 9a4f74130..cf00db07d 100644 --- a/packages/tools/src/tools/document-generator.ts +++ b/packages/tools/src/tools/document-generator.ts @@ -137,6 +137,5 @@ export class DocumentGenerator implements BaseTool<DocumentParameter> { } } -export function getTools(): BaseTool[] { - return [new DocumentGenerator({})]; -} +export const documentGenerator = (params?: DocumentGeneratorParams) => + new DocumentGenerator(params ?? {}); diff --git a/packages/tools/src/tools/duckduckgo.ts b/packages/tools/src/tools/duckduckgo.ts index 4f2a3e24a..628a3c702 100644 --- a/packages/tools/src/tools/duckduckgo.ts +++ b/packages/tools/src/tools/duckduckgo.ts @@ -8,39 +8,41 @@ export type DuckDuckGoToolOutput = { url: string; }[]; -export const duckduckgo = tool({ - name: "duckduckgo_search", - description: - "Use this function to search for information (only text) in the internet using DuckDuckGo.", - parameters: z.object({ - query: z.string().describe("The query to search in DuckDuckGo."), - region: z - .string() - .optional() - .describe( - "Optional, The region to be used for the search in [country-language] convention, ex us-en, uk-en, ru-ru, etc...", - ), - maxResults: z - .number() - .default(10) - .optional() - .describe( - "Optional, The maximum number of results to be returned. Default is 10.", - ), - }), - execute: async ({ - query, - region, - maxResults = 10, - }): Promise<DuckDuckGoToolOutput> => { - const options = region ? { region } : {}; - const searchResults = await search(query, options); - return searchResults.results.slice(0, maxResults).map((result) => { - return { - title: result.title, - description: result.description, - url: result.url, - }; - }); - }, -}); +export const duckduckgo = () => { + return tool({ + name: "duckduckgo_search", + description: + "Use this function to search for information (only text) in the internet using DuckDuckGo.", + parameters: z.object({ + query: z.string().describe("The query to search in DuckDuckGo."), + region: z + .string() + .optional() + .describe( + "Optional, The region to be used for the search in [country-language] convention, ex us-en, uk-en, ru-ru, etc...", + ), + maxResults: z + .number() + .default(10) + .optional() + .describe( + "Optional, The maximum number of results to be returned. Default is 10.", + ), + }), + execute: async ({ + query, + region, + maxResults = 10, + }): Promise<DuckDuckGoToolOutput> => { + const options = region ? { region } : {}; + const searchResults = await search(query, options); + return searchResults.results.slice(0, maxResults).map((result) => { + return { + title: result.title, + description: result.description, + url: result.url, + }; + }); + }, + }); +}; diff --git a/packages/tools/src/tools/img-gen.ts b/packages/tools/src/tools/img-gen.ts index 654fc6a51..2f7d32e35 100644 --- a/packages/tools/src/tools/img-gen.ts +++ b/packages/tools/src/tools/img-gen.ts @@ -13,6 +13,13 @@ export type ImgGeneratorToolOutput = { errorMessage?: string; }; +export type ImgGeneratorToolParams = { + outputFormat?: string; + outputDir?: string; + apiKey?: string; + fileServerURLPrefix?: string; +}; + // Constants const IMG_OUTPUT_FORMAT = "webp"; const IMG_OUTPUT_DIR = "output/tools"; @@ -33,7 +40,10 @@ function checkRequiredEnvVars() { } } -async function promptToImgBuffer(prompt: string): Promise<Buffer> { +async function promptToImgBuffer( + prompt: string, + apiKey: string, +): Promise<Buffer> { const form = new FormData(); form.append("prompt", prompt); form.append("output_format", IMG_OUTPUT_FORMAT); @@ -42,7 +52,7 @@ async function promptToImgBuffer(prompt: string): Promise<Buffer> { .post(IMG_GEN_API, { body: form as unknown as Buffer | Readable | string, headers: { - Authorization: `Bearer ${process.env.STABILITY_API_KEY}`, + Authorization: `Bearer ${apiKey}`, Accept: "image/*", }, }) @@ -51,43 +61,75 @@ async function promptToImgBuffer(prompt: string): Promise<Buffer> { return buffer; } -function saveImage(buffer: Buffer): string { - const filename = `${crypto.randomUUID()}.${IMG_OUTPUT_FORMAT}`; +function saveImage( + buffer: Buffer, + options: { + outputFormat?: string; + outputDir?: string; + fileServerURLPrefix?: string; + }, +): string { + const { + outputFormat = IMG_OUTPUT_FORMAT, + outputDir = IMG_OUTPUT_DIR, + fileServerURLPrefix = process.env.FILESERVER_URL_PREFIX, + } = options; + const filename = `${crypto.randomUUID()}.${outputFormat}`; // Create output directory if it doesn't exist - if (!fs.existsSync(IMG_OUTPUT_DIR)) { - fs.mkdirSync(IMG_OUTPUT_DIR, { recursive: true }); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); } - const outputPath = path.join(IMG_OUTPUT_DIR, filename); + const outputPath = path.join(outputDir, filename); fs.writeFileSync(outputPath, buffer); - const url = `${process.env.FILESERVER_URL_PREFIX}/${IMG_OUTPUT_DIR}/${filename}`; + const url = `${fileServerURLPrefix}/${outputDir}/${filename}`; console.log(`Saved image to ${outputPath}.\nURL: ${url}`); return url; } -export const imageGenerator = tool({ - name: "image_generator", - description: "Use this function to generate an image based on the prompt.", - parameters: z.object({ - prompt: z.string().describe("The prompt to generate the image"), - }), - execute: async ({ prompt }): Promise<ImgGeneratorToolOutput> => { - // Check required environment variables - checkRequiredEnvVars(); +export const imageGenerator = (params?: ImgGeneratorToolParams) => { + return tool({ + name: "image_generator", + description: "Use this function to generate an image based on the prompt.", + parameters: z.object({ + prompt: z.string().describe("The prompt to generate the image"), + }), + execute: async ({ prompt }): Promise<ImgGeneratorToolOutput> => { + const outputFormat = params?.outputFormat ?? IMG_OUTPUT_FORMAT; + const outputDir = params?.outputDir ?? IMG_OUTPUT_DIR; + const apiKey = params?.apiKey ?? process.env.STABILITY_API_KEY; + const fileServerURLPrefix = + params?.fileServerURLPrefix ?? process.env.FILESERVER_URL_PREFIX; - try { - const buffer = await promptToImgBuffer(prompt); - const imageUrl = saveImage(buffer); - return { isSuccess: true, imageUrl }; - } catch (error) { - console.error(error); - return { - isSuccess: false, - errorMessage: "Failed to generate image. Please try again.", - }; - } - }, -}); + if (!apiKey) { + throw new Error( + "STABILITY_API_KEY key is required to run image generator. Get it here: https://platform.stability.ai/account/keys", + ); + } + if (!fileServerURLPrefix) { + throw new Error( + "FILESERVER_URL_PREFIX is required to display file output after generation", + ); + } + + try { + const buffer = await promptToImgBuffer(prompt, apiKey); + const imageUrl = saveImage(buffer, { + outputFormat, + outputDir, + fileServerURLPrefix, + }); + return { isSuccess: true, imageUrl }; + } catch (error) { + console.error(error); + return { + isSuccess: false, + errorMessage: "Failed to generate image. Please try again.", + }; + } + }, + }); +}; diff --git a/packages/tools/src/tools/interpreter.ts b/packages/tools/src/tools/interpreter.ts index 20db2df49..354683f1b 100644 --- a/packages/tools/src/tools/interpreter.ts +++ b/packages/tools/src/tools/interpreter.ts @@ -14,8 +14,10 @@ export type InterpreterParameter = { export type InterpreterToolParams = { metadata?: ToolMetadata<JSONSchemaType<InterpreterParameter>>; - apiKey?: string; - fileServerURLPrefix?: string; + apiKey?: string | undefined; + fileServerURLPrefix?: string | undefined; + outputDir?: string | undefined; + uploadedFilesDir?: string | undefined; }; export type InterpreterToolOutput = { @@ -78,8 +80,8 @@ You have a maximum of 3 retries to get the code to run successfully. }; export class InterpreterTool implements BaseTool<InterpreterParameter> { - private readonly outputDir = "output/tools"; - private readonly uploadedFilesDir = "output/uploaded"; + private outputDir: string; + private uploadedFilesDir: string; private apiKey: string; private fileServerURLPrefix: string; metadata: ToolMetadata<JSONSchemaType<InterpreterParameter>>; @@ -90,6 +92,8 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> { this.apiKey = params?.apiKey || process.env.E2B_API_KEY!; this.fileServerURLPrefix = params?.fileServerURLPrefix || process.env.FILESERVER_URL_PREFIX!; + this.outputDir = params?.outputDir || "output/tools"; + this.uploadedFilesDir = params?.uploadedFilesDir || "output/uploaded"; if (!this.apiKey) { throw new Error( @@ -248,3 +252,6 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> { return `${this.fileServerURLPrefix}/${this.outputDir}/${filename}`; } } + +export const interpreter = (params?: InterpreterToolParams) => + new InterpreterTool(params); diff --git a/packages/tools/src/tools/weather.ts b/packages/tools/src/tools/weather.ts index 6f3206b12..62be492d4 100644 --- a/packages/tools/src/tools/weather.ts +++ b/packages/tools/src/tools/weather.ts @@ -41,36 +41,38 @@ export type WeatherToolOutput = { }; }; -export const weather = tool({ - name: "weather", - description: ` - Use this function to get the weather of any given location. - Note that the weather code should follow WMO Weather interpretation codes (WW): - 0: Clear sky - 1, 2, 3: Mainly clear, partly cloudy, and overcast - 45, 48: Fog and depositing rime fog - 51, 53, 55: Drizzle: Light, moderate, and dense intensity - 56, 57: Freezing Drizzle: Light and dense intensity - 61, 63, 65: Rain: Slight, moderate and heavy intensity - 66, 67: Freezing Rain: Light and heavy intensity - 71, 73, 75: Snow fall: Slight, moderate, and heavy intensity - 77: Snow grains - 80, 81, 82: Rain showers: Slight, moderate, and violent - 85, 86: Snow showers slight and heavy - 95: Thunderstorm: Slight or moderate - 96, 99: Thunderstorm with slight and heavy hail - `, - parameters: z.object({ - location: z.string().describe("The location to get the weather"), - }), - execute: async ({ - location, - }: { - location: string; - }): Promise<WeatherToolOutput> => { - return await getWeatherByLocation(location); - }, -}); +export const weather = () => { + return tool({ + name: "weather", + description: ` + Use this function to get the weather of any given location. + Note that the weather code should follow WMO Weather interpretation codes (WW): + 0: Clear sky + 1, 2, 3: Mainly clear, partly cloudy, and overcast + 45, 48: Fog and depositing rime fog + 51, 53, 55: Drizzle: Light, moderate, and dense intensity + 56, 57: Freezing Drizzle: Light and dense intensity + 61, 63, 65: Rain: Slight, moderate and heavy intensity + 66, 67: Freezing Rain: Light and heavy intensity + 71, 73, 75: Snow fall: Slight, moderate, and heavy intensity + 77: Snow grains + 80, 81, 82: Rain showers: Slight, moderate, and violent + 85, 86: Snow showers slight and heavy + 95: Thunderstorm: Slight or moderate + 96, 99: Thunderstorm with slight and heavy hail + `, + parameters: z.object({ + location: z.string().describe("The location to get the weather"), + }), + execute: async ({ + location, + }: { + location: string; + }): Promise<WeatherToolOutput> => { + return await getWeatherByLocation(location); + }, + }); +}; async function getWeatherByLocation(location: string) { const { latitude, longitude } = await getGeoLocation(location); -- GitLab