From 8b1ceb30c159cf3a10efa16275bc6849d84e4ea8 Mon Sep 17 00:00:00 2001
From: Timothy Carambat <rambat1010@gmail.com>
Date: Thu, 20 Jun 2024 14:08:00 -0700
Subject: [PATCH] Add support for searXNG search for agents (#1733)

resolves #1367
---
 .vscode/settings.json                         |   2 +
 docker/.env.example                           |   3 +
 .../SearchProviderOptions/index.jsx           |  22 +++++
 .../WebSearchSelection/icons/searxng.png      | Bin 0 -> 7722 bytes
 .../Admin/Agents/WebSearchSelection/index.jsx |  10 +++
 server/.env.example                           |   3 +
 server/models/systemSettings.js               |  10 ++-
 .../agents/aibitat/plugins/web-browsing.js    |  85 +++++++++++++++++-
 server/utils/helpers/updateENV.js             |   4 +
 9 files changed, 132 insertions(+), 7 deletions(-)
 create mode 100644 frontend/src/pages/Admin/Agents/WebSearchSelection/icons/searxng.png

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4930aa2d1..8d924b71c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -32,7 +32,9 @@
     "opendocument",
     "openrouter",
     "Qdrant",
+    "searxng",
     "Serper",
+    "Serply",
     "textgenwebui",
     "togetherai",
     "vectordbs",
diff --git a/docker/.env.example b/docker/.env.example
index a38b4c5a2..71572cc8e 100644
--- a/docker/.env.example
+++ b/docker/.env.example
@@ -245,3 +245,6 @@ GID='1000'
 
 #------ Serply.io ----------- https://serply.io/
 # AGENT_SERPLY_API_KEY=
+
+#------ SearXNG ----------- https://github.com/searxng/searxng
+# AGENT_SEARXNG_API_URL=
\ No newline at end of file
diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx
index 58ceb8447..c5ccd2607 100644
--- a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx
+++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx
@@ -182,3 +182,25 @@ export function SerplySearchOptions({ settings }) {
     </>
   );
 }
