UNPKG

@draftor/tools

Version:

A simple TypeScript/Javascript functions to openai tool call format

159 lines (133 loc) 4.46 kB
import * as doctrine from 'doctrine'; import { IParam, ITools, ITool, IFunctionDescription, IToolCall, IFunctionCall } from './types'; export class Tools implements ITools { private queue: Array<{ toolCall: IToolCall | string; args?: object }> = []; private isProcessing = false; public funcs: Function[] | undefined; constructor(funcs: Function[]) { this.funcs = funcs; } private extractFirstJsDocComment(func: Function) { const jsDocRegex = /\/\*\*([\s\S]*?)\*\//; const match = jsDocRegex.exec(func.toString()); return match ? match[0] : null; } private transformDoctrineToOpenAITool( doctrineOutput: any, func: Function ): ITool { const { description, tags } = doctrineOutput; const params: IParam = { type: 'object', properties: {}, }; let returnType = 'void'; let returnDescription = ''; tags.forEach((tag: any) => { if (tag.title === 'param') { const param = tag?.name as string; const type = tag?.type?.name || 'any'; const desc = tag?.description || ''; console.log(typeof desc); params.properties[param] = { type, description: desc, }; } if (tag.title === 'returns') { returnType = tag.type?.name || 'any'; returnDescription = tag.description || ''; } }); const funcDesc: IFunctionDescription = { name: func.name, description: description?.trim() || 'Description unavailable.', parameters: params, }; const tool: ITool = { type: 'function', function: funcDesc, }; return tool; // } toOpenAI(type?: string): ITool[] | string { if (this.funcs && this.funcs?.length !== 0) { // reducer const processedFunctions = this.funcs.reduce((acc, func) => { const comment = this.extractFirstJsDocComment(func); const doc = doctrine.parse(comment || '', { unwrap: true }); const transformed = this.transformDoctrineToOpenAITool( doc, func ) as unknown as ITool; acc.push(transformed); return acc; }, [] as ITool[]); return type == 'string' ? JSON.stringify(processedFunctions) : processedFunctions; } return ''; } /** * Processes the queue of function calls * @returns Array of results from executed functions */ private async processQueue(): Promise<any[]> { this.isProcessing = true; const results: any[] = []; while (this.queue.length > 0) { const { toolCall, args } = this.queue.shift()!; try { const result = this.exec(toolCall, args); results.push(result); } catch (error) { console.error('Error executing function:', error); results.push(null); } } this.isProcessing = false; return results; } /** * Adds function calls to queue and processes them * @param toolCalls Array of tool calls or function names * @param args Optional arguments for each call */ async executeAll(toolCalls: Array<IToolCall | string>, args?: object[]): Promise<any[]> { // Add calls to queue toolCalls.forEach((call, index) => { this.queue.push({ toolCall: call, args: args?.[index] }); }); // Process queue if not already processing if (!this.isProcessing) { return this.processQueue(); } return []; } exec(toolCall: IToolCall | string, args?: object): any { if (typeof toolCall !== 'string') { const { function: func } = toolCall; const { name, arguments: args } = func; try { const callArgs = JSON.parse(args); const callFunc = this.funcs?.filter((e) => e.name == name)[0] as unknown as Function; const x = Object.keys(callArgs).map(e => callArgs[e]); return callFunc(...x); } catch (e) { console.log(e) } } else { try { const callFunc = this.funcs?.filter((e) => e.name == toolCall)[0] as unknown as Function; const y = args ?? {}; const x = Object.keys(y).map((key: string) => (y as Record<string, any>)[key]); return callFunc(...x); } catch (e) { console.log(e) } } } }