diff --git a/docker/.env.example b/docker/.env.example index c84f838c1..48068cfdc 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -328,6 +328,9 @@ GID='1000' #------ Tavily ----------- https://www.tavily.com/ # AGENT_TAVILY_API_KEY= +#------ Exa Search ----------- https://www.exa.ai/ +# AGENT_EXA_API_KEY= + ########################################### ######## Other Configurations ############ ########################################### diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx index bda5ef0c0..cce0dd11b 100644 --- a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx @@ -327,3 +327,38 @@ export function DuckDuckGoOptions() { ); } + +export function ExaSearchOptions({ settings }) { + return ( + <> +

+ You can get an API key{" "} + + from Exa. + +

+
+
+ + +
+
+ + ); +} diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/exa.png b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/exa.png new file mode 100644 index 000000000..5794b0667 Binary files /dev/null and b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/exa.png differ diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx index 6dc45d786..f52d793ac 100644 --- a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx @@ -9,6 +9,7 @@ import SerplySearchIcon from "./icons/serply.png"; import SearXNGSearchIcon from "./icons/searxng.png"; import TavilySearchIcon from "./icons/tavily.svg"; import DuckDuckGoIcon from "./icons/duckduckgo.png"; +import ExaIcon from "./icons/exa.png"; import { CaretUpDown, MagnifyingGlass, @@ -26,6 +27,7 @@ import { SearXNGOptions, TavilySearchOptions, DuckDuckGoOptions, + ExaSearchOptions, } from "./SearchProviderOptions"; const SEARCH_PROVIDERS = [ @@ -42,8 +44,7 @@ const SEARCH_PROVIDERS = [ value: "duckduckgo-engine", logo: DuckDuckGoIcon, options: () => , - description: - "Free and privacy-focused web search using DuckDuckGo's HTML interface.", + description: "Free and privacy-focused web search using DuckDuckGo.", }, { name: "Google Search Engine", @@ -100,6 +101,13 @@ const SEARCH_PROVIDERS = [ description: "Tavily Search API. Offers a free tier with 1000 queries per month.", }, + { + name: "Exa Search", + value: "exa-search", + logo: ExaIcon, + options: (settings) => , + description: "AI-powered search engine optimized for LLM use cases.", + }, ]; export default function AgentWebSearchSelection({ diff --git a/server/.env.example b/server/.env.example index 0ccefd5b9..03c5382fb 100644 --- a/server/.env.example +++ b/server/.env.example @@ -325,6 +325,9 @@ TTS_PROVIDER="native" #------ Tavily ----------- https://www.tavily.com/ # AGENT_TAVILY_API_KEY= +#------ Exa Search ----------- https://www.exa.ai/ +# AGENT_EXA_API_KEY= + ########################################### ######## Other Configurations ############ ########################################### diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 999e75029..961ccc567 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -113,6 +113,7 @@ const SystemSettings = { "searxng-engine", "tavily-search", "duckduckgo-engine", + "exa-search", ].includes(update) ) throw new Error("Invalid SERP provider."); @@ -282,6 +283,7 @@ const SystemSettings = { AgentSerplyApiKey: !!process.env.AGENT_SERPLY_API_KEY || null, AgentSearXNGApiUrl: process.env.AGENT_SEARXNG_API_URL || null, AgentTavilyApiKey: !!process.env.AGENT_TAVILY_API_KEY || null, + AgentExaApiKey: !!process.env.AGENT_EXA_API_KEY || null, // -------------------------------------------------------- // Compliance Settings diff --git a/server/utils/agents/aibitat/plugins/web-browsing.js b/server/utils/agents/aibitat/plugins/web-browsing.js index 8a0357c4c..2825b1068 100644 --- a/server/utils/agents/aibitat/plugins/web-browsing.js +++ b/server/utils/agents/aibitat/plugins/web-browsing.js @@ -90,6 +90,9 @@ const webBrowsing = { case "duckduckgo-engine": engine = "_duckDuckGoEngine"; break; + case "exa-search": + engine = "_exaSearch"; + break; default: engine = "_googleSearchEngine"; } @@ -643,6 +646,73 @@ const webBrowsing = { return `No information was found online for the search query.`; } + const result = JSON.stringify(data); + this.super.introspect( + `${this.caller}: I found ${data.length} results - reviewing the results now. (~${this.countTokens(result)} tokens)` + ); + return result; + }, + _exaSearch: async function (query) { + if (!process.env.AGENT_EXA_API_KEY) { + this.super.introspect( + `${this.caller}: I can't use Exa searching because the user has not defined the required API key.\nVisit: https://exa.ai to create the API key.` + ); + return `Search is disabled and no content was found. This functionality is disabled because the user has not set it up yet.`; + } + + this.super.introspect( + `${this.caller}: Using Exa to search for "${ + query.length > 100 ? `${query.slice(0, 100)}...` : query + }"` + ); + + const url = "https://api.exa.ai/search"; + const { response, error } = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": process.env.AGENT_EXA_API_KEY, + }, + body: JSON.stringify({ + query: query, + type: "auto", + numResults: 10, + contents: { + text: true, + }, + }), + }) + .then((res) => { + if (res.ok) return res.json(); + throw new Error( + `${res.status} - ${res.statusText}. params: ${JSON.stringify({ auth: this.middleTruncate(process.env.AGENT_EXA_API_KEY, 5), q: query })}` + ); + }) + .then((data) => { + return { response: data, error: null }; + }) + .catch((e) => { + this.super.handlerProps.log(`Exa Search Error: ${e.message}`); + return { response: null, error: e.message }; + }); + + if (error) + return `There was an error searching for content. ${error}`; + + const data = []; + response.results?.forEach((searchResult) => { + const { title, url, text, publishedDate } = searchResult; + data.push({ + title, + link: url, + snippet: text, + publishedDate, + }); + }); + + if (data.length === 0) + return `No information was found online for the search query.`; + const result = JSON.stringify(data); this.super.introspect( `${this.caller}: I found ${data.length} results - reviewing the results now. (~${this.countTokens(result)} tokens)` diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 175fa8ee3..6bb397c30 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -563,6 +563,10 @@ const KEY_MAPPING = { envKey: "AGENT_TAVILY_API_KEY", checks: [], }, + AgentExaApiKey: { + envKey: "AGENT_EXA_API_KEY", + checks: [], + }, // TTS/STT Integration ENVS TextToSpeechProvider: {