diff --git a/packages/components/credentials/ExaSearchApi.credential.ts b/packages/components/credentials/ExaSearchApi.credential.ts
new file mode 100644
index 000000000..20f360c42
--- /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 000000000..9cfafadee
--- /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 000000000..8f7be51c9
--- /dev/null
+++ b/packages/components/nodes/tools/ExaSearch/exa.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/components/package.json b/packages/components/package.json
index 3b5e113a7..289783eb3 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 5039b68db..621908b84 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