mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
Add SerpApi web search (#4623)
Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -46,6 +46,7 @@
|
|||||||
"royalblue",
|
"royalblue",
|
||||||
"SearchApi",
|
"SearchApi",
|
||||||
"searxng",
|
"searxng",
|
||||||
|
"SerpApi",
|
||||||
"Serper",
|
"Serper",
|
||||||
"Serply",
|
"Serply",
|
||||||
"streamable",
|
"streamable",
|
||||||
|
|||||||
@@ -331,6 +331,10 @@ GID='1000'
|
|||||||
# AGENT_SEARCHAPI_API_KEY=
|
# AGENT_SEARCHAPI_API_KEY=
|
||||||
# AGENT_SEARCHAPI_ENGINE=google
|
# AGENT_SEARCHAPI_ENGINE=google
|
||||||
|
|
||||||
|
#------ SerpApi ----------- https://serpapi.com/
|
||||||
|
# AGENT_SERPAPI_API_KEY=
|
||||||
|
# AGENT_SERPAPI_ENGINE=google
|
||||||
|
|
||||||
#------ Serper.dev ----------- https://serper.dev/
|
#------ Serper.dev ----------- https://serper.dev/
|
||||||
# AGENT_SERPER_DEV_KEY=
|
# AGENT_SERPER_DEV_KEY=
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<p className="text-sm text-white/60 my-2">
|
||||||
|
Get a free API key{" "}
|
||||||
|
<a
|
||||||
|
href="https://serpapi.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-300 underline"
|
||||||
|
>
|
||||||
|
from SerpApi.
|
||||||
|
</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::AgentSerpApiKey"
|
||||||
|
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||||
|
placeholder="SerpApi API Key"
|
||||||
|
defaultValue={settings?.AgentSerpApiKey ? "*".repeat(20) : ""}
|
||||||
|
required={true}
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col w-60">
|
||||||
|
<label className="text-white text-sm font-semibold block mb-3">
|
||||||
|
Engine
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
name="env::AgentSerpApiEngine"
|
||||||
|
required={true}
|
||||||
|
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||||
|
defaultValue={settings?.AgentSerpApiEngine || "google"}
|
||||||
|
>
|
||||||
|
{SerpApiEngines.map(({ name, value }) => (
|
||||||
|
<option key={name} value={value}>
|
||||||
|
{name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{/* <input
|
||||||
|
type="text"
|
||||||
|
name="env::AgentSerpApiEngine"
|
||||||
|
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||||
|
placeholder="SerpApi engine (Google, Amazon...)"
|
||||||
|
defaultValue={settings?.AgentSerpApiEngine || "google"}
|
||||||
|
required={true}
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const SearchApiEngines = [
|
const SearchApiEngines = [
|
||||||
{ name: "Google Search", value: "google" },
|
{ name: "Google Search", value: "google" },
|
||||||
{ name: "Google Maps", value: "google_maps" },
|
{ name: "Google Maps", value: "google_maps" },
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
@@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
|
|||||||
import Admin from "@/models/admin";
|
import Admin from "@/models/admin";
|
||||||
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
|
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
|
||||||
import GoogleSearchIcon from "./icons/google.png";
|
import GoogleSearchIcon from "./icons/google.png";
|
||||||
|
import SerpApiIcon from "./icons/serpapi.png";
|
||||||
import SearchApiIcon from "./icons/searchapi.png";
|
import SearchApiIcon from "./icons/searchapi.png";
|
||||||
import SerperDotDevIcon from "./icons/serper.png";
|
import SerperDotDevIcon from "./icons/serper.png";
|
||||||
import BingSearchIcon from "./icons/bing.png";
|
import BingSearchIcon from "./icons/bing.png";
|
||||||
@@ -19,6 +20,7 @@ import {
|
|||||||
import SearchProviderItem from "./SearchProviderItem";
|
import SearchProviderItem from "./SearchProviderItem";
|
||||||
import WebSearchImage from "@/media/agents/scrape-websites.png";
|
import WebSearchImage from "@/media/agents/scrape-websites.png";
|
||||||
import {
|
import {
|
||||||
|
SerpApiOptions,
|
||||||
SearchApiOptions,
|
SearchApiOptions,
|
||||||
SerperDotDevOptions,
|
SerperDotDevOptions,
|
||||||
GoogleSearchOptions,
|
GoogleSearchOptions,
|
||||||
@@ -54,6 +56,14 @@ const SEARCH_PROVIDERS = [
|
|||||||
description:
|
description:
|
||||||
"Web search powered by a custom Google Search Engine. Free for 100 queries per day.",
|
"Web search powered by a custom Google Search Engine. Free for 100 queries per day.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "SerpApi",
|
||||||
|
value: "serpapi",
|
||||||
|
logo: SerpApiIcon,
|
||||||
|
options: (settings) => <SerpApiOptions settings={settings} />,
|
||||||
|
description:
|
||||||
|
"Scrape Google and several other search engines with SerpApi. 250 free searches every month, and then paid.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "SearchApi",
|
name: "SearchApi",
|
||||||
value: "searchapi",
|
value: "searchapi",
|
||||||
|
|||||||
@@ -325,6 +325,10 @@ TTS_PROVIDER="native"
|
|||||||
# AGENT_GSE_KEY=
|
# AGENT_GSE_KEY=
|
||||||
# AGENT_GSE_CTX=
|
# AGENT_GSE_CTX=
|
||||||
|
|
||||||
|
#------ SerpApi ----------- https://serpapi.com/
|
||||||
|
# AGENT_SERPAPI_API_KEY=
|
||||||
|
# AGENT_SERPAPI_ENGINE=google
|
||||||
|
|
||||||
#------ SearchApi.io ----------- https://www.searchapi.io/
|
#------ SearchApi.io ----------- https://www.searchapi.io/
|
||||||
# AGENT_SEARCHAPI_API_KEY=
|
# AGENT_SEARCHAPI_API_KEY=
|
||||||
# AGENT_SEARCHAPI_ENGINE=google
|
# AGENT_SEARCHAPI_ENGINE=google
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ const SystemSettings = {
|
|||||||
if (
|
if (
|
||||||
![
|
![
|
||||||
"google-search-engine",
|
"google-search-engine",
|
||||||
|
"serpapi",
|
||||||
"searchapi",
|
"searchapi",
|
||||||
"serper-dot-dev",
|
"serper-dot-dev",
|
||||||
"bing-search",
|
"bing-search",
|
||||||
@@ -276,6 +277,8 @@ const SystemSettings = {
|
|||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
AgentGoogleSearchEngineId: process.env.AGENT_GSE_CTX || null,
|
AgentGoogleSearchEngineId: process.env.AGENT_GSE_CTX || null,
|
||||||
AgentGoogleSearchEngineKey: !!process.env.AGENT_GSE_KEY || 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,
|
AgentSearchApiKey: !!process.env.AGENT_SEARCHAPI_API_KEY || null,
|
||||||
AgentSearchApiEngine: process.env.AGENT_SEARCHAPI_ENGINE || "google",
|
AgentSearchApiEngine: process.env.AGENT_SEARCHAPI_ENGINE || "google",
|
||||||
AgentSerperApiKey: !!process.env.AGENT_SERPER_DEV_KEY || null,
|
AgentSerperApiKey: !!process.env.AGENT_SERPER_DEV_KEY || null,
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ const webBrowsing = {
|
|||||||
case "google-search-engine":
|
case "google-search-engine":
|
||||||
engine = "_googleSearchEngine";
|
engine = "_googleSearchEngine";
|
||||||
break;
|
break;
|
||||||
|
case "serpapi":
|
||||||
|
engine = "_serpApi";
|
||||||
|
break;
|
||||||
case "searchapi":
|
case "searchapi":
|
||||||
engine = "_searchApi";
|
engine = "_searchApi";
|
||||||
break;
|
break;
|
||||||
@@ -170,6 +173,255 @@ const webBrowsing = {
|
|||||||
return result;
|
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
|
* Use SearchApi
|
||||||
* SearchApi supports multiple search engines like Google Search, Bing Search, Baidu Search, Google News, YouTube, and many more.
|
* SearchApi supports multiple search engines like Google Search, Bing Search, Baidu Search, Google News, YouTube, and many more.
|
||||||
|
|||||||
@@ -549,6 +549,14 @@ const KEY_MAPPING = {
|
|||||||
envKey: "AGENT_GSE_KEY",
|
envKey: "AGENT_GSE_KEY",
|
||||||
checks: [],
|
checks: [],
|
||||||
},
|
},
|
||||||
|
AgentSerpApiKey: {
|
||||||
|
envKey: "AGENT_SERPAPI_API_KEY",
|
||||||
|
checks: [],
|
||||||
|
},
|
||||||
|
AgentSerpApiEngine: {
|
||||||
|
envKey: "AGENT_SERPAPI_ENGINE",
|
||||||
|
checks: [],
|
||||||
|
},
|
||||||
AgentSearchApiKey: {
|
AgentSearchApiKey: {
|
||||||
envKey: "AGENT_SEARCHAPI_API_KEY",
|
envKey: "AGENT_SEARCHAPI_API_KEY",
|
||||||
checks: [],
|
checks: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user