From 596502c232ea6b6b88c3c58486e139074ea056ff Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Tue, 15 Oct 2024 14:18:44 -0400 Subject: tried something for typechecking but way too overcomplicated --- .../views/nodes/chatbot/agentsystem/Agent.ts | 8 +-- src/client/views/nodes/chatbot/tools/BaseTool.ts | 57 ++++++++++++++++------ .../views/nodes/chatbot/tools/CalculateTool.ts | 20 ++++---- .../views/nodes/chatbot/tools/CreateCSVTool.ts | 27 +++++----- .../nodes/chatbot/tools/CreateCollectionTool.ts | 3 +- .../views/nodes/chatbot/tools/DataAnalysisTool.ts | 12 ++--- .../views/nodes/chatbot/tools/GetDocsTool.ts | 32 +++++++----- src/client/views/nodes/chatbot/tools/NoTool.ts | 54 +++++++++++++++----- src/client/views/nodes/chatbot/tools/RAGTool.ts | 7 +-- src/client/views/nodes/chatbot/tools/SearchTool.ts | 20 ++++---- .../nodes/chatbot/tools/WebsiteInfoScraperTool.ts | 7 +-- .../views/nodes/chatbot/tools/WikipediaTool.ts | 3 +- src/client/views/nodes/chatbot/types/types.ts | 49 ++++++++++++++++--- 13 files changed, 202 insertions(+), 97 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts index ccf9caf15..0747ddd60 100644 --- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts +++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts @@ -11,7 +11,7 @@ import { NoTool } from '../tools/NoTool'; import { RAGTool } from '../tools/RAGTool'; import { SearchTool } from '../tools/SearchTool'; import { WebsiteInfoScraperTool } from '../tools/WebsiteInfoScraperTool'; -import { AgentMessage, AssistantMessage, PROCESSING_TYPE, ProcessingInfo, Tool } from '../types/types'; +import { AgentMessage, AssistantMessage, Observation, PROCESSING_TYPE, ProcessingInfo, Tool } from '../types/types'; import { Vectorstore } from '../vectorstore/Vectorstore'; import { getReactPrompt } from './prompts'; @@ -24,7 +24,6 @@ dotenv.config(); export class Agent { // Private properties private client: OpenAI; - private tools: Record>; // bcz: need a real type here private messages: AgentMessage[] = []; private interMessages: AgentMessage[] = []; private vectorstore: Vectorstore; @@ -36,6 +35,7 @@ export class Agent { private processingNumber: number = 0; private processingInfo: ProcessingInfo[] = []; private streamedAnswerParser: StreamedAnswerParser = new StreamedAnswerParser(); + private tools: Record>; /** * The constructor initializes the agent with the vector store and toolset, and sets up the OpenAI client. @@ -166,7 +166,7 @@ export class Agent { if (currentAction) { try { // Process the action with its input - const observation = (await this.processAction(currentAction, actionInput.inputs)) as any; // bcz: really need a type here + const observation = (await this.processAction(currentAction, actionInput.inputs)) as Observation[]; const nextPrompt = [{ type: 'text', text: ` ` }, ...observation, { type: 'text', text: '' }]; console.log(observation); this.interMessages.push({ role: 'user', content: nextPrompt }); @@ -266,7 +266,7 @@ export class Agent { * @param actionInput The inputs for the action. * @returns The result of the action. */ - private async processAction(action: string, actionInput: unknown): Promise { + private async processAction(action: string, actionInput: Record): Promise { if (!(action in this.tools)) { throw new Error(`Unknown action: ${action}`); } diff --git a/src/client/views/nodes/chatbot/tools/BaseTool.ts b/src/client/views/nodes/chatbot/tools/BaseTool.ts index a77f567a5..4f8f81bcb 100644 --- a/src/client/views/nodes/chatbot/tools/BaseTool.ts +++ b/src/client/views/nodes/chatbot/tools/BaseTool.ts @@ -1,3 +1,5 @@ +import { Tool, ParameterArray, ParametersType, Observation, ParamConfig } from '../types/types'; + /** * @file BaseTool.ts * @description This file defines the abstract BaseTool class, which serves as a blueprint @@ -6,27 +8,52 @@ * and retrieving action rules for use within the assistant's workflow. */ -import { Tool } from '../types/types'; +export abstract class BaseTool implements Tool { + name: string; + description: string; + parameterRules: ParameterArray; // Still using ParameterArray + citationRules: string; + briefSummary: string; + paramConfig: ParamConfig[]; -export abstract class BaseTool = Record> implements Tool { constructor( - public name: string, - public description: string, - public parameters: Record, - public citationRules: string, - public briefSummary: string - ) {} + name: string, + description: string, + parameterRules: ParameterArray, // Allow tuple types for parameters + 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(args: T): Promise; + // Abstract execute method with dynamic types based on parameters + abstract execute(args: ParametersType): Promise; + // Implement the getActionRule method getActionRule(): Record { return { - [this.name]: { - name: this.name, - citationRules: this.citationRules, - description: this.description, - parameters: this.parameters, - }, + tool: this.name, + description: this.description, + parameters: this.parameterRules.reduce( + (acc, param) => { + acc[param.name] = { + type: param.type, + description: param.description, + required: param.required, + max_inputs: param.max_inputs, + }; + return acc; + }, + {} as Record> + ), }; } } diff --git a/src/client/views/nodes/chatbot/tools/CalculateTool.ts b/src/client/views/nodes/chatbot/tools/CalculateTool.ts index 77ab1b39b..050e6f708 100644 --- a/src/client/views/nodes/chatbot/tools/CalculateTool.ts +++ b/src/client/views/nodes/chatbot/tools/CalculateTool.ts @@ -1,26 +1,28 @@ +import { Observation, ParametersType } from '../types/types'; import { BaseTool } from './BaseTool'; -export class CalculateTool extends BaseTool<{ expression: string }> { +export class CalculateTool extends BaseTool { constructor() { super( 'calculate', 'Perform a calculation', - { - expression: { + [ + { + name: 'expression', type: 'string', description: 'The mathematical expression to evaluate', - required: 'true', - max_inputs: '1', + required: true, }, - }, + ], '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 { - // Note: Using eval() can be dangerous. Consider using a safer alternative. - const result = eval(args.expression); + async execute(args: { expression: string }): Promise { + // 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. + 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 d3ded0de0..fff57c6d4 100644 --- a/src/client/views/nodes/chatbot/tools/CreateCSVTool.ts +++ b/src/client/views/nodes/chatbot/tools/CreateCSVTool.ts @@ -1,7 +1,8 @@ import { BaseTool } from './BaseTool'; import { Networking } from '../../../../Network'; +import { Observation } from '../types/types'; -export class CreateCSVTool extends BaseTool<{ csvData: string; filename: string }> { +export class CreateCSVTool extends BaseTool<{ csvData: { type: string; description: string; required: boolean }; filename: { type: string; description: string; required: boolean } }> { private _handleCSVResult: (url: string, filename: string, id: string, data: string) => void; constructor(handleCSVResult: (url: string, title: string, id: string, data: string) => void) { @@ -9,18 +10,16 @@ export class CreateCSVTool extends BaseTool<{ csvData: string; filename: string 'createCSV', 'Creates a CSV file from raw CSV data and saves it to the server', { - type: 'object', - properties: { - csvData: { - type: 'string', - description: 'A string of comma-separated values representing the CSV data.', - }, - filename: { - type: 'string', - description: 'The base name of the CSV file to be created. Should end in ".csv".', - }, + 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, }, - required: ['csvData', 'filename'], }, '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.' @@ -28,13 +27,11 @@ export class CreateCSVTool extends BaseTool<{ csvData: string; filename: string this._handleCSVResult = handleCSVResult; } - async execute(args: { csvData: string; filename: string }): Promise { + async execute(args: { csvData: string; filename: string }): Promise { try { console.log('Creating CSV file:', args.filename, ' with data:', args.csvData); - // Post the raw CSV data to the createCSV endpoint on the server const { fileUrl, id } = await Networking.PostToServer('/createCSV', { filename: args.filename, data: args.csvData }); - // Handle the result by invoking the callback this._handleCSVResult(fileUrl, args.filename, id, args.csvData); return [ diff --git a/src/client/views/nodes/chatbot/tools/CreateCollectionTool.ts b/src/client/views/nodes/chatbot/tools/CreateCollectionTool.ts index 1e479a62c..fb0d9ca4c 100644 --- a/src/client/views/nodes/chatbot/tools/CreateCollectionTool.ts +++ b/src/client/views/nodes/chatbot/tools/CreateCollectionTool.ts @@ -3,6 +3,7 @@ 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[] }> { @@ -25,7 +26,7 @@ export class GetDocsContentTool extends BaseTool<{ title: string; document_ids: this._docView = docView; } - async execute(args: { title: string; document_ids: string[] }): Promise { + async execute(args: { title: string; document_ids: string[] }): Promise { // 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 }); diff --git a/src/client/views/nodes/chatbot/tools/DataAnalysisTool.ts b/src/client/views/nodes/chatbot/tools/DataAnalysisTool.ts index 2e663fed1..b97576095 100644 --- a/src/client/views/nodes/chatbot/tools/DataAnalysisTool.ts +++ b/src/client/views/nodes/chatbot/tools/DataAnalysisTool.ts @@ -1,6 +1,7 @@ +import { Observation } from '../types/types'; import { BaseTool } from './BaseTool'; -export class DataAnalysisTool extends BaseTool<{ csv_file_name: string | string[] }> { +export class DataAnalysisTool extends BaseTool<{ csv_file_name: { type: string | string[]; description: string; required: boolean } }> { private csv_files_function: () => { filename: string; id: string; text: string }[]; constructor(csv_files: () => { filename: string; id: string; text: string }[]) { @@ -11,12 +12,11 @@ export class DataAnalysisTool extends BaseTool<{ csv_file_name: string | string[ csv_file_name: { type: 'string', description: 'Name(s) of the CSV file(s) to analyze', - required: 'true', - max_inputs: '3', + required: true, }, }, '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). ' + 'Provides the full CSV file text for your analysis based on the user query and the available CSV file(s).' ); this.csv_files_function = csv_files; } @@ -33,9 +33,9 @@ export class DataAnalysisTool extends BaseTool<{ csv_file_name: string | string[ return file?.id; } - async execute(args: { csv_file_name: string | string[] }): Promise { + async execute(args: { csv_file_name: string | string[] }): Promise { const filenames = Array.isArray(args.csv_file_name) ? args.csv_file_name : [args.csv_file_name]; - const results = []; + const results: Observation[] = []; for (const filename of filenames) { const fileContent = this.getFileContent(filename); diff --git a/src/client/views/nodes/chatbot/tools/GetDocsTool.ts b/src/client/views/nodes/chatbot/tools/GetDocsTool.ts index 903f3f69c..b6e549d93 100644 --- a/src/client/views/nodes/chatbot/tools/GetDocsTool.ts +++ b/src/client/views/nodes/chatbot/tools/GetDocsTool.ts @@ -1,29 +1,39 @@ -import { DocCast } from '../../../../../fields/Types'; +import { Observation } from '../types/types'; +import { BaseTool } from './BaseTool'; import { DocServer } from '../../../../DocServer'; import { Docs } from '../../../../documents/Documents'; import { DocumentView } from '../../DocumentView'; import { OpenWhere } from '../../OpenWhere'; -import { BaseTool } from './BaseTool'; -export class GetDocsTool extends BaseTool<{ title: string; document_ids: string[] }> { +export class GetDocsTool extends BaseTool<{ title: { type: string; description: string; required: boolean }; document_ids: { type: string; description: string; required: boolean } }> { 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, + }, + }, '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 the documents in Dash in JSON form. This will include the title of the document, the location in the FreeFormDocument, and the content of the document, any applicable data fields, the layout of the document, etc.' + 'Returns the documents in Dash in JSON form.' ); this._docView = docView; } - async execute(args: { title: string; document_ids: string[] }): Promise { - // Note: Using eval() can be dangerous. Consider using a safer alternative. - const docs = args.document_ids.map(doc_id => DocCast(DocServer.GetCachedRefField(doc_id))); + async execute(args: { title: string; document_ids: string[] }): Promise { + const docs = args.document_ids.map(doc_id => 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 }]; + this._docView._props.addDocTab(collection, OpenWhere.addRight); + 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/NoTool.ts b/src/client/views/nodes/chatbot/tools/NoTool.ts index edd3160ec..103abcdbe 100644 --- a/src/client/views/nodes/chatbot/tools/NoTool.ts +++ b/src/client/views/nodes/chatbot/tools/NoTool.ts @@ -1,19 +1,51 @@ -// tools/NoTool.ts +import { v4 as uuidv4 } from 'uuid'; +import { Networking } from '../../../../Network'; import { BaseTool } from './BaseTool'; +import { Observation } from '../types/types'; -export class NoTool extends BaseTool> { - constructor() { +export class SearchTool extends BaseTool<{ query: { type: string | string[]; description: string; required: boolean } }> { + private _addLinkedUrlDoc: (url: string, id: string) => void; + private _max_results: number; + + constructor(addLinkedUrlDoc: (url: string, id: string) => void, max_results: number = 5) { super( - 'no_tool', - 'Use this when no external tool or action is required to answer the question.', - {}, - 'When using the "no_tool" action, simply provide an empty element. The observation will always be "No tool used. Proceed with answering the question."', - 'Use when no external tool or action is required to answer the question.' + '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; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async execute(args: object): Promise { - return [{ type: 'text', text: 'No tool used. Proceed with answering the question.' }]; + async execute(args: { query: string | string[] }): Promise { + const queries = Array.isArray(args.query) ? args.query : [args.query]; + const allResults = []; + + 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: `${result.url}${result.snippet}`, + }; + }); + allResults.push(...data); + } catch (error) { + console.log(error); + allResults.push({ type: 'text', text: `An error occurred while performing the web search for query: ${query}` }); + } + } + + return allResults; } } diff --git a/src/client/views/nodes/chatbot/tools/RAGTool.ts b/src/client/views/nodes/chatbot/tools/RAGTool.ts index 4cc2f26ff..4babf540a 100644 --- a/src/client/views/nodes/chatbot/tools/RAGTool.ts +++ b/src/client/views/nodes/chatbot/tools/RAGTool.ts @@ -1,5 +1,6 @@ +import { O } from '@fullcalendar/core/internal-common'; import { Networking } from '../../../../Network'; -import { RAGChunk } from '../types/types'; +import { Observation, RAGChunk } from '../types/types'; import { Vectorstore } from '../vectorstore/Vectorstore'; import { BaseTool } from './BaseTool'; @@ -56,13 +57,13 @@ export class RAGTool extends BaseTool { ); } - async execute(args: { hypothetical_document_chunk: string }): Promise { + async execute(args: { hypothetical_document_chunk: string }): Promise { const relevantChunks = await this.vectorstore.retrieve(args.hypothetical_document_chunk); const formatted_chunks = await this.getFormattedChunks(relevantChunks); return formatted_chunks; } - async getFormattedChunks(relevantChunks: RAGChunk[]): Promise { + async getFormattedChunks(relevantChunks: RAGChunk[]): Promise { try { const { formattedChunks } = await Networking.PostToServer('/formatChunks', { relevantChunks }); diff --git a/src/client/views/nodes/chatbot/tools/SearchTool.ts b/src/client/views/nodes/chatbot/tools/SearchTool.ts index 3a4668422..103abcdbe 100644 --- a/src/client/views/nodes/chatbot/tools/SearchTool.ts +++ b/src/client/views/nodes/chatbot/tools/SearchTool.ts @@ -1,10 +1,12 @@ import { v4 as uuidv4 } from 'uuid'; import { Networking } from '../../../../Network'; import { BaseTool } from './BaseTool'; +import { Observation } from '../types/types'; -export class SearchTool extends BaseTool<{ query: string | string[] }> { +export class SearchTool extends BaseTool<{ query: { type: string | string[]; description: string; required: boolean } }> { private _addLinkedUrlDoc: (url: string, id: string) => void; private _max_results: number; + constructor(addLinkedUrlDoc: (url: string, id: string) => void, max_results: number = 5) { super( 'searchTool', @@ -13,32 +15,28 @@ export class SearchTool extends BaseTool<{ query: string | string[] }> { query: { type: 'string', description: 'The search query or queries to use for finding websites', - required: 'true', - max_inputs: '3', + required: true, }, }, - 'Provide up to 3 search queries to find a broad range of websites. This tool is intended to help you identify relevant websites, but not to be used for providing the final answer. Use this information to determine which specific website to investigate further.', - 'Returns a list of websites and their overviews based on the search queries, helping to identify which websites might contain relevant information.' + '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 { + async execute(args: { query: string | string[] }): Promise { const queries = Array.isArray(args.query) ? args.query : [args.query]; const allResults = []; for (const query of queries) { try { const { results } = await Networking.PostToServer('/getWebSearchResults', { query, max_results: this._max_results }); - const data: { type: string; text: string }[] = results.map((result: { url: string; snippet: string }) => { + const data = results.map((result: { url: string; snippet: string }) => { const id = uuidv4(); return { type: 'text', - text: ` - ${result.url} - ${result.snippet} - `, + text: `${result.url}${result.snippet}`, }; }); allResults.push(...data); diff --git a/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts b/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts index 1efb389b8..8a4181b43 100644 --- a/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts +++ b/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts @@ -1,6 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; import { Networking } from '../../../../Network'; import { BaseTool } from './BaseTool'; +import { Observation } from '../types/types'; export class WebsiteInfoScraperTool extends BaseTool<{ url: string | string[] }> { private _addLinkedUrlDoc: (url: string, id: string) => void; @@ -63,16 +64,16 @@ export class WebsiteInfoScraperTool extends BaseTool<{ url: string | string[] }> this._addLinkedUrlDoc = addLinkedUrlDoc; } - async execute(args: { url: string | string[] }): Promise { + async execute(args: { url: string | string[] }): Promise { const urls = Array.isArray(args.url) ? args.url : [args.url]; - const results = []; + const results: Observation[] = []; for (const url of urls) { try { const { website_plain_text } = await Networking.PostToServer('/scrapeWebsite', { url }); const id = uuidv4(); this._addLinkedUrlDoc(url, id); - results.push({ type: 'text', text: `\n${website_plain_text}\n\n` }); + results.push({ type: 'text', text: `\n${website_plain_text}\n` }); } catch (error) { console.log(error); 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 692dff749..ee5661905 100644 --- a/src/client/views/nodes/chatbot/tools/WikipediaTool.ts +++ b/src/client/views/nodes/chatbot/tools/WikipediaTool.ts @@ -1,6 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; import { Networking } from '../../../../Network'; import { BaseTool } from './BaseTool'; +import { Observation } from '../types/types'; export class WikipediaTool extends BaseTool<{ title: string }> { private _addLinkedUrlDoc: (url: string, id: string) => void; @@ -21,7 +22,7 @@ export class WikipediaTool extends BaseTool<{ title: string }> { this._addLinkedUrlDoc = addLinkedUrlDoc; } - async execute(args: { title: string }): Promise { + async execute(args: { title: string }): Promise { try { const { text } = await Networking.PostToServer('/getWikipediaSummary', { title: args.title }); const id = uuidv4(); diff --git a/src/client/views/nodes/chatbot/types/types.ts b/src/client/views/nodes/chatbot/types/types.ts index 2bc7f4952..fcf33702d 100644 --- a/src/client/views/nodes/chatbot/types/types.ts +++ b/src/client/views/nodes/chatbot/types/types.ts @@ -112,17 +112,52 @@ export interface AI_Document { type: string; } -export interface Tool = Record> { +export interface AgentMessage { + role: 'system' | 'user' | 'assistant'; + content: string | Observation[]; +} + +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

{ name: string; description: string; - parameters: Record; + parameterRules: ParameterArray; citationRules: string; briefSummary: string; - execute: (args: T) => Promise; + execute: (args: ParametersType

) => Promise; // Allow flexibility in args type getActionRule: () => Record; } -export interface AgentMessage { - role: 'system' | 'user' | 'assistant'; - content: string | { type: string; text?: string; image_url?: { url: string } }[]; -} +// 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['type'] extends keyof TypeMap ? TypeMap[P['type']] : unknown; + +// Create a ParametersType that infers the argument types from ParamConfig[] +export type ParametersType

= { + [K in P[number] as K['name']]: ParamType; +}; -- cgit v1.2.3-70-g09d2 From eaa7255b1b9aec1d9b54f469d53d6ab6feae8683 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Oct 2024 09:23:32 -0400 Subject: fixed script compile error --- src/client/util/CurrentUserUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 555ccdd88..98f3eb757 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -88,7 +88,7 @@ export class CurrentUserUtils { const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true}; const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([this])))"}, - { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}]; + { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView, "${OpenWhere.addRight}")`}]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === opts.opts.title): undefined; -- cgit v1.2.3-70-g09d2 From 80d86bd5ae3e1d3dc70e7636f72a872a5fb2f01d Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Thu, 17 Oct 2024 10:41:49 -0400 Subject: Implemented strict typechecking for tools, specifically tool inputs --- .../views/nodes/chatbot/agentsystem/Agent.ts | 3 +- src/client/views/nodes/chatbot/tools/BaseTool.ts | 64 ++++++++----- .../views/nodes/chatbot/tools/CalculateTool.ts | 32 ++++--- .../views/nodes/chatbot/tools/CreateCSVTool.ts | 40 +++++--- .../nodes/chatbot/tools/CreateCollectionTool.ts | 37 -------- .../views/nodes/chatbot/tools/DataAnalysisTool.ts | 31 +++--- .../views/nodes/chatbot/tools/GetDocsTool.ts | 38 +++++--- src/client/views/nodes/chatbot/tools/NoTool.ts | 53 ++--------- src/client/views/nodes/chatbot/tools/RAGTool.ts | 30 +++--- src/client/views/nodes/chatbot/tools/SearchTool.ts | 39 +++++--- src/client/views/nodes/chatbot/tools/ToolTypes.ts | 76 +++++++++++++++ .../nodes/chatbot/tools/WebsiteInfoScraperTool.ts | 104 ++++++++++++--------- .../views/nodes/chatbot/tools/WikipediaTool.ts | 32 +++++-- src/client/views/nodes/chatbot/types/types.ts | 45 +-------- 14 files changed, 339 insertions(+), 285 deletions(-) delete mode 100644 src/client/views/nodes/chatbot/tools/CreateCollectionTool.ts create mode 100644 src/client/views/nodes/chatbot/tools/ToolTypes.ts (limited to 'src') 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>; + private tools: Record; /** * 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 { +/** + * 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

