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: [],