diff --git a/docker/.env.example b/docker/.env.example index 174a9d692724b036f0d297a0aeddafcc9d6b9345..a38b4c5a293c81f1fc7ca7e6a0092b76db4e627e 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -241,4 +241,7 @@ GID='1000' # AGENT_SERPER_DEV_KEY= #------ Bing Search ----------- https://portal.azure.com/ -# AGENT_BING_SEARCH_API_KEY= \ No newline at end of file +# AGENT_BING_SEARCH_API_KEY= + +#------ Serply.io ----------- https://serply.io/ +# AGENT_SERPLY_API_KEY= diff --git a/docker/HOW_TO_USE_DOCKER.md b/docker/HOW_TO_USE_DOCKER.md index fed16a2747ea9bd113073a745112ccdacc34c179..f570dce900c101b84d6194238e6bf20efe363786 100644 --- a/docker/HOW_TO_USE_DOCKER.md +++ b/docker/HOW_TO_USE_DOCKER.md @@ -89,7 +89,6 @@ mintplexlabs/anythingllm; <tr> <td> Docker Compose</td> <td> - version: '3.8' services: anythingllm: @@ -116,6 +115,7 @@ mintplexlabs/anythingllm; - TTS_PROVIDER=native - PASSWORDMINCHAR=8 - AGENT_SERPER_DEV_KEY="SERPER DEV API KEY" + - AGENT_SERPLY_API_KEY="Serply.io API KEY" volumes: - anythingllm_storage:/app/server/storage restart: always diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/SearchProviderOptions/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/SearchProviderOptions/index.jsx index 3d579ed43ae18a8a7e24a6f32b7eb37df68869ab..58ceb844741919d3f22f468c0c9818b2d4ec9b0f 100644 --- a/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/SearchProviderOptions/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/SearchProviderOptions/index.jsx @@ -147,3 +147,38 @@ export function BingSearchOptions({ settings }) { </> ); } + +export function SerplySearchOptions({ settings }) { + return ( + <> + <p className="text-sm text-white/60 my-2"> + You can get a free API key{" "} + <a + href="https://serply.io" + target="_blank" + rel="noreferrer" + className="text-blue-300 underline" + > + from Serply.io. + </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-4"> + API Key + </label> + <input + type="password" + name="env::AgentSerplyApiKey" + 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="Serply API Key" + defaultValue={settings?.AgentSerplyApiKey ? "*".repeat(20) : ""} + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> + </div> + </> + ); +} diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/icons/serply.png b/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/icons/serply.png new file mode 100644 index 0000000000000000000000000000000000000000..8ac0ef7c00d094dfa8f85ad48fd02eb2fc209742 Binary files /dev/null and b/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/icons/serply.png differ diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/index.jsx index 8e8f054ea08c532d7a506a1aefdcf9ce6ad171f5..0bbc053ab83b77ac6474bb6784f01ca7fe938037 100644 --- a/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/WebSearchSelection/index.jsx @@ -3,12 +3,14 @@ import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png"; 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 { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react"; import SearchProviderItem from "./SearchProviderItem"; import { SerperDotDevOptions, GoogleSearchOptions, BingSearchOptions, + SerplySearchOptions } from "./SearchProviderOptions"; const SEARCH_PROVIDERS = [ @@ -44,6 +46,14 @@ const SEARCH_PROVIDERS = [ description: "Web search powered by the Bing Search API. Free for 1000 queries per month.", }, + { + name: "Serply.io", + value: "serply-engine", + logo: SerplySearchIcon, + options: (settings) => <SerplySearchOptions settings={settings} />, + description: + "Serply.io web-search. Free account with a 100 calls/month forever.", + }, ]; export default function AgentWebSearchSelection({ diff --git a/server/.env.example b/server/.env.example index 6148d594f925e866bf8db40dff76d7a929e47f01..a88a8a039b09ec0b4298e8c9bd7a866c2fd0862c 100644 --- a/server/.env.example +++ b/server/.env.example @@ -238,3 +238,6 @@ TTS_PROVIDER="native" #------ Bing Search ----------- https://portal.azure.com/ # AGENT_BING_SEARCH_API_KEY= + +#------ Serply.io ----------- https://serply.io/ +# AGENT_SERPLY_API_KEY= diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index ac0523198479895f8d13ec448516dd28509d10fb..3cb0456f4293b8edff01ce4449fc8202f7efc960 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -71,7 +71,7 @@ const SystemSettings = { try { if (update === "none") return null; if ( - !["google-search-engine", "serper-dot-dev", "bing-search"].includes( + !["google-search-engine", "serper-dot-dev", "bing-search", "serply-engine"].includes( update ) ) @@ -176,6 +176,7 @@ const SystemSettings = { 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, }; }, diff --git a/server/utils/agents/aibitat/plugins/web-browsing.js b/server/utils/agents/aibitat/plugins/web-browsing.js index b30688f174904da5e8f0762e14133274b334c511..00b004cbc4b4cd5736266742fb0037993af9d5b4 100644 --- a/server/utils/agents/aibitat/plugins/web-browsing.js +++ b/server/utils/agents/aibitat/plugins/web-browsing.js @@ -68,6 +68,9 @@ const webBrowsing = { case "bing-search": engine = "_bingWebSearch"; break; + case "serply-engine": + engine = "_serplyEngine"; + break; default: engine = "_googleSearchEngine"; } @@ -218,6 +221,72 @@ const webBrowsing = { return `No information was found online for the search query.`; return JSON.stringify(searchResponse); }, + _serplyEngine: async function (query, language = "en", hl = "us", limit = 100, device_type = "desktop", proxy_location = "US") { + // query (str): The query to search for + // hl (str): Host Language code to display results in (reference https://developers.google.com/custom-search/docs/xml_results?hl=en#wsInterfaceLanguages) + // limit (int): The maximum number of results to return [10-100, defaults to 100] + // device_type: get results based on desktop/mobile (defaults to desktop) + + if (!process.env.AGENT_SERPLY_API_KEY) { + this.super.introspect( + `${this.caller}: I can't use Serply.io searching because the user has not defined the required API key.\nVisit: https://serply.io 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 Serply to search for "${ + query.length > 100 ? `${query.slice(0, 100)}...` : query + }"` + ); + + const params = new URLSearchParams({ + q: query, + language: language, + hl, + gl: proxy_location.toUpperCase() + }) + const url = `https://api.serply.io/v1/search/${params.toString()}` + const { response, error } = await fetch( + url, + { + method: "GET", + headers: { + "X-API-KEY": process.env.AGENT_SERPLY_API_KEY, + "Content-Type": "application/json", + "User-Agent": "anything-llm", + "X-Proxy-Location": proxy_location, + "X-User-Agent": device_type + } + } + ) + .then((res) => res.json()) + .then((data) => { + if (data?.message === "Unauthorized"){ + return { response: null, error: "Unauthorized. Please double check your AGENT_SERPLY_API_KEY" }; + } + 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, link, description } = searchResult; + data.push({ + title, + link, + snippet: description, + }); + }); + + if (data.length === 0) + return `No information was found online for the search query.`; + return JSON.stringify(data); + }, }); }, }; diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 1a0e710a97561e2cc04e6952da9c9d669adaad35..3f2baf7e3ef0ade2fa2b074e31a6683264091537 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -403,6 +403,10 @@ const KEY_MAPPING = { envKey: "AGENT_BING_SEARCH_API_KEY", checks: [], }, + AgentSerplyApiKey: { + envKey: "AGENT_SERPLY_API_KEY", + checks: [], + }, // TTS/STT Integration ENVS TextToSpeechProvider: { @@ -769,6 +773,7 @@ async function dumpENV() { "AGENT_GSE_KEY", "AGENT_SERPER_DEV_KEY", "AGENT_BING_SEARCH_API_KEY", + "AGENT_SERPLY_API_KEY" ]; // Simple sanitization of each value to prevent ENV injection via newline or quote escaping.