+
+export function SearXNGOptions({ settings }) {
+  return (
+    <div className="flex gap-x-4">
+      <div className="flex flex-col w-60">
+        <label className="text-white text-sm font-semibold block mb-4">
+          SearXNG API base URL
+        </label>
+        <input
+          type="url"
+          name="env::AgentSearXNGApiUrl"
+          className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5"
+          placeholder="SearXNG API Key"
+          defaultValue={settings?.AgentSearXNGApiUrl}
+          required={true}
+          autoComplete="off"
+          spellCheck={false}
+        />
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/searxng.png b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/searxng.png
new file mode 100644
index 0000000000000000000000000000000000000000..434e570f8bfe0f8752b186a8fac772e73c40fe18
GIT binary patch
literal 7722
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

literal 0
HcmV?d00001

diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx
index 9650c38fb..438be1114 100644
--- a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx
+++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx
@@ -4,6 +4,7 @@ import GoogleSearchIcon from "./icons/google.png";
 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 {
   CaretUpDown,
   MagnifyingGlass,
@@ -17,6 +18,7 @@ import {
   GoogleSearchOptions,
   BingSearchOptions,
   SerplySearchOptions,
+  SearXNGOptions,
 } from "./SearchProviderOptions";
 
 const SEARCH_PROVIDERS = [
@@ -60,6 +62,14 @@ const SEARCH_PROVIDERS = [
     description:
       "Serply.io web-search. Free account with a 100 calls/month forever.",
   },
+  {
+    name: "SearXNG",
+    value: "searxng-engine",
+    logo: SearXNGSearchIcon,
+    options: (settings) => <SearXNGOptions settings={settings} />,
+    description:
+      "Free, open-source, internet meta-search engine with no tracking.",
+  },
 ];
 
 export default function AgentWebSearchSelection({
diff --git a/server/.env.example b/server/.env.example
index a88a8a039..3a4fb072f 100644
--- a/server/.env.example
+++ b/server/.env.example
@@ -241,3 +241,6 @@ TTS_PROVIDER="native"
 
 #------ Serply.io ----------- https://serply.io/
 # AGENT_SERPLY_API_KEY=
+
+#------ SearXNG ----------- https://github.com/searxng/searxng
+# AGENT_SEARXNG_API_URL=
\ No newline at end of file
diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js
index 8d548c7bc..4d998e819 100644
--- a/server/models/systemSettings.js
+++ b/server/models/systemSettings.js
@@ -76,6 +76,7 @@ const SystemSettings = {
             "serper-dot-dev",
             "bing-search",
             "serply-engine",
+            "searxng-engine",
           ].includes(update)
         )
           throw new Error("Invalid SERP provider.");
@@ -176,10 +177,11 @@ const SystemSettings = {
       // Agent Settings & Configs
       // --------------------------------------------------------
       AgentGoogleSearchEngineId: process.env.AGENT_GSE_CTX || null,
-      AgentGoogleSearchEngineKey: process.env.AGENT_GSE_KEY || null,
-      AgentSerperApiKey: process.env.AGENT_SERPER_DEV_KEY || null,
-      AgentBingSearchApiKey: process.env.AGENT_BING_SEARCH_API_KEY || null,
-      AgentSerplyApiKey: process.env.AGENT_SERPLY_API_KEY || null,
+      AgentGoogleSearchEngineKey: !!process.env.AGENT_GSE_KEY || null,
+      AgentSerperApiKey: !!process.env.AGENT_SERPER_DEV_KEY || null,
+      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,
     };
   },
 
diff --git a/server/utils/agents/aibitat/plugins/web-browsing.js b/server/utils/agents/aibitat/plugins/web-browsing.js
index 81314f178..f4269fe13 100644
--- a/server/utils/agents/aibitat/plugins/web-browsing.js
+++ b/server/utils/agents/aibitat/plugins/web-browsing.js
@@ -71,6 +71,9 @@ const webBrowsing = {
               case "serply-engine":
                 engine = "_serplyEngine";
                 break;
+              case "searxng-engine":
+                engine = "_searXNGEngine";
+                break;
               default:
                 engine = "_googleSearchEngine";
             }
@@ -102,7 +105,7 @@ const webBrowsing = {
                 query.length > 100 ? `${query.slice(0, 100)}...` : query
               }"`
             );
-            const searchResponse = await fetch(searchURL)
+            const data = await fetch(searchURL)
               .then((res) => res.json())
               .then((searchResult) => searchResult?.items || [])
               .then((items) => {
@@ -116,10 +119,15 @@ const webBrowsing = {
               })
               .catch((e) => {
                 console.log(e);
-                return {};
+                return [];
               });
 
-            return JSON.stringify(searchResponse);
+            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);
           },
 
           /**
@@ -176,6 +184,9 @@ 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);
           },
           _bingWebSearch: async function (query) {
@@ -219,6 +230,9 @@ const webBrowsing = {
 
             if (searchResponse.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(searchResponse);
           },
           _serplyEngine: async function (
@@ -293,6 +307,71 @@ 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);
+          },
+          _searXNGEngine: async function (query) {
+            let searchURL;
+            if (!process.env.AGENT_SEARXNG_API_URL) {
+              this.super.introspect(
+                `${this.caller}: I can't use SearXNG searching because the user has not defined the required base URL.\nPlease set this value in the agent skill settings.`
+              );
+              return `Search is disabled and no content was found. This functionality is disabled because the user has not set it up yet.`;
+            }
+
+            try {
+              searchURL = new URL(process.env.AGENT_SEARXNG_API_URL);
+              searchURL.searchParams.append("q", encodeURIComponent(query));
+              searchURL.searchParams.append("format", "json");
+            } catch (e) {
+              this.super.handlerProps.log(`SearXNG Search: ${e.message}`);
+              this.super.introspect(
+                `${this.caller}: I can't use SearXNG searching because the url provided is not a valid URL.`
+              );
+              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 SearXNG to search for "${
+                query.length > 100 ? `${query.slice(0, 100)}...` : query
+              }"`
+            );
+
+            const { response, error } = await fetch(searchURL.toString(), {
+              method: "GET",
+              headers: {
+                "Content-Type": "application/json",
+                "User-Agent": "anything-llm",
+              },
+            })
+              .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 { url, title, content, publishedDate } = searchResult;
+              data.push({
+                title,
+                link: url,
+                snippet: content,
+                publishedDate,
+              });
+            });
+
+            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);
           },
         });
diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js
index 513641917..6b170da3a 100644
--- a/server/utils/helpers/updateENV.js
+++ b/server/utils/helpers/updateENV.js
@@ -407,6 +407,10 @@ const KEY_MAPPING = {
     envKey: "AGENT_SERPLY_API_KEY",
     checks: [],
   },
+  AgentSearXNGApiUrl: {
+    envKey: "AGENT_SEARXNG_API_URL",
+    checks: [],
+  },
 
   // TTS/STT Integration ENVS
   TextToSpeechProvider: {