From cc1d3d8feae8115c57f8c9d3e9f9fd2463ed75a6 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 30 May 2024 03:08:49 +0100 Subject: [PATCH 1/2] add exa search tool --- .../credentials/ExaSearchApi.credential.ts | 26 ++ .../nodes/tools/ExaSearch/ExaSearch.ts | 230 ++++++++++++++++++ .../components/nodes/tools/ExaSearch/exa.svg | 1 + packages/components/package.json | 2 + pnpm-lock.yaml | 26 ++ 5 files changed, 285 insertions(+) create mode 100644 packages/components/credentials/ExaSearchApi.credential.ts create mode 100644 packages/components/nodes/tools/ExaSearch/ExaSearch.ts create mode 100644 packages/components/nodes/tools/ExaSearch/exa.svg diff --git a/packages/components/credentials/ExaSearchApi.credential.ts b/packages/components/credentials/ExaSearchApi.credential.ts new file mode 100644 index 00000000000..20f360c424c --- /dev/null +++ b/packages/components/credentials/ExaSearchApi.credential.ts @@ -0,0 +1,26 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class ExaSearchApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Exa Search API' + this.name = 'exaSearchApi' + this.version = 1.0 + this.description = + 'Refer to official guide on how to get an API Key from Exa' + this.inputs = [ + { + label: 'ExaSearch Api Key', + name: 'exaSearchApiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: ExaSearchApi } diff --git a/packages/components/nodes/tools/ExaSearch/ExaSearch.ts b/packages/components/nodes/tools/ExaSearch/ExaSearch.ts new file mode 100644 index 00000000000..9cfafadee7e --- /dev/null +++ b/packages/components/nodes/tools/ExaSearch/ExaSearch.ts @@ -0,0 +1,230 @@ +import { ExaSearchResults } from '@langchain/exa' +import Exa from 'exa-js' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' + +const DESC = `A wrapper around Exa Search. Input should be an Exa-optimized query. Output is a JSON array of the query results` + +class ExaSearch_Tools implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Exa Search' + this.name = 'exaSearch' + this.version = 1.0 + this.type = 'ExaSearch' + this.icon = 'exa.svg' + this.category = 'Tools' + this.description = 'Wrapper around Exa Search API - search engine fully designed for use by LLMs' + this.inputs = [ + { + label: 'Tool Description', + name: 'description', + type: 'string', + description: 'Description of what the tool does. This is for LLM to determine when to use this tool.', + rows: 4, + additionalParams: true, + default: DESC + }, + { + label: 'Num of Results', + name: 'numResults', + type: 'number', + optional: true, + step: 1, + additionalParams: true, + description: 'Number of search results to return. Default 10. Max 10 for basic plans. Up to thousands for custom plans.' + }, + { + label: 'Search Type', + name: 'type', + type: 'options', + options: [ + { + label: 'keyword', + name: 'keyword' + }, + { + label: 'neural', + name: 'neural' + }, + { + label: 'magic', + name: 'magic', + description: 'decides between keyword and neural' + } + ], + optional: true, + additionalParams: true + }, + { + label: 'Use Auto Prompt', + name: 'useAutoprompt', + type: 'boolean', + optional: true, + additionalParams: true, + description: 'If true, your query will be converted to a Exa query. Default false.' + }, + { + label: 'Category (Beta)', + name: 'category', + type: 'options', + description: + 'A data category to focus on, with higher comprehensivity and data cleanliness. Categories right now include company, research paper, news, github, tweet, movie, song, personal site, and pdf', + options: [ + { + label: 'company', + name: 'company' + }, + { + label: 'research paper', + name: 'research paper' + }, + { + label: 'news', + name: 'news' + }, + { + label: 'github', + name: 'github' + }, + { + label: 'tweet', + name: 'tweet' + }, + { + label: 'movie', + name: 'movie' + }, + { + label: 'song', + name: 'song' + }, + { + label: 'pdf', + name: 'pdf' + }, + { + label: 'personal site', + name: 'personal site' + } + ], + optional: true, + additionalParams: true + }, + { + label: 'Include Domains', + name: 'includeDomains', + type: 'string', + rows: 4, + optional: true, + additionalParams: true, + description: + 'List of domains to include in the search, separated by comma. If specified, results will only come from these domains.' + }, + { + label: 'Exclude Domains', + name: 'excludeDomains', + type: 'string', + rows: 4, + optional: true, + additionalParams: true, + description: + 'List of domains to exclude in the search, separated by comma. If specified, results will not include any from these domains.' + }, + { + label: 'Start Crawl Date', + name: 'startCrawlDate', + type: 'string', + optional: true, + additionalParams: true, + placeholder: '2023-01-01T00:00:00.000Z', + description: + 'Crawl date refers to the date that Exa discovered a link. Results will include links that were crawled after this date. Must be specified in ISO 8601 format.' + }, + { + label: 'End Crawl Date', + name: 'endCrawlDate', + type: 'string', + optional: true, + additionalParams: true, + placeholder: '2023-12-31T00:00:00.000Z', + description: + 'Crawl date refers to the date that Exa discovered a link. Results will include links that were crawled before this date. Must be specified in ISO 8601 format.' + }, + { + label: 'Start Published Date', + name: 'startPublishedDate', + type: 'string', + optional: true, + additionalParams: true, + placeholder: '2023-01-01T00:00:00.000Z', + description: 'Only links with a published date after this will be returned. Must be specified in ISO 8601 format.' + }, + { + label: 'End Published Date', + name: 'endPublishedDate', + type: 'string', + optional: true, + additionalParams: true, + placeholder: '2023-12-31T00:00:00.000Z', + description: 'Only links with a published date before this will be returned. Must be specified in ISO 8601 format.' + } + ] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['exaSearchApi'] + } + this.baseClasses = [this.type, ...getBaseClasses(ExaSearchResults)] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const description = nodeData.inputs?.description as string + const numResults = nodeData.inputs?.numResults as string + const type = nodeData.inputs?.type as string + const useAutoprompt = nodeData.inputs?.useAutoprompt as boolean + const category = nodeData.inputs?.category as string + const includeDomains = nodeData.inputs?.includeDomains as string + const excludeDomains = nodeData.inputs?.excludeDomains as string + const startCrawlDate = nodeData.inputs?.startCrawlDate as string + const endCrawlDate = nodeData.inputs?.endCrawlDate as string + const startPublishedDate = nodeData.inputs?.startPublishedDate as string + const endPublishedDate = nodeData.inputs?.endPublishedDate as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const exaSearchApiKey = getCredentialParam('exaSearchApiKey', credentialData, nodeData) + + const tool = new ExaSearchResults({ + client: new Exa(exaSearchApiKey), + searchArgs: { + numResults: numResults ? parseFloat(numResults) : undefined, + type: type || undefined, + useAutoprompt: useAutoprompt || undefined, + category: category || undefined, + includeDomains: includeDomains ? includeDomains.split(',') : undefined, + excludeDomains: excludeDomains ? excludeDomains.split(',') : undefined, + startCrawlDate: startCrawlDate || undefined, + endCrawlDate: endCrawlDate || undefined, + startPublishedDate: startPublishedDate || undefined, + endPublishedDate: endPublishedDate || undefined + } + }) + + if (description) tool.description = description + + return tool + } +} + +module.exports = { nodeClass: ExaSearch_Tools } diff --git a/packages/components/nodes/tools/ExaSearch/exa.svg b/packages/components/nodes/tools/ExaSearch/exa.svg new file mode 100644 index 00000000000..d745f78316e --- /dev/null +++ b/packages/components/nodes/tools/ExaSearch/exa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 3b5e113a7cd..289783eb37e 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -38,6 +38,7 @@ "@langchain/cohere": "^0.0.7", "@langchain/community": "^0.0.43", "@langchain/core": "^0.1.63", + "@langchain/exa": "^0.0.5", "@langchain/google-genai": "^0.0.10", "@langchain/google-vertexai": "^0.0.5", "@langchain/groq": "^0.0.8", @@ -68,6 +69,7 @@ "css-what": "^6.1.0", "d3-dsv": "2", "dotenv": "^16.0.0", + "exa-js": "^1.0.12", "express": "^4.17.3", "faiss-node": "^0.5.1", "fast-json-patch": "^3.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5039b68dbac..621908b846c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,6 +135,9 @@ importers: '@langchain/core': specifier: ^0.1.63 version: 0.1.63 + '@langchain/exa': + specifier: ^0.0.5 + version: 0.0.5(encoding@0.1.13) '@langchain/google-genai': specifier: ^0.0.10 version: 0.0.10 @@ -225,6 +228,9 @@ importers: dotenv: specifier: ^16.0.0 version: 16.4.5 + exa-js: + specifier: ^1.0.12 + version: 1.0.12(encoding@0.1.13) express: specifier: ^4.17.3 version: 4.18.3 @@ -3586,6 +3592,10 @@ packages: resolution: { integrity: sha512-+fjyYi8wy6x1P+Ee1RWfIIEyxd9Ee9jksEwvrggPwwI/p45kIDTdYTblXsM13y4mNWTiACyLSdbwnPaxxdoz+w== } engines: { node: '>=18' } + '@langchain/exa@0.0.5': + resolution: { integrity: sha512-KXNCYLxKs6rDGw+jcrFqE4CrIooUgzU0ip0k76YFptvMPrqLpNurYyqr5mAys0qn2vFavFfC3eJV/wrZ602EfA== } + engines: { node: '>=18' } + '@langchain/google-common@0.0.9': resolution: { integrity: sha512-jP7vIgsigUSYYVyT5hej4rg8fV8sutxI6Vm2B2xQGNwUXiCQIDPfA9bmlGyXPWomILKB21dRUqNkun/AMYAWvA== } engines: { node: '>=18' } @@ -8131,6 +8141,9 @@ packages: resolution: { integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== } engines: { node: '>=0.8.x' } + exa-js@1.0.12: + resolution: { integrity: sha512-4oDvjl1966qy1BwjuGm/q/k2gZomS8WhpcuiXyn672cTmEfaRIwQnAbXBznuqzT1WaWeHfJXGTeeboaW41OCiw== } + execa@0.2.2: resolution: { integrity: sha512-zmBGzLd3nhA/NB9P7VLoceAO6vyYPftvl809Vjwe5U2fYI9tYWbeKqP3wZlAw9WS+znnkogf/bhSU+Gcn2NbkQ== } engines: { node: '>=0.12' } @@ -19827,6 +19840,13 @@ snapshots: zod: 3.22.4 zod-to-json-schema: 3.22.5(zod@3.22.4) + '@langchain/exa@0.0.5(encoding@0.1.13)': + dependencies: + '@langchain/core': 0.1.63 + exa-js: 1.0.12(encoding@0.1.13) + transitivePeerDependencies: + - encoding + '@langchain/google-common@0.0.9(zod@3.22.4)': dependencies: '@langchain/core': 0.1.63 @@ -25923,6 +25943,12 @@ snapshots: events@3.3.0: {} + exa-js@1.0.12(encoding@0.1.13): + dependencies: + cross-fetch: 4.0.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + execa@0.2.2: dependencies: cross-spawn-async: 2.2.5 From 74aabc61bd0098d81960afde421350c90448d5fa Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 30 May 2024 22:19:20 +0100 Subject: [PATCH 2/2] add exa svg --- packages/components/nodes/tools/ExaSearch/exa.svg | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/tools/ExaSearch/exa.svg b/packages/components/nodes/tools/ExaSearch/exa.svg index d745f78316e..8f7be51c96e 100644 --- a/packages/components/nodes/tools/ExaSearch/exa.svg +++ b/packages/components/nodes/tools/ExaSearch/exa.svg @@ -1 +1,3 @@ - \ No newline at end of file + + +