implements Tool

{ + // 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): Promise; + /** + * 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

`. + * @returns A promise that resolves to an array of `Observation` objects. + */ + abstract execute(args: ParametersType

): Promise; - // 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 { 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; // Type assertion to exclude the 'name' property return acc; }, - {} as Record> + {} as Record> // 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 { 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 { - // 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): Promise { + // 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 { 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 { + async execute(args: ParametersType): Promise { 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 { - // 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 { 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 { - const filenames = Array.isArray(args.csv_file_name) ? args.csv_file_name : [args.csv_file_name]; + async execute(args: ParametersType): Promise { + 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: `${fileContent}`, + text: `${fileContent}`, }); } 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 { 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 { - const docs = args.document_ids.map(doc_id => DocServer.GetCachedRefField(doc_id)); + async execute(args: ParametersType): Promise { + 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 { - 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: `${result.url}${result.snippet}`, - }; - }); - 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 { + 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): Promise { + // 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 { 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 { `, - `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 { + async execute(args: ParametersType): Promise { 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 { 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 { 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 { - const queries = Array.isArray(args.query) ? args.query : [args.query]; - const allResults = []; + async execute(args: ParametersType): Promise { + 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

{ + // 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

`. + * @returns A promise that resolves to an array of `Observation` objects. + */ + execute: (args: ParametersType

) => Promise; + /** + * Generates an action rule object that describes the tool's usage. + * @returns An object representing the tool's action rules. + */ + getActionRule: () => Record; +} + +/** + * 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['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

= { + [K in P[number] as K['name']]: ParamType; +}; 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 { 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 tags. - - **Do not include non-sourced information** in tags. - - Use a single tag for content derived from a single website. If citing multiple websites, create new tags for each. - - Ensure each 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 tags. + - **Do not include non-sourced information** in tags. + - Use a single tag for content derived from a single website. If citing multiple websites, create new tags for each. + - Ensure each tag has a citation index corresponding to the scraped URL. - 2. Citation Tag Structure: - - Create a tag for each distinct piece of information used from the website(s). - - Each 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 tag for each distinct piece of information used from the website(s). + - Each 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 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 tags correspond to valid citations. + - Do not over-cite—cite only the most relevant parts of the websites. - Example Usage: + Example Usage: - - - Based on data from the World Bank, economic growth has stabilized in recent years, following a surge in investments. - - - According to information retrieved from the International Monetary Fund, the inflation rate has been gradually decreasing since 2020. - + + + Based on data from the World Bank, economic growth has stabilized in recent years, following a surge in investments. + + + According to information retrieved from the International Monetary Fund, the inflation rate has been gradually decreasing since 2020. + - - - - + + + + - - What are the long-term economic impacts of increased investments on GDP? - How might inflation trends affect future monetary policy? - Are there additional factors that could influence economic growth beyond investments and inflation? - - - `, + + What are the long-term economic impacts of increased investments on GDP? + How might inflation trends affect future monetary policy? + Are there additional factors that could influence economic growth beyond investments and inflation? + + + `, 'Returns the text content of the webpages for further analysis and grounding.' ); this._addLinkedUrlDoc = addLinkedUrlDoc; } - async execute(args: { url: string | string[] }): Promise { - const urls = Array.isArray(args.url) ? args.url : [args.url]; + async execute(args: ParametersType): Promise { + 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: `\n${website_plain_text}\n` }); + results.push({ + type: 'text', + text: `\n${website_plain_text}\n`, + }); } 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 { 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 { + async execute(args: ParametersType): Promise { 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: ` ${text} ` }]; + return [ + { + type: 'text', + text: ` ${text} `, + }, + ]; } 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

{ - name: string; - description: string; - parameterRules: ParameterArray; - citationRules: string; - briefSummary: string; - execute: (args: ParametersType

) => Promise; // Allow flexibility in args type - getActionRule: () => Record; -} - -// 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['type'] extends keyof TypeMap ? TypeMap[P['type']] : unknown; - -// Create a ParametersType that infers the argument types from ParamConfig[] -export type ParametersType

= { - [K in P[number] as K['name']]: ParamType; -}; -- cgit v1.2.3-70-g09d2 From 14f412611299fc350f13b6f96be913d59533cfb3 Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Thu, 17 Oct 2024 11:14:51 -0400 Subject: Removed awaits inside loops and made Parameters readonly for better type safety --- .../views/nodes/chatbot/agentsystem/Agent.ts | 77 ++++++++++++++++++---- .../chatbot/chatboxcomponents/MessageComponent.tsx | 3 +- src/client/views/nodes/chatbot/tools/BaseTool.ts | 11 ++-- src/client/views/nodes/chatbot/tools/SearchTool.ts | 22 ++++--- src/client/views/nodes/chatbot/tools/ToolTypes.ts | 16 ++--- .../nodes/chatbot/tools/WebsiteInfoScraperTool.ts | 17 +++-- 6 files changed, 104 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts index ba5868207..34e7cf5ea 100644 --- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts +++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts @@ -11,10 +11,11 @@ import { NoTool } from '../tools/NoTool'; import { RAGTool } from '../tools/RAGTool'; import { SearchTool } from '../tools/SearchTool'; import { WebsiteInfoScraperTool } from '../tools/WebsiteInfoScraperTool'; -import { AgentMessage, AssistantMessage, Observation, PROCESSING_TYPE, ProcessingInfo, Tool } from '../types/types'; +import { AgentMessage, AssistantMessage, Observation, PROCESSING_TYPE, ProcessingInfo } from '../types/types'; import { Vectorstore } from '../vectorstore/Vectorstore'; import { getReactPrompt } from './prompts'; import { BaseTool } from '../tools/BaseTool'; +import { Parameter, ParametersType, Tool } from '../tools/ToolTypes'; dotenv.config(); @@ -36,7 +37,7 @@ export class Agent { private processingNumber: number = 0; private processingInfo: ProcessingInfo[] = []; private streamedAnswerParser: StreamedAnswerParser = new StreamedAnswerParser(); - private tools: Record; + private tools: Record>>; /** * The constructor initializes the agent with the vector store and toolset, and sets up the OpenAI client. @@ -109,15 +110,16 @@ export class Agent { let currentAction: string | undefined; this.processingInfo = []; - // Conversation loop (up to maxTurns) - for (let i = 2; i < maxTurns; i += 2) { + let i = 2; + while (i < maxTurns) { console.log(this.interMessages); console.log(`Turn ${i}/${maxTurns}`); - // Execute a step in the conversation and get the result const result = await this.execute(onProcessingUpdate, onAnswerUpdate); this.interMessages.push({ role: 'assistant', content: result }); + i += 2; + let parsedResult; try { // Parse XML result from the assistant @@ -149,7 +151,7 @@ export class Agent { { type: 'text', text: `` + builder.build({ action_rules: this.tools[currentAction].getActionRule() }) + ``, - }, + } as Observation, ]; this.interMessages.push({ role: 'user', content: nextPrompt }); break; @@ -168,7 +170,7 @@ export class Agent { try { // Process the action with its input const observation = (await this.processAction(currentAction, actionInput.inputs)) as Observation[]; - const nextPrompt = [{ type: 'text', text: ` ` }, ...observation, { type: 'text', text: '' }]; + const nextPrompt = [{ type: 'text', text: ` ` }, ...observation, { type: 'text', text: '' }] as Observation[]; console.log(observation); this.interMessages.push({ role: 'user', content: nextPrompt }); this.processingNumber++; @@ -263,16 +265,69 @@ export class Agent { /** * Processes a specific action by invoking the appropriate tool with the provided inputs. - * @param action The action to perform. - * @param actionInput The inputs for the action. - * @returns The result of the action. + * This method ensures that the action exists and validates the types of `actionInput` + * based on the tool's parameter rules. It throws errors for missing required parameters + * or mismatched types before safely executing the tool with the validated input. + * + * Type validation includes checks for: + * - `string`, `number`, `boolean` + * - `string[]`, `number[]` (arrays of strings or numbers) + * + * @param action The action to perform. It corresponds to a registered tool. + * @param actionInput The inputs for the action, passed as an object where each key is a parameter name. + * @returns A promise that resolves to an array of `Observation` objects representing the result of the action. + * @throws An error if the action is unknown, if required parameters are missing, or if input types don't match the expected parameter types. */ private async processAction(action: string, actionInput: Record): Promise { + // Check if the action exists in the tools list if (!(action in this.tools)) { throw new Error(`Unknown action: ${action}`); } const tool = this.tools[action]; - return await tool.execute(actionInput); + + // Validate actionInput based on tool's parameter rules + for (const paramRule of tool.parameterRules) { + const inputValue = actionInput[paramRule.name]; + + if (paramRule.required && inputValue === undefined) { + throw new Error(`Missing required parameter: ${paramRule.name}`); + } + + // If the parameter is defined, check its type + if (inputValue !== undefined) { + switch (paramRule.type) { + case 'string': + if (typeof inputValue !== 'string') { + throw new Error(`Expected parameter '${paramRule.name}' to be a string.`); + } + break; + case 'number': + if (typeof inputValue !== 'number') { + throw new Error(`Expected parameter '${paramRule.name}' to be a number.`); + } + break; + case 'boolean': + if (typeof inputValue !== 'boolean') { + throw new Error(`Expected parameter '${paramRule.name}' to be a boolean.`); + } + break; + case 'string[]': + if (!Array.isArray(inputValue) || !inputValue.every(item => typeof item === 'string')) { + throw new Error(`Expected parameter '${paramRule.name}' to be an array of strings.`); + } + break; + case 'number[]': + if (!Array.isArray(inputValue) || !inputValue.every(item => typeof item === 'number')) { + throw new Error(`Expected parameter '${paramRule.name}' to be an array of numbers.`); + } + break; + default: + throw new Error(`Unsupported parameter type: ${paramRule.type}`); + } + } + } + + return await tool.execute(actionInput as ParametersType); } } diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx index d48f46963..e463d15bf 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx @@ -23,7 +23,6 @@ import ReactMarkdown from 'react-markdown'; */ interface MessageComponentProps { message: AssistantMessage; - index: number; onFollowUpClick: (question: string) => void; onCitationClick: (citation: Citation) => void; updateMessageCitations: (index: number, citations: Citation[]) => void; @@ -34,7 +33,7 @@ interface MessageComponentProps { * processing information, and follow-up questions. * @param {MessageComponentProps} props - The props for the component. */ -const MessageComponentBox: React.FC = ({ message, index, onFollowUpClick, onCitationClick, updateMessageCitations }) => { +const MessageComponentBox: React.FC = ({ message, onFollowUpClick, onCitationClick }) => { // State for managing whether the dropdown is open or closed for processing info const [dropdownOpen, setDropdownOpen] = useState(false); diff --git a/src/client/views/nodes/chatbot/tools/BaseTool.ts b/src/client/views/nodes/chatbot/tools/BaseTool.ts index e01296ac4..58cd514d9 100644 --- a/src/client/views/nodes/chatbot/tools/BaseTool.ts +++ b/src/client/views/nodes/chatbot/tools/BaseTool.ts @@ -1,4 +1,5 @@ -import { Tool, Parameter, ParametersType, Observation } from '../types/types'; +import { Observation } from '../types/types'; +import { Parameter, Tool, ParametersType } from './ToolTypes'; /** * @file BaseTool.ts @@ -10,10 +11,10 @@ import { Tool, Parameter, ParametersType, Observation } from '../types/types'; /** * 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). + * It is generic over a type parameter `P`, which extends `ReadonlyArray`. + * This means `P` is a readonly array of `Parameter` objects that cannot be modified (immutable). */ -export abstract class BaseTool

implements Tool

{ +export abstract class BaseTool

> implements Tool

{ // The name of the tool (e.g., "calculate", "searchTool") name: string; // A description of the tool's functionality @@ -29,7 +30,7 @@ export abstract class BaseTool

implements Tool

`). * @param citationRules - Rules or guidelines for citations. * @param briefSummary - A short summary of the tool. */ diff --git a/src/client/views/nodes/chatbot/tools/SearchTool.ts b/src/client/views/nodes/chatbot/tools/SearchTool.ts index c5cf951e7..fd5144dd6 100644 --- a/src/client/views/nodes/chatbot/tools/SearchTool.ts +++ b/src/client/views/nodes/chatbot/tools/SearchTool.ts @@ -34,9 +34,9 @@ export class SearchTool extends BaseTool { async execute(args: ParametersType): Promise { const queries = args.query; - const allResults: Observation[] = []; - for (const query of queries) { + // Create an array of promises, each one handling a search for a query + const searchPromises = queries.map(async query => { try { const { results } = await Networking.PostToServer('/getWebSearchResults', { query, @@ -49,16 +49,20 @@ export class SearchTool extends BaseTool { text: `${result.url}${result.snippet}`, }; }); - allResults.push(...data); + return data; } catch (error) { console.log(error); - allResults.push({ - type: 'text', - text: `An error occurred while performing the web search for query: ${query}`, - }); + return [ + { + type: 'text', + text: `An error occurred while performing the web search for query: ${query}`, + }, + ]; } - } + }); + + const allResultsArrays = await Promise.all(searchPromises); - return allResults; + return allResultsArrays.flat(); } } diff --git a/src/client/views/nodes/chatbot/tools/ToolTypes.ts b/src/client/views/nodes/chatbot/tools/ToolTypes.ts index 74a92bcf2..d47a38952 100644 --- a/src/client/views/nodes/chatbot/tools/ToolTypes.ts +++ b/src/client/views/nodes/chatbot/tools/ToolTypes.ts @@ -2,10 +2,10 @@ 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[]`. + * It is generic over a type parameter `P`, which extends `ReadonlyArray`. * @template P - An array of `Parameter` objects defining the tool's parameters. */ -export interface Tool

{ +export interface Tool

> { // The name of the tool (e.g., "calculate", "searchTool") name: string; // A description of the tool's functionality @@ -34,15 +34,15 @@ export interface Tool

{ */ export type Parameter = { // The type of the parameter; constrained to the types 'string', 'number', 'boolean', 'string[]', 'number[]' - type: 'string' | 'number' | 'boolean' | 'string[]' | 'number[]'; + readonly type: 'string' | 'number' | 'boolean' | 'string[]' | 'number[]'; // The name of the parameter - name: string; + readonly name: string; // A description of the parameter - description: string; + readonly description: string; // Indicates whether the parameter is required - required: boolean; + readonly required: boolean; // (Optional) The maximum number of inputs (useful for array types) - max_inputs?: number; + readonly max_inputs?: number; }; /** @@ -71,6 +71,6 @@ export type ParamType

= P['type'] extends keyof TypeMap ? T * 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

= { +export type ParametersType

> = { [K in P[number] as K['name']]: ParamType; }; diff --git a/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts b/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts index e91ebdad1..f2e3863a6 100644 --- a/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts +++ b/src/client/views/nodes/chatbot/tools/WebsiteInfoScraperTool.ts @@ -72,25 +72,28 @@ export class WebsiteInfoScraperTool extends BaseTool): Promise { const urls = args.urls; - const results: Observation[] = []; - for (const url of urls) { + // Create an array of promises, each one handling a website scrape for a URL + const scrapingPromises = urls.map(async url => { try { const { website_plain_text } = await Networking.PostToServer('/scrapeWebsite', { url }); const id = uuidv4(); this._addLinkedUrlDoc(url, id); - results.push({ + return { type: 'text', text: `\n${website_plain_text}\n`, - }); + } as Observation; } catch (error) { console.log(error); - results.push({ + return { type: 'text', text: `An error occurred while scraping the website: ${url}`, - }); + } as Observation; } - } + }); + + // Wait for all scraping promises to resolve + const results = await Promise.all(scrapingPromises); return results; } -- cgit v1.2.3-70-g09d2 From a439a7be3c5cb001d55c78b99cb8da6506afb31a Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Oct 2024 11:16:25 -0400 Subject: fixed drag/drop of faces from one face to another. --- .../collectionFreeForm/FaceCollectionBox.tsx | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index 534f67927..6d51ecac6 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react'; import React from 'react'; import { DivHeight, lightOrDark, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; -import { Doc, Opt } from '../../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; import { List } from '../../../../fields/List'; import { DocCast, ImageCast, NumCast, StrCast } from '../../../../fields/Types'; @@ -54,7 +54,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent() { @observable _headerRef: HTMLDivElement | null = null; @observable _listRef: HTMLDivElement | null = null; - observer = new ResizeObserver(a => { + observer = new ResizeObserver(() => { this._props.setHeight?.( (this.props.Document._face_showImages ? 20 : 0) + // (!this._headerRef ? 0 : DivHeight(this._headerRef)) + @@ -97,9 +97,9 @@ export class UniqueFaceBox extends ViewBoxBaseComponent() { const faceMatcher = new FaceMatcher([labeledFaceDescriptor], 1); const faceAnno = FaceRecognitionHandler.ImageDocFaceAnnos(imgDoc).reduce( - (prev, faceAnno) => { - const match = faceMatcher.matchDescriptor(new Float32Array(Array.from(faceAnno.faceDescriptor as List))); - return match.distance < prev.dist ? { dist: match.distance, faceAnno } : prev; + (prev, fAnno) => { + const match = faceMatcher.matchDescriptor(new Float32Array(Array.from(fAnno.faceDescriptor as List))); + return match.distance < prev.dist ? { dist: match.distance, faceAnno: fAnno } : prev; }, { dist: 1, faceAnno: undefined as Opt } ).faceAnno ?? imgDoc; @@ -108,10 +108,18 @@ export class UniqueFaceBox extends ViewBoxBaseComponent() { if (faceAnno) { faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(faceAnno, DocCast(faceAnno.face)); FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document); - faceAnno.face = this.Document; + faceAnno[DocData].face = this.Document[DocData]; } } }); + de.complete.docDragData?.droppedDocuments + ?.filter(doc => DocCast(doc.face)?.type === DocumentType.UFACE) + .forEach(faceAnno => { + const imgDoc = faceAnno; + faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, DocCast(faceAnno.face)); + FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document); + faceAnno[DocData].face = this.Document[DocData]; + }); e.stopPropagation(); return true; } @@ -189,7 +197,8 @@ export class UniqueFaceBox extends ViewBoxBaseComponent() { this, e, () => { - DragManager.StartDocumentDrag([e.target as HTMLElement], new DragManager.DocumentDragData([doc], dropActionType.embed), e.clientX, e.clientY); + const dragDoc = DocListCast(doc.data_annotations).find(a => a.face === this.Document[DocData]) ?? this.Document; + DragManager.StartDocumentDrag([e.target as HTMLElement], new DragManager.DocumentDragData([dragDoc], dropActionType.embed), e.clientX, e.clientY); return true; }, emptyFunction, -- cgit v1.2.3-70-g09d2 From 4a9330e996b9117fb27560b9898b6fc1dbb78f96 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Oct 2024 12:01:15 -0400 Subject: made Card and Carousel part of novice mode views. --- src/client/documents/DocumentTypes.ts | 24 +++++++++++----------- src/client/util/CurrentUserUtils.ts | 9 ++++---- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 2 +- 3 files changed, 17 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index e79207b04..efe73fbbe 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -48,22 +48,22 @@ export enum DocumentType { export enum CollectionViewType { Invalid = 'invalid', Freeform = 'freeform', - Schema = 'schema', - Docking = 'docking', - Tree = 'tree', - Stacking = 'stacking', - Masonry = 'masonry', - Multicolumn = 'multicolumn', - Multirow = 'multirow', - Time = 'time', + Calendar = 'calendar', + Card = 'card', Carousel = 'carousel', Carousel3D = '3D Carousel', + Docking = 'docking', + Grid = 'grid', Linear = 'linear', Map = 'map', - Grid = 'grid', + Masonry = 'masonry', + Multicolumn = 'multicolumn', + Multirow = 'multirow', + NoteTaking = 'notetaking', Pile = 'pileup', + Schema = 'schema', + Stacking = 'stacking', StackedTimeline = 'stacked timeline', - NoteTaking = 'notetaking', - Calendar = 'calendar', - Card = 'card', + Time = 'time', + Tree = 'tree', } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 98f3eb757..30c75c659 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -805,11 +805,10 @@ pie title Minerals in my tap water } static contextMenuTools(doc:Doc):Button[] { return [ - { btnList: new List([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, - CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, - CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, - CollectionViewType.Carousel3D, CollectionViewType.Card, CollectionViewType.Linear, CollectionViewType.Map, - CollectionViewType.Calendar, CollectionViewType.Grid, CollectionViewType.NoteTaking, ]), + { btnList: new List([CollectionViewType.Freeform, CollectionViewType.Card, CollectionViewType.Carousel,CollectionViewType.Carousel3D, + CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Linear, + CollectionViewType.Map, CollectionViewType.NoteTaking, CollectionViewType.Schema, CollectionViewType.Stacking, + CollectionViewType.Calendar, CollectionViewType.Grid, CollectionViewType.Tree, CollectionViewType.Time, ]), title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, _readOnly_); }'}}, { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}}, { title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} }, diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index feaf84b7b..d4898eb3c 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -187,7 +187,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { } else { return