diff --git a/.vscode/settings.json b/.vscode/settings.json index e6b76c9e9..c4e7a4b9c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -46,6 +46,7 @@ "royalblue", "SearchApi", "searxng", + "SerpApi", "Serper", "Serply", "streamable", diff --git a/docker/.env.example b/docker/.env.example index 379885e62..125a85a17 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -331,6 +331,10 @@ GID='1000' # AGENT_SEARCHAPI_API_KEY= # AGENT_SEARCHAPI_ENGINE=google +#------ SerpApi ----------- https://serpapi.com/ +# AGENT_SERPAPI_API_KEY= +# AGENT_SERPAPI_ENGINE=google + #------ Serper.dev ----------- https://serper.dev/ # AGENT_SERPER_DEV_KEY= diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx index cce0dd11b..f26e3ede9 100644 --- a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx @@ -50,6 +50,80 @@ export function GoogleSearchOptions({ settings }) { ); } +const SerpApiEngines = [ + { name: "Google Search", value: "google" }, + { name: "Google Images", value: "google_images_light" }, + { name: "Google Jobs", value: "google_jobs" }, + { name: "Google Maps", value: "google_maps" }, + { name: "Google News", value: "google_news_light" }, + { name: "Google Patents", value: "google_patents" }, + { name: "Google Scholar", value: "google_scholar" }, + { name: "Google Shopping", value: "google_shopping_light" }, + { name: "Amazon", value: "amazon" }, + { name: "Baidu", value: "baidu" }, +]; +export function SerpApiOptions({ settings }) { + return ( + <> +

+ Get a free API key{" "} + + from SerpApi. + +

