diff options
author | A.J. Shulman <Shulman.aj@gmail.com> | 2024-10-17 10:41:49 -0400 |
---|---|---|
committer | A.J. Shulman <Shulman.aj@gmail.com> | 2024-10-17 10:41:49 -0400 |
commit | 80d86bd5ae3e1d3dc70e7636f72a872a5fb2f01d (patch) | |
tree | 0eaea49f596bd16720f05a6535958ab8270673c8 | |
parent | 596502c232ea6b6b88c3c58486e139074ea056ff (diff) |
Implemented strict typechecking for tools, specifically tool inputs
-rw-r--r-- | src/client/views/nodes/chatbot/agentsystem/Agent.ts | 3 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/BaseTool.ts | 64 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/CalculateTool.ts | 32 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/CreateCSVTool.ts | 40 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/CreateCollectionTool.ts | 37 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/DataAnalysisTool.ts | 31 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/GetDocsTool.ts | 38 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/NoTool.ts | 53 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/RAGTool.ts | 30 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/SearchTool.ts | 39 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/ToolTypes.ts | 76 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts | 104 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/WikipediaTool.ts | 32 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/types/types.ts | 45 |
14 files changed, 339 insertions, 285 deletions
diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts index 0747ddd60..ba5868207 100644 --- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts +++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts @@ -14,6 +14,7 @@ import { WebsiteInfoScraperTool } from '../tools/WebsiteInfoScraperTool'; import { AgentMessage, AssistantMessage, Observation, PROCESSING_TYPE, ProcessingInfo, Tool } from '../types/types'; import { Vectorstore } from '../vectorstore/Vectorstore'; import { getReactPrompt } from './prompts'; +import { BaseTool } from '../tools/BaseTool'; dotenv.config(); @@ -35,7 +36,7 @@ export class Agent { private processingNumber: number = 0; private processingInfo: ProcessingInfo[] = []; private streamedAnswerParser: StreamedAnswerParser = new StreamedAnswerParser(); - private tools: Record<string, Tool<ToolArgument>>; + private tools: Record<string, BaseTool>; /** * The constructor initializes the agent with the vector store and toolset, and sets up the OpenAI client. diff --git a/src/client/views/nodes/chatbot/tools/BaseTool.ts b/src/client/views/nodes/chatbot/tools/BaseTool.ts index 4f8f81bcb..e01296ac4 100644 --- a/src/client/views/nodes/chatbot/tools/BaseTool.ts +++ b/src/client/views/nodes/chatbot/tools/BaseTool.ts @@ -1,58 +1,76 @@ -import { Tool, ParameterArray, ParametersType, Observation, ParamConfig } from '../types/types'; +import { Tool, Parameter, ParametersType, Observation } from '../types/types'; /** * @file BaseTool.ts - * @description This file defines the abstract BaseTool class, which serves as a blueprint + * @description This file defines the abstract `BaseTool` class, which serves as a blueprint * for tool implementations in the AI assistant system. Each tool has a name, description, - * parameters, and citation rules. The BaseTool class provides a structure for executing actions + * parameters, and citation rules. The `BaseTool` class provides a structure for executing actions * and retrieving action rules for use within the assistant's workflow. */ -export abstract class BaseTool implements Tool<ParamConfig[]> { +/** + * The `BaseTool` class is an abstract class that implements the `Tool` interface. + * It is generic over a type parameter `P`, which extends `readonly Parameter[]`. + * This means `P` is an array of `Parameter` objects that cannot be modified (immutable). + */ +export abstract class BaseTool<P extends readonly Parameter[]> implements Tool<P> { + // The name of the tool (e.g., "calculate", "searchTool") name: string; + // A description of the tool's functionality description: string; - parameterRules: ParameterArray; // Still using ParameterArray + // An array of parameter definitions for the tool + parameterRules: P; + // Guidelines for how to handle citations when using the tool citationRules: string; + // A brief summary of the tool's purpose briefSummary: string; - paramConfig: ParamConfig[]; - constructor( - name: string, - description: string, - parameterRules: ParameterArray, // Allow tuple types for parameters - citationRules: string, - briefSummary: string - ) { + /** + * Constructs a new `BaseTool` instance. + * @param name - The name of the tool. + * @param description - A detailed description of what the tool does. + * @param parameterRules - An array of parameter definitions (`Parameter[]`). + * @param citationRules - Rules or guidelines for citations. + * @param briefSummary - A short summary of the tool. + */ + constructor(name: string, description: string, parameterRules: P, citationRules: string, briefSummary: string) { this.name = name; this.description = description; this.parameterRules = parameterRules; this.citationRules = citationRules; this.briefSummary = briefSummary; - this.paramConfig = this.parameterRules.map(param => ({ - name: param.name, - type: param.type, - })); // Convert ParameterArray to ParamConfig[] } - // Abstract execute method with dynamic types based on parameters - abstract execute(args: ParametersType<ParamConfig[]>): Promise<Observation[]>; + /** + * The `execute` method is abstract and must be implemented by subclasses. + * It defines the action the tool performs when executed. + * @param args - The arguments for the tool's execution, whose types are inferred from `ParametersType<P>`. + * @returns A promise that resolves to an array of `Observation` objects. + */ + abstract execute(args: ParametersType<P>): Promise<Observation[]>; - // Implement the getActionRule method + /** + * Generates an action rule object that describes the tool's usage. + * This is useful for dynamically generating documentation or for tools that need to expose their parameters at runtime. + * @returns An object containing the tool's name, description, and parameter definitions. + */ getActionRule(): Record<string, unknown> { return { tool: this.name, description: this.description, parameters: this.parameterRules.reduce( (acc, param) => { + // Build an object for each parameter without the 'name' property, since it's used as the key acc[param.name] = { type: param.type, description: param.description, required: param.required, - max_inputs: param.max_inputs, - }; + // Conditionally include 'max_inputs' only if it is defined + ...(param.max_inputs !== undefined && { max_inputs: param.max_inputs }), + } as Omit<P[number], 'name'>; // Type assertion to exclude the 'name' property return acc; }, - {} as Record<string, Omit<ParameterArray[number], 'name'>> + {} as Record<string, Omit<P[number], 'name'>> // Initialize the accumulator as an empty object ), }; } diff --git a/src/client/views/nodes/chatbot/tools/CalculateTool.ts b/src/client/views/nodes/chatbot/tools/CalculateTool.ts index 050e6f708..e96c9a98a 100644 --- a/src/client/views/nodes/chatbot/tools/CalculateTool.ts +++ b/src/client/views/nodes/chatbot/tools/CalculateTool.ts @@ -1,28 +1,32 @@ -import { Observation, ParametersType } from '../types/types'; +import { Observation } from '../types/types'; +import { ParametersType } from './ToolTypes'; import { BaseTool } from './BaseTool'; -export class CalculateTool extends BaseTool { +const calculateToolParams = [ + { + name: 'expression', + type: 'string', + description: 'The mathematical expression to evaluate', + required: true, + }, +] as const; + +type CalculateToolParamsType = typeof calculateToolParams; + +export class CalculateTool extends BaseTool<CalculateToolParamsType> { constructor() { super( 'calculate', 'Perform a calculation', - [ - { - name: 'expression', - type: 'string', - description: 'The mathematical expression to evaluate', - required: true, - }, - ], + calculateToolParams, // Use the reusable param config here 'Provide a mathematical expression to calculate that would work with JavaScript eval().', 'Runs a calculation and returns the number - uses JavaScript so be sure to use floating point syntax if necessary' ); } - async execute(args: { expression: string }): Promise<Observation[]> { - // Since the 'expression' parameter is typed as 'string', TypeScript will ensure 'args.expression' is a string - const result = eval(args.expression); // Be cautious with eval(), as it can be dangerous. Consider using a safer alternative if possible. - + async execute(args: ParametersType<CalculateToolParamsType>): Promise<Observation[]> { + // TypeScript will ensure 'args.expression' is a string based on the param config + const result = eval(args.expression); // Be cautious with eval(), as it can be dangerous. Consider using a safer alternative. return [{ type: 'text', text: result.toString() }]; } } diff --git a/src/client/views/nodes/chatbot/tools/CreateCSVTool.ts b/src/client/views/nodes/chatbot/tools/CreateCSVTool.ts index fff57c6d4..b321d98ba 100644 --- a/src/client/views/nodes/chatbot/tools/CreateCSVTool.ts +++ b/src/client/views/nodes/chatbot/tools/CreateCSVTool.ts @@ -1,36 +1,46 @@ import { BaseTool } from './BaseTool'; import { Networking } from '../../../../Network'; import { Observation } from '../types/types'; +import { ParametersType } from './ToolTypes'; -export class CreateCSVTool extends BaseTool<{ csvData: { type: string; description: string; required: boolean }; filename: { type: string; description: string; required: boolean } }> { +const createCSVToolParams = [ + { + name: 'csvData', + type: 'string', + description: 'A string of comma-separated values representing the CSV data.', + required: true, + }, + { + name: 'filename', + type: 'string', + description: 'The base name of the CSV file to be created. Should end in ".csv".', + required: true, + }, +] as const; + +type CreateCSVToolParamsType = typeof createCSVToolParams; + +export class CreateCSVTool extends BaseTool<CreateCSVToolParamsType> { private _handleCSVResult: (url: string, filename: string, id: string, data: string) => void; constructor(handleCSVResult: (url: string, title: string, id: string, data: string) => void) { super( 'createCSV', 'Creates a CSV file from raw CSV data and saves it to the server', - { - csvData: { - type: 'string', - description: 'A string of comma-separated values representing the CSV data.', - required: true, - }, - filename: { - type: 'string', - description: 'The base name of the CSV file to be created. Should end in ".csv".', - required: true, - }, - }, + createCSVToolParams, 'Provide a CSV string and a filename to create a CSV file.', 'Creates a CSV file from the provided CSV string and saves it to the server with a unique identifier, returning the file URL and UUID.' ); this._handleCSVResult = handleCSVResult; } - async execute(args: { csvData: string; filename: string }): Promise<Observation[]> { + async execute(args: ParametersType<CreateCSVToolParamsType>): Promise<Observation[]> { try { console.log('Creating CSV file:', args.filename, ' with data:', args.csvData); - const { fileUrl, id } = await Networking.PostToServer('/createCSV', { filename: args.filename, data: args.csvData }); + const { fileUrl, id } = await Networking.PostToServer('/createCSV', { + filename: args.filename, + data: args.csvData, + }); this._handleCSVResult(fileUrl, args.filename, id, args.csvData); diff --git a/src/client/views/nodes/chatbot/tools/CreateCollectionTool.ts b/src/client/views/nodes/chatbot/tools/CreateCollectionTool.ts deleted file mode 100644 index fb0d9ca4c..000000000 --- a/src/client/views/nodes/chatbot/tools/CreateCollectionTool.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { DocCast } from '../../../../../fields/Types'; -import { DocServer } from '../../../../DocServer'; -import { Docs } from '../../../../documents/Documents'; -import { DocumentView } from '../../DocumentView'; -import { OpenWhere } from '../../OpenWhere'; -import { Observation } from '../types/types'; -import { BaseTool } from './BaseTool'; - -export class GetDocsContentTool extends BaseTool<{ title: string; document_ids: string[] }> { - private _docView: DocumentView; - constructor(docView: DocumentView) { - super( - 'retrieveDocs', - 'Retrieves the contents of all Documents that the user is interacting with in Dash ', - { - title: { - type: 'string', - description: 'the title of the collection that you will be making', - required: 'true', - max_inputs: '1', - }, - }, - 'Provide a mathematical expression to calculate that would work with JavaScript eval().', - 'Runs a calculation and returns the number - uses JavaScript so be sure to use floating point syntax if necessary' - ); - this._docView = docView; - } - - async execute(args: { title: string; document_ids: string[] }): Promise<Observation[]> { - // Note: Using eval() can be dangerous. Consider using a safer alternative. - const docs = args.document_ids.map(doc_id => DocCast(DocServer.GetCachedRefField(doc_id))); - const collection = Docs.Create.FreeformDocument(docs, { title: args.title }); - this._docView._props.addDocTab(collection, OpenWhere.addRight); //in future, create popup prompting user where to add - return [{ type: 'text', text: 'Collection created in Dash called ' + args.title }]; - } -} -//export function create_collection(docView: DocumentView, document_ids: string[], title: string): string {} diff --git a/src/client/views/nodes/chatbot/tools/DataAnalysisTool.ts b/src/client/views/nodes/chatbot/tools/DataAnalysisTool.ts index b97576095..d9b75219d 100644 --- a/src/client/views/nodes/chatbot/tools/DataAnalysisTool.ts +++ b/src/client/views/nodes/chatbot/tools/DataAnalysisTool.ts @@ -1,20 +1,27 @@ import { Observation } from '../types/types'; +import { ParametersType } from './ToolTypes'; import { BaseTool } from './BaseTool'; -export class DataAnalysisTool extends BaseTool<{ csv_file_name: { type: string | string[]; description: string; required: boolean } }> { +const dataAnalysisToolParams = [ + { + name: 'csv_file_names', + type: 'string[]', + description: 'List of names of the CSV files to analyze', + required: true, + max_inputs: 3, + }, +] as const; + +type DataAnalysisToolParamsType = typeof dataAnalysisToolParams; + +export class DataAnalysisTool extends BaseTool<DataAnalysisToolParamsType> { private csv_files_function: () => { filename: string; id: string; text: string }[]; constructor(csv_files: () => { filename: string; id: string; text: string }[]) { super( 'dataAnalysis', - 'Analyzes, and provides insights, from one or more CSV files', - { - csv_file_name: { - type: 'string', - description: 'Name(s) of the CSV file(s) to analyze', - required: true, - }, - }, + 'Analyzes and provides insights from one or more CSV files', + dataAnalysisToolParams, 'Provide the name(s) of up to 3 CSV files to analyze based on the user query and whichever available CSV files may be relevant.', 'Provides the full CSV file text for your analysis based on the user query and the available CSV file(s).' ); @@ -33,8 +40,8 @@ export class DataAnalysisTool extends BaseTool<{ csv_file_name: { type: string | return file?.id; } - async execute(args: { csv_file_name: string | string[] }): Promise<Observation[]> { - const filenames = Array.isArray(args.csv_file_name) ? args.csv_file_name : [args.csv_file_name]; + async execute(args: ParametersType<DataAnalysisToolParamsType>): Promise<Observation[]> { + const filenames = args.csv_file_names; const results: Observation[] = []; for (const filename of filenames) { @@ -44,7 +51,7 @@ export class DataAnalysisTool extends BaseTool<{ csv_file_name: { type: string | if (fileContent && fileID) { results.push({ type: 'text', - text: `<chunk chunk_id=${fileID} chunk_type=csv>${fileContent}</chunk>`, + text: `<chunk chunk_id="${fileID}" chunk_type="csv">${fileContent}</chunk>`, }); } else { results.push({ diff --git a/src/client/views/nodes/chatbot/tools/GetDocsTool.ts b/src/client/views/nodes/chatbot/tools/GetDocsTool.ts index b6e549d93..26756522c 100644 --- a/src/client/views/nodes/chatbot/tools/GetDocsTool.ts +++ b/src/client/views/nodes/chatbot/tools/GetDocsTool.ts @@ -1,37 +1,45 @@ import { Observation } from '../types/types'; +import { ParametersType } from './ToolTypes'; import { BaseTool } from './BaseTool'; import { DocServer } from '../../../../DocServer'; import { Docs } from '../../../../documents/Documents'; import { DocumentView } from '../../DocumentView'; import { OpenWhere } from '../../OpenWhere'; +import { DocCast } from '../../../../../fields/Types'; -export class GetDocsTool extends BaseTool<{ title: { type: string; description: string; required: boolean }; document_ids: { type: string; description: string; required: boolean } }> { +const getDocsToolParams = [ + { + name: 'title', + type: 'string', + description: 'Title of the collection being created from retrieved documents', + required: true, + }, + { + name: 'document_ids', + type: 'string[]', + description: 'List of document IDs to retrieve', + required: true, + }, +] as const; + +type GetDocsToolParamsType = typeof getDocsToolParams; + +export class GetDocsTool extends BaseTool<GetDocsToolParamsType> { private _docView: DocumentView; constructor(docView: DocumentView) { super( 'retrieveDocs', 'Retrieves the contents of all Documents that the user is interacting with in Dash', - { - title: { - type: 'string', - description: 'Title of the collection being created from retrieved documents', - required: true, - }, - document_ids: { - type: 'string[]', - description: 'List of document IDs to retrieve', - required: true, - }, - }, + getDocsToolParams, 'No need to provide anything. Just run the tool and it will retrieve the contents of all Documents that the user is interacting with in Dash.', 'Returns the documents in Dash in JSON form.' ); this._docView = docView; } - async execute(args: { title: string; document_ids: string[] }): Promise<Observation[]> { - const docs = args.document_ids.map(doc_id => DocServer.GetCachedRefField(doc_id)); + async execute(args: ParametersType<GetDocsToolParamsType>): Promise<Observation[]> { + const docs = args.document_ids.map(doc_id => DocCast(DocServer.GetCachedRefField(doc_id))); const collection = Docs.Create.FreeformDocument(docs, { title: args.title }); this._docView._props.addDocTab(collection, OpenWhere.addRight); return [{ type: 'text', text: `Collection created in Dash called ${args.title}` }]; diff --git a/src/client/views/nodes/chatbot/tools/NoTool.ts b/src/client/views/nodes/chatbot/tools/NoTool.ts index 103abcdbe..a92e3fa23 100644 --- a/src/client/views/nodes/chatbot/tools/NoTool.ts +++ b/src/client/views/nodes/chatbot/tools/NoTool.ts @@ -1,51 +1,18 @@ -import { v4 as uuidv4 } from 'uuid'; -import { Networking } from '../../../../Network'; import { BaseTool } from './BaseTool'; import { Observation } from '../types/types'; +import { ParametersType } from './ToolTypes'; -export class SearchTool extends BaseTool<{ query: { type: string | string[]; description: string; required: boolean } }> { - private _addLinkedUrlDoc: (url: string, id: string) => void; - private _max_results: number; +const noToolParams = [] as const; - constructor(addLinkedUrlDoc: (url: string, id: string) => void, max_results: number = 5) { - super( - 'searchTool', - 'Search the web to find a wide range of websites related to a query or multiple queries', - { - query: { - type: 'string', - description: 'The search query or queries to use for finding websites', - required: true, - }, - }, - 'Provide up to 3 search queries to find a broad range of websites.', - 'Returns a list of websites and their overviews based on the search queries.' - ); - this._addLinkedUrlDoc = addLinkedUrlDoc; - this._max_results = max_results; - } - - async execute(args: { query: string | string[] }): Promise<Observation[]> { - const queries = Array.isArray(args.query) ? args.query : [args.query]; - const allResults = []; +type NoToolParamsType = typeof noToolParams; - for (const query of queries) { - try { - const { results } = await Networking.PostToServer('/getWebSearchResults', { query, max_results: this._max_results }); - const data = results.map((result: { url: string; snippet: string }) => { - const id = uuidv4(); - return { - type: 'text', - text: `<chunk chunk_id="${id}" chunk_type="text"><url>${result.url}</url><overview>${result.snippet}</overview></chunk>`, - }; - }); - allResults.push(...data); - } catch (error) { - console.log(error); - allResults.push({ type: 'text', text: `An error occurred while performing the web search for query: ${query}` }); - } - } +export class NoTool extends BaseTool<NoToolParamsType> { + constructor() { + super('noTool', 'A placeholder tool that performs no action', noToolParams, 'This tool does not require any input or perform any action.', 'Does nothing.'); + } - return allResults; + async execute(args: ParametersType<NoToolParamsType>): Promise<Observation[]> { + // Since there are no parameters, args will be an empty object + return [{ type: 'text', text: 'This tool does nothing.' }]; } } diff --git a/src/client/views/nodes/chatbot/tools/RAGTool.ts b/src/client/views/nodes/chatbot/tools/RAGTool.ts index 4babf540a..482069f36 100644 --- a/src/client/views/nodes/chatbot/tools/RAGTool.ts +++ b/src/client/views/nodes/chatbot/tools/RAGTool.ts @@ -1,21 +1,26 @@ -import { O } from '@fullcalendar/core/internal-common'; import { Networking } from '../../../../Network'; import { Observation, RAGChunk } from '../types/types'; +import { ParametersType } from './ToolTypes'; import { Vectorstore } from '../vectorstore/Vectorstore'; import { BaseTool } from './BaseTool'; -export class RAGTool extends BaseTool { +const ragToolParams = [ + { + name: 'hypothetical_document_chunk', + type: 'string', + description: "A detailed prompt representing an ideal chunk to embed and compare against document vectors to retrieve the most relevant content for answering the user's query.", + required: true, + }, +] as const; + +type RAGToolParamsType = typeof ragToolParams; + +export class RAGTool extends BaseTool<RAGToolParamsType> { constructor(private vectorstore: Vectorstore) { super( 'rag', 'Perform a RAG search on user documents', - { - hypothetical_document_chunk: { - type: 'string', - description: "A detailed prompt representing an ideal chunk to embed and compare against document vectors to retrieve the most relevant content for answering the user's query.", - required: 'true', - }, - }, + ragToolParams, ` When using the RAG tool, the structure must adhere to the format described in the ReAct prompt. Below are additional guidelines specifically for RAG-based responses: @@ -52,15 +57,14 @@ export class RAGTool extends BaseTool { </follow_up_questions> </answer> `, - `Performs a RAG (Retrieval-Augmented Generation) search on user documents and returns a set of document chunks (text or images) to provide a grounded response based on user documents.` ); } - async execute(args: { hypothetical_document_chunk: string }): Promise<Observation[]> { + async execute(args: ParametersType<RAGToolParamsType>): Promise<Observation[]> { const relevantChunks = await this.vectorstore.retrieve(args.hypothetical_document_chunk); - const formatted_chunks = await this.getFormattedChunks(relevantChunks); - return formatted_chunks; + const formattedChunks = await this.getFormattedChunks(relevantChunks); + return formattedChunks; } async getFormattedChunks(relevantChunks: RAGChunk[]): Promise<Observation[]> { diff --git a/src/client/views/nodes/chatbot/tools/SearchTool.ts b/src/client/views/nodes/chatbot/tools/SearchTool.ts index 103abcdbe..c5cf951e7 100644 --- a/src/client/views/nodes/chatbot/tools/SearchTool.ts +++ b/src/client/views/nodes/chatbot/tools/SearchTool.ts @@ -2,8 +2,21 @@ import { v4 as uuidv4 } from 'uuid'; import { Networking } from '../../../../Network'; import { BaseTool } from './BaseTool'; import { Observation } from '../types/types'; +import { ParametersType } from './ToolTypes'; -export class SearchTool extends BaseTool<{ query: { type: string | string[]; description: string; required: boolean } }> { +const searchToolParams = [ + { + name: 'query', + type: 'string[]', + description: 'The search query or queries to use for finding websites', + required: true, + max_inputs: 3, + }, +] as const; + +type SearchToolParamsType = typeof searchToolParams; + +export class SearchTool extends BaseTool<SearchToolParamsType> { private _addLinkedUrlDoc: (url: string, id: string) => void; private _max_results: number; @@ -11,13 +24,7 @@ export class SearchTool extends BaseTool<{ query: { type: string | string[]; des super( 'searchTool', 'Search the web to find a wide range of websites related to a query or multiple queries', - { - query: { - type: 'string', - description: 'The search query or queries to use for finding websites', - required: true, - }, - }, + searchToolParams, 'Provide up to 3 search queries to find a broad range of websites.', 'Returns a list of websites and their overviews based on the search queries.' ); @@ -25,13 +32,16 @@ export class SearchTool extends BaseTool<{ query: { type: string | string[]; des this._max_results = max_results; } - async execute(args: { query: string | string[] }): Promise<Observation[]> { - const queries = Array.isArray(args.query) ? args.query : [args.query]; - const allResults = []; + async execute(args: ParametersType<SearchToolParamsType>): Promise<Observation[]> { + const queries = args.query; + const allResults: Observation[] = []; for (const query of queries) { try { - const { results } = await Networking.PostToServer('/getWebSearchResults', { query, max_results: this._max_results }); + const { results } = await Networking.PostToServer('/getWebSearchResults', { + query, + max_results: this._max_results, + }); const data = results.map((result: { url: string; snippet: string }) => { const id = uuidv4(); return { @@ -42,7 +52,10 @@ export class SearchTool extends BaseTool<{ query: { type: string | string[]; des allResults.push(...data); } catch (error) { console.log(error); - allResults.push({ type: 'text', text: `An error occurred while performing the web search for query: ${query}` }); + allResults.push({ + type: 'text', + text: `An error occurred while performing the web search for query: ${query}`, + }); } } diff --git a/src/client/views/nodes/chatbot/tools/ToolTypes.ts b/src/client/views/nodes/chatbot/tools/ToolTypes.ts new file mode 100644 index 000000000..74a92bcf2 --- /dev/null +++ b/src/client/views/nodes/chatbot/tools/ToolTypes.ts @@ -0,0 +1,76 @@ +import { Observation } from '../types/types'; + +/** + * The `Tool` interface represents a generic tool in the system. + * It is generic over a type parameter `P`, which extends `readonly Parameter[]`. + * @template P - An array of `Parameter` objects defining the tool's parameters. + */ +export interface Tool<P extends readonly Parameter[]> { + // The name of the tool (e.g., "calculate", "searchTool") + name: string; + // A description of the tool's functionality + description: string; + // An array of parameter definitions for the tool + parameterRules: P; + // Guidelines for how to handle citations when using the tool + citationRules: string; + // A brief summary of the tool's purpose + briefSummary: string; + /** + * Executes the tool's main functionality. + * @param args - The arguments for execution, with types inferred from `ParametersType<P>`. + * @returns A promise that resolves to an array of `Observation` objects. + */ + execute: (args: ParametersType<P>) => Promise<Observation[]>; + /** + * Generates an action rule object that describes the tool's usage. + * @returns An object representing the tool's action rules. + */ + getActionRule: () => Record<string, unknown>; +} + +/** + * The `Parameter` type defines the structure of a parameter configuration. + */ +export type Parameter = { + // The type of the parameter; constrained to the types 'string', 'number', 'boolean', 'string[]', 'number[]' + type: 'string' | 'number' | 'boolean' | 'string[]' | 'number[]'; + // The name of the parameter + name: string; + // A description of the parameter + description: string; + // Indicates whether the parameter is required + required: boolean; + // (Optional) The maximum number of inputs (useful for array types) + max_inputs?: number; +}; + +/** + * A utility type that maps string representations of types to actual TypeScript types. + * This is used to convert the `type` field of a `Parameter` into a concrete TypeScript type. + */ +type TypeMap = { + string: string; + number: number; + boolean: boolean; + 'string[]': string[]; + 'number[]': number[]; +}; + +/** + * The `ParamType` type maps a `Parameter`'s `type` field to the corresponding TypeScript type. + * If the `type` field matches a key in `TypeMap`, it returns the associated type. + * Otherwise, it returns `unknown`. + * @template P - A `Parameter` object. + */ +export type ParamType<P extends Parameter> = P['type'] extends keyof TypeMap ? TypeMap[P['type']] : unknown; + +/** + * The `ParametersType` type transforms an array of `Parameter` objects into an object type + * where each key is the parameter's name, and the value is the corresponding TypeScript type. + * This is used to define the types of the arguments passed to the `execute` method of a tool. + * @template P - An array of `Parameter` objects. + */ +export type ParametersType<P extends readonly Parameter[]> = { + [K in P[number] as K['name']]: ParamType<K>; +}; diff --git a/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts b/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts index 8a4181b43..e91ebdad1 100644 --- a/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts +++ b/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts @@ -2,70 +2,76 @@ import { v4 as uuidv4 } from 'uuid'; import { Networking } from '../../../../Network'; import { BaseTool } from './BaseTool'; import { Observation } from '../types/types'; +import { ParametersType } from './ToolTypes'; -export class WebsiteInfoScraperTool extends BaseTool<{ url: string | string[] }> { +const websiteInfoScraperToolParams = [ + { + name: 'urls', + type: 'string[]', + description: 'The URLs of the websites to scrape', + required: true, + max_inputs: 3, + }, +] as const; + +type WebsiteInfoScraperToolParamsType = typeof websiteInfoScraperToolParams; + +export class WebsiteInfoScraperTool extends BaseTool<WebsiteInfoScraperToolParamsType> { private _addLinkedUrlDoc: (url: string, id: string) => void; constructor(addLinkedUrlDoc: (url: string, id: string) => void) { super( 'websiteInfoScraper', 'Scrape detailed information from specific websites relevant to the user query', - { - url: { - type: 'string', - description: 'The URL(s) of the website(s) to scrape', - required: true, - max_inputs: 3, - }, - }, + websiteInfoScraperToolParams, ` - Your task is to provide a comprehensive response to the user's prompt using the content scraped from relevant websites. Ensure you follow these guidelines for structuring your response: + Your task is to provide a comprehensive response to the user's prompt using the content scraped from relevant websites. Ensure you follow these guidelines for structuring your response: - 1. Grounded Text Tag Structure: - - Wrap all text derived from the scraped website(s) in <grounded_text> tags. - - **Do not include non-sourced information** in <grounded_text> tags. - - Use a single <grounded_text> tag for content derived from a single website. If citing multiple websites, create new <grounded_text> tags for each. - - Ensure each <grounded_text> tag has a citation index corresponding to the scraped URL. + 1. Grounded Text Tag Structure: + - Wrap all text derived from the scraped website(s) in <grounded_text> tags. + - **Do not include non-sourced information** in <grounded_text> tags. + - Use a single <grounded_text> tag for content derived from a single website. If citing multiple websites, create new <grounded_text> tags for each. + - Ensure each <grounded_text> tag has a citation index corresponding to the scraped URL. - 2. Citation Tag Structure: - - Create a <citation> tag for each distinct piece of information used from the website(s). - - Each <citation> tag must reference a URL chunk using the chunk_id attribute. - - For URL-based citations, leave the citation content empty, but reference the chunk_id and type as 'url'. + 2. Citation Tag Structure: + - Create a <citation> tag for each distinct piece of information used from the website(s). + - Each <citation> tag must reference a URL chunk using the chunk_id attribute. + - For URL-based citations, leave the citation content empty, but reference the chunk_id and type as 'url'. - 3. Structural Integrity Checks: - - Ensure all opening and closing tags are matched properly. - - Verify that all citation_index attributes in <grounded_text> tags correspond to valid citations. - - Do not over-cite—cite only the most relevant parts of the websites. + 3. Structural Integrity Checks: + - Ensure all opening and closing tags are matched properly. + - Verify that all citation_index attributes in <grounded_text> tags correspond to valid citations. + - Do not over-cite—cite only the most relevant parts of the websites. - Example Usage: + Example Usage: - <answer> - <grounded_text citation_index="1"> - Based on data from the World Bank, economic growth has stabilized in recent years, following a surge in investments. - </grounded_text> - <grounded_text citation_index="2"> - According to information retrieved from the International Monetary Fund, the inflation rate has been gradually decreasing since 2020. - </grounded_text> + <answer> + <grounded_text citation_index="1"> + Based on data from the World Bank, economic growth has stabilized in recent years, following a surge in investments. + </grounded_text> + <grounded_text citation_index="2"> + According to information retrieved from the International Monetary Fund, the inflation rate has been gradually decreasing since 2020. + </grounded_text> - <citations> - <citation index="1" chunk_id="1234" type="url"></citation> - <citation index="2" chunk_id="5678" type="url"></citation> - </citations> + <citations> + <citation index="1" chunk_id="1234" type="url"></citation> + <citation index="2" chunk_id="5678" type="url"></citation> + </citations> - <follow_up_questions> - <question>What are the long-term economic impacts of increased investments on GDP?</question> - <question>How might inflation trends affect future monetary policy?</question> - <question>Are there additional factors that could influence economic growth beyond investments and inflation?</question> - </follow_up_questions> - </answer> - `, + <follow_up_questions> + <question>What are the long-term economic impacts of increased investments on GDP?</question> + <question>How might inflation trends affect future monetary policy?</question> + <question>Are there additional factors that could influence economic growth beyond investments and inflation?</question> + </follow_up_questions> + </answer> + `, 'Returns the text content of the webpages for further analysis and grounding.' ); this._addLinkedUrlDoc = addLinkedUrlDoc; } - async execute(args: { url: string | string[] }): Promise<Observation[]> { - const urls = Array.isArray(args.url) ? args.url : [args.url]; + async execute(args: ParametersType<WebsiteInfoScraperToolParamsType>): Promise<Observation[]> { + const urls = args.urls; const results: Observation[] = []; for (const url of urls) { @@ -73,10 +79,16 @@ export class WebsiteInfoScraperTool extends BaseTool<{ url: string | string[] }> const { website_plain_text } = await Networking.PostToServer('/scrapeWebsite', { url }); const id = uuidv4(); this._addLinkedUrlDoc(url, id); - results.push({ type: 'text', text: `<chunk chunk_id=${id} chunk_type=url>\n${website_plain_text}\n</chunk>` }); + results.push({ + type: 'text', + text: `<chunk chunk_id="${id}" chunk_type="url">\n${website_plain_text}\n</chunk>`, + }); } catch (error) { console.log(error); - results.push({ type: 'text', text: `An error occurred while scraping the website: ${url}` }); + results.push({ + type: 'text', + text: `An error occurred while scraping the website: ${url}`, + }); } } diff --git a/src/client/views/nodes/chatbot/tools/WikipediaTool.ts b/src/client/views/nodes/chatbot/tools/WikipediaTool.ts index ee5661905..4fcffe2ed 100644 --- a/src/client/views/nodes/chatbot/tools/WikipediaTool.ts +++ b/src/client/views/nodes/chatbot/tools/WikipediaTool.ts @@ -2,33 +2,45 @@ import { v4 as uuidv4 } from 'uuid'; import { Networking } from '../../../../Network'; import { BaseTool } from './BaseTool'; import { Observation } from '../types/types'; +import { ParametersType } from './ToolTypes'; -export class WikipediaTool extends BaseTool<{ title: string }> { +const wikipediaToolParams = [ + { + name: 'title', + type: 'string', + description: 'The title of the Wikipedia article to search', + required: true, + }, +] as const; + +type WikipediaToolParamsType = typeof wikipediaToolParams; + +export class WikipediaTool extends BaseTool<WikipediaToolParamsType> { private _addLinkedUrlDoc: (url: string, id: string) => void; + constructor(addLinkedUrlDoc: (url: string, id: string) => void) { super( 'wikipedia', 'Search Wikipedia and return a summary', - { - title: { - type: 'string', - description: 'The title of the Wikipedia article to search', - required: true, - }, - }, + wikipediaToolParams, 'Provide simply the title you want to search on Wikipedia and nothing more. If re-using this tool, try a different title for different information.', 'Returns a summary from searching an article title on Wikipedia' ); this._addLinkedUrlDoc = addLinkedUrlDoc; } - async execute(args: { title: string }): Promise<Observation[]> { + async execute(args: ParametersType<WikipediaToolParamsType>): Promise<Observation[]> { try { const { text } = await Networking.PostToServer('/getWikipediaSummary', { title: args.title }); const id = uuidv4(); const url = `https://en.wikipedia.org/wiki/${args.title.replace(/ /g, '_')}`; this._addLinkedUrlDoc(url, id); - return [{ type: 'text', text: `<chunk chunk_id=${id} chunk_type=csv}> ${text} </chunk>` }]; + return [ + { + type: 'text', + text: `<chunk chunk_id="${id}" chunk_type="text"> ${text} </chunk>`, + }, + ]; } catch (error) { console.log(error); return [{ type: 'text', text: 'An error occurred while fetching the article.' }]; diff --git a/src/client/views/nodes/chatbot/types/types.ts b/src/client/views/nodes/chatbot/types/types.ts index fcf33702d..7abad85f0 100644 --- a/src/client/views/nodes/chatbot/types/types.ts +++ b/src/client/views/nodes/chatbot/types/types.ts @@ -1,3 +1,5 @@ +import { AnyLayer } from 'react-map-gl'; + export enum ASSISTANT_ROLE { USER = 'user', ASSISTANT = 'assistant', @@ -118,46 +120,3 @@ export interface AgentMessage { } export type Observation = { type: 'text'; text: string } | { type: 'image_url'; image_url: { url: string } }; - -// Utility type to map string representations of types to actual TypeScript types -export interface Tool<P extends ParamConfig[] = ParamConfig[]> { - name: string; - description: string; - parameterRules: ParameterArray; - citationRules: string; - briefSummary: string; - execute: (args: ParametersType<P>) => Promise<Observation[]>; // Allow flexibility in args type - getActionRule: () => Record<string, unknown>; -} - -// ParameterArray defines the structure of parameter configurations -export type ParameterArray = { - type: 'string' | 'number' | 'boolean' | 'string[]' | 'number[]'; - name: string; - description: string; - required?: boolean; - max_inputs?: number; -}[]; - -// ParamConfig is a more generic representation of individual parameters -export type ParamConfig = { - name: string; - type: string; -}; - -// Utility type to map string representations of types to actual TypeScript types -type TypeMap = { - string: string; - number: number; - boolean: boolean; - 'number[]': number[]; - 'string[]': string[]; -}; - -// Map ParamConfig's type field to actual TypeScript types -export type ParamType<P extends ParamConfig> = P['type'] extends keyof TypeMap ? TypeMap[P['type']] : unknown; - -// Create a ParametersType that infers the argument types from ParamConfig[] -export type ParametersType<P extends ParamConfig[]> = { - [K in P[number] as K['name']]: ParamType<K>; -}; |