diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx index 1e5349857666b5a679d4b9b9364e7175b9c17b37..2dae3f7db4d3ba94d7f1ea57b995def972c21fae 100644 --- a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx @@ -281,3 +281,38 @@ export function SearXNGOptions({ settings }) { </div> ); } + +export function TavilySearchOptions({ settings }) { + return ( + <> + <p className="text-sm text-white/60 my-2"> + You can get an API key{" "} + <a + href="https://tavily.com/" + target="_blank" + rel="noreferrer" + className="text-blue-300 underline" + > + from Tavily. + </a> + </p> + <div className="flex gap-x-4"> + <div className="flex flex-col w-60"> + <label className="text-white text-sm font-semibold block mb-3"> + API Key + </label> + <input + type="password" + name="env::AgentTavilyApiKey" + className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5" + placeholder="Tavily API Key" + defaultValue={settings?.AgentTavilyApiKey ? "*".repeat(20) : ""} + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> + </div> + </> + ); +} diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/tavily.svg b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/tavily.svg new file mode 100644 index 0000000000000000000000000000000000000000..ce1551057794916f01da9d5d4f6a5503a1f9df15 --- /dev/null +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/tavily.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" zoomAndPan="magnify" viewBox="0 0 375 374.999991" height="500" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="d0348dc115"><path d="M 109.378906 231.132812 L 146.484375 231.132812 L 146.484375 268.238281 L 109.378906 268.238281 Z M 109.378906 231.132812 " clip-rule="nonzero"/></clipPath><clipPath id="a28b194a7a"><path d="M 127.933594 231.132812 C 117.6875 231.132812 109.378906 239.4375 109.378906 249.6875 C 109.378906 259.933594 117.6875 268.238281 127.933594 268.238281 C 138.179688 268.238281 146.484375 259.933594 146.484375 249.6875 C 146.484375 239.4375 138.179688 231.132812 127.933594 231.132812 Z M 127.933594 231.132812 " clip-rule="nonzero"/></clipPath></defs><path stroke-linecap="round" transform="matrix(0, -2.578223, 2.578223, 0, 113.745458, 254.140061)" fill="none" stroke-linejoin="miter" d="M 5.499573 5.500038 L 79.114962 5.500038 " stroke="#f25022" stroke-width="11" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(0, -2.578223, 2.578223, 0, 113.745458, 254.140061)" fill="none" stroke-linejoin="round" d="M 59.865692 -10.999336 L 81.864858 5.500038 L 59.865692 21.999412 " stroke="#f25022" stroke-width="11" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(2.578223, -0.000251357, 0.000251357, 2.578223, 126.828174, 239.987372)" fill="none" stroke-linejoin="miter" d="M 5.500751 5.50068 L 72.398214 5.499627 " stroke="#ffb901" stroke-width="11" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(2.578223, -0.000251357, 0.000251357, 2.578223, 126.828174, 239.987372)" fill="none" stroke-linejoin="round" d="M 53.149037 -11.000109 L 75.148109 5.499895 L 53.14885 22.000154 " stroke="#ffb901" stroke-width="11" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(-1.692446, 1.944957, -1.944957, -1.692446, 134.219043, 258.208373)" fill="none" stroke-linejoin="miter" d="M 4.499518 4.49999 L 38.441562 4.500107 " stroke="#04a3ec" stroke-width="9" stroke-opacity="1" stroke-miterlimit="4"/><path stroke-linecap="round" transform="matrix(-1.692446, 1.944957, -1.944957, -1.692446, 134.219043, 258.208373)" fill="none" stroke-linejoin="round" d="M 22.691248 -9.000192 L 40.69038 4.49943 L 22.68978 17.999994 " stroke="#04a3ec" stroke-width="9" stroke-opacity="1" stroke-miterlimit="4"/><g clip-path="url(#d0348dc115)"><g clip-path="url(#a28b194a7a)"><path fill="#32b37f" d="M 109.378906 231.132812 L 146.484375 231.132812 L 146.484375 268.238281 L 109.378906 268.238281 Z M 109.378906 231.132812 " fill-opacity="1" fill-rule="nonzero"/></g></g></svg> \ No newline at end of file diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx index 345d3ef05e29cd2040054d600e2316baf8168ee0..cba4397c7990191eab55d6921a0af2a12abf3d98 100644 --- a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx @@ -7,6 +7,7 @@ import SerperDotDevIcon from "./icons/serper.png"; import BingSearchIcon from "./icons/bing.png"; import SerplySearchIcon from "./icons/serply.png"; import SearXNGSearchIcon from "./icons/searxng.png"; +import TavilySearchIcon from "./icons/tavily.svg"; import { CaretUpDown, MagnifyingGlass, @@ -22,6 +23,7 @@ import { BingSearchOptions, SerplySearchOptions, SearXNGOptions, + TavilySearchOptions, } from "./SearchProviderOptions"; const SEARCH_PROVIDERS = [ @@ -81,6 +83,14 @@ const SEARCH_PROVIDERS = [ description: "Free, open-source, internet meta-search engine with no tracking.", }, + { + name: "Tavily Search", + value: "tavily-search", + logo: TavilySearchIcon, + options: (settings) => <TavilySearchOptions settings={settings} />, + description: + "Tavily Search API. Offers a free tier with 1000 queries per month.", + }, ]; export default function AgentWebSearchSelection({ diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index c510001917def88def2326b3b350f297d5f4f367..e4c0fa9d9bfb16410ced246a81fedd5cb236b418 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -103,6 +103,7 @@ const SystemSettings = { "bing-search", "serply-engine", "searxng-engine", + "tavily-search", ].includes(update) ) throw new Error("Invalid SERP provider."); @@ -242,6 +243,7 @@ const SystemSettings = { AgentBingSearchApiKey: !!process.env.AGENT_BING_SEARCH_API_KEY || null, AgentSerplyApiKey: !!process.env.AGENT_SERPLY_API_KEY || null, AgentSearXNGApiUrl: process.env.AGENT_SEARXNG_API_URL || null, + AgentTavilyApiKey: !!process.env.AGENT_TAVILY_API_KEY || null, }; }, diff --git a/server/utils/agents/aibitat/plugins/web-browsing.js b/server/utils/agents/aibitat/plugins/web-browsing.js index 76849056eef6ee73d981b33d2b1fa69c2ef8b784..31e06cab00600925424cb6d2f21f9989fd3c6a9a 100644 --- a/server/utils/agents/aibitat/plugins/web-browsing.js +++ b/server/utils/agents/aibitat/plugins/web-browsing.js @@ -77,6 +77,9 @@ const webBrowsing = { case "searxng-engine": engine = "_searXNGEngine"; break; + case "tavily-search": + engine = "_tavilySearch"; + break; default: engine = "_googleSearchEngine"; } @@ -436,6 +439,59 @@ const webBrowsing = { }); }); + if (data.length === 0) + return `No information was found online for the search query.`; + this.super.introspect( + `${this.caller}: I found ${data.length} results - looking over them now.` + ); + return JSON.stringify(data); + }, + _tavilySearch: async function (query) { + if (!process.env.AGENT_TAVILY_API_KEY) { + this.super.introspect( + `${this.caller}: I can't use Tavily searching because the user has not defined the required API key.\nVisit: https://tavily.com/ 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 Tavily to search for "${ + query.length > 100 ? `${query.slice(0, 100)}...` : query + }"` + ); + + const url = "https://api.tavily.com/search"; + const { response, error } = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + api_key: process.env.AGENT_TAVILY_API_KEY, + query: query, + }), + }) + .then((res) => res.json()) + .then((data) => { + return { response: data, error: null }; + }) + .catch((e) => { + 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, content } = searchResult; + data.push({ + title, + link: url, + snippet: content, + }); + }); + if (data.length === 0) return `No information was found online for the search query.`; this.super.introspect( diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index e898d4b098db5c8cb0510a372a1787daa512ef42..db5cfe0e3a15840cab65029ea8e575134d572eb0 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -469,6 +469,10 @@ const KEY_MAPPING = { envKey: "AGENT_SEARXNG_API_URL", checks: [], }, + AgentTavilyApiKey: { + envKey: "AGENT_TAVILY_API_KEY", + checks: [], + }, // TTS/STT Integration ENVS TextToSpeechProvider: {