+
+
+ + +
+
+ + + {/* */} +
+
+ + ); +} + const SearchApiEngines = [ { name: "Google Search", value: "google" }, { name: "Google Maps", value: "google_maps" }, diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/serpapi.png b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/serpapi.png new file mode 100644 index 000000000..6adc8f512 Binary files /dev/null and b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/serpapi.png differ diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx index f52d793ac..8a7f8ec70 100644 --- a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import Admin from "@/models/admin"; import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png"; import GoogleSearchIcon from "./icons/google.png"; +import SerpApiIcon from "./icons/serpapi.png"; import SearchApiIcon from "./icons/searchapi.png"; import SerperDotDevIcon from "./icons/serper.png"; import BingSearchIcon from "./icons/bing.png"; @@ -19,6 +20,7 @@ import { import SearchProviderItem from "./SearchProviderItem"; import WebSearchImage from "@/media/agents/scrape-websites.png"; import { + SerpApiOptions, SearchApiOptions, SerperDotDevOptions, GoogleSearchOptions, @@ -54,6 +56,14 @@ const SEARCH_PROVIDERS = [ description: "Web search powered by a custom Google Search Engine. Free for 100 queries per day.", }, + { + name: "SerpApi", + value: "serpapi", + logo: SerpApiIcon, + options: (settings) => , + description: + "Scrape Google and several other search engines with SerpApi. 250 free searches every month, and then paid.", + }, { name: "SearchApi", value: "searchapi", diff --git a/server/.env.example b/server/.env.example index fb47d8d70..49057ab65 100644 --- a/server/.env.example +++ b/server/.env.example @@ -325,6 +325,10 @@ TTS_PROVIDER="native" # AGENT_GSE_KEY= # AGENT_GSE_CTX= +#------ SerpApi ----------- https://serpapi.com/ +# AGENT_SERPAPI_API_KEY= +# AGENT_SERPAPI_ENGINE=google + #------ SearchApi.io ----------- https://www.searchapi.io/ # AGENT_SEARCHAPI_API_KEY= # AGENT_SEARCHAPI_ENGINE=google diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 3a7a4b215..0db31e66f 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -106,6 +106,7 @@ const SystemSettings = { if ( ![ "google-search-engine", + "serpapi", "searchapi", "serper-dot-dev", "bing-search", @@ -276,6 +277,8 @@ const SystemSettings = { // -------------------------------------------------------- AgentGoogleSearchEngineId: process.env.AGENT_GSE_CTX || null, AgentGoogleSearchEngineKey: !!process.env.AGENT_GSE_KEY || null, + AgentSerpApiKey: !!process.env.AGENT_SERPAPI_API_KEY || null, + AgentSerpApiEngine: process.env.AGENT_SERPAPI_ENGINE || "google", AgentSearchApiKey: !!process.env.AGENT_SEARCHAPI_API_KEY || null, AgentSearchApiEngine: process.env.AGENT_SEARCHAPI_ENGINE || "google", AgentSerperApiKey: !!process.env.AGENT_SERPER_DEV_KEY || null, diff --git a/server/utils/agents/aibitat/plugins/web-browsing.js b/server/utils/agents/aibitat/plugins/web-browsing.js index 2825b1068..6d8dae3b6 100644 --- a/server/utils/agents/aibitat/plugins/web-browsing.js +++ b/server/utils/agents/aibitat/plugins/web-browsing.js @@ -69,6 +69,9 @@ const webBrowsing = { case "google-search-engine": engine = "_googleSearchEngine"; break; + case "serpapi": + engine = "_serpApi"; + break; case "searchapi": engine = "_searchApi"; break; @@ -170,6 +173,255 @@ const webBrowsing = { return result; }, + /** + * Use SerpApi + * SerpApi supports dozens of search engines across the major platforms including Google, DuckDuckGo, Bing, eBay, Amazon, Baidu, Yandex, and more. + * https://serpapi.com/ + */ + _serpApi: async function (query) { + if (!process.env.AGENT_SERPAPI_API_KEY) { + this.super.introspect( + `${this.caller}: I can't use SerpApi searching because the user has not defined the required API key.\nVisit: https://serpapi.com/ to create the API key for free.` + ); + 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 SerpApi to search for "${ + query.length > 100 ? `${query.slice(0, 100)}...` : query + }"` + ); + + const engine = process.env.AGENT_SERPAPI_ENGINE; + const queryParamKey = engine === "amazon" ? "k" : "q"; + + const params = new URLSearchParams({ + engine: engine, + [queryParamKey]: query, + api_key: process.env.AGENT_SERPAPI_API_KEY, + }); + + const url = `https://serpapi.com/search.json?${params.toString()}`; + const { response, error } = await fetch(url, { + method: "GET", + headers: {}, + }) + .then((res) => { + if (res.ok) return res.json(); + throw new Error( + `${res.status} - ${res.statusText}. params: ${JSON.stringify({ auth: this.middleTruncate(process.env.AGENT_SERPAPI_API_KEY, 5), q: query })}` + ); + }) + .then((data) => { + return { response: data, error: null }; + }) + .catch((e) => { + this.super.handlerProps.log(`SerpApi Error: ${e.message}`); + return { response: null, error: e.message }; + }); + if (error) + return `There was an error searching for content. ${error}`; + + const data = []; + + switch (engine) { + case "google": + if (response.hasOwnProperty("knowledge_graph")) + data.push(response.knowledge_graph); + if (response.hasOwnProperty("answer_box")) + data.push(response.answer_box); + response.organic_results?.forEach((searchResult) => { + const { title, link, snippet } = searchResult; + data.push({ + title, + link, + snippet, + }); + }); + response.local_results?.forEach((searchResult) => { + const { + title, + rating, + reviews, + description, + address, + website, + extensions, + } = searchResult; + data.push({ + title, + rating, + reviews, + description, + address, + website, + extensions, + }); + }); + case "google_maps": + response.local_results?.slice(0, 10).forEach((searchResult) => { + const { + title, + rating, + reviews, + description, + address, + website, + extensions, + } = searchResult; + data.push({ + title, + rating, + reviews, + description, + address, + website, + extensions, + }); + }); + case "google_images_light": + response.images_results + ?.slice(0, 10) + .forEach((searchResult) => { + const { title, source, link, thumbnail } = searchResult; + data.push({ + title, + source, + link, + thumbnail, + }); + }); + case "google_shopping_light": + response.shopping_results + ?.slice(0, 10) + .forEach((searchResult) => { + const { + title, + source, + price, + rating, + reviews, + snippet, + thumbnail, + product_link, + } = searchResult; + data.push({ + title, + source, + price, + rating, + reviews, + snippet, + thumbnail, + product_link, + }); + }); + case "google_news_light": + response.news_results?.slice(0, 10).forEach((searchResult) => { + const { title, link, source, thumbnail, snippet, date } = + searchResult; + data.push({ + title, + link, + source, + thumbnail, + snippet, + date, + }); + }); + case "google_jobs": + response.jobs_results?.forEach((searchResult) => { + const { + title, + company_name, + location, + description, + apply_options, + extensions, + } = searchResult; + data.push({ + title, + company_name, + location, + description, + apply_options, + extensions, + }); + }); + case "google_patents": + response.organic_results?.forEach((searchResult) => { + const { + title, + patent_link, + snippet, + inventor, + assignee, + publication_number, + } = searchResult; + data.push({ + title, + patent_link, + snippet, + inventor, + assignee, + publication_number, + }); + }); + case "google_scholar": + response.organic_results?.forEach((searchResult) => { + const { title, link, snippet, publication_info } = + searchResult; + data.push({ + title, + link, + snippet, + publication_info, + }); + }); + case "baidu": + if (response.hasOwnProperty("answer_box")) + data.push(response.answer_box); + response.organic_results?.forEach((searchResult) => { + const { title, link, snippet } = searchResult; + data.push({ + title, + link, + snippet, + }); + }); + case "amazon": + response.organic_results + ?.slice(0, 10) + .forEach((searchResult) => { + const { + title, + rating, + reviews, + price, + link_clean, + thumbnail, + } = searchResult; + data.push({ + title, + rating, + reviews, + price, + link_clean, + thumbnail, + }); + }); + } + + 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)` + ); + return result; + }, + /** * Use SearchApi * SearchApi supports multiple search engines like Google Search, Bing Search, Baidu Search, Google News, YouTube, and many more. diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index c8109efb1..0895879b0 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -549,6 +549,14 @@ const KEY_MAPPING = { envKey: "AGENT_GSE_KEY", checks: [], }, + AgentSerpApiKey: { + envKey: "AGENT_SERPAPI_API_KEY", + checks: [], + }, + AgentSerpApiEngine: { + envKey: "AGENT_SERPAPI_ENGINE", + checks: [], + }, AgentSearchApiKey: { envKey: "AGENT_SEARCHAPI_API_KEY", checks: [],