mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
feat: Add Exa as a Search Provider (#4258)
* Added exa-search case to the search provider switch in web-browsing.js * Added ExaSearchOptions component for API key input * update * Patch missing image crashing UI Fix issue where ENV key did not exist or was saved on click Update copy for provider Add Docs for ENV keys for manual placements update systemssettings for returning key saved to UI --------- Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
@@ -328,6 +328,9 @@ GID='1000'
|
||||
#------ Tavily ----------- https://www.tavily.com/
|
||||
# AGENT_TAVILY_API_KEY=
|
||||
|
||||
#------ Exa Search ----------- https://www.exa.ai/
|
||||
# AGENT_EXA_API_KEY=
|
||||
|
||||
###########################################
|
||||
######## Other Configurations ############
|
||||
###########################################
|
||||
|
||||
@@ -327,3 +327,38 @@ export function DuckDuckGoOptions() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ExaSearchOptions({ settings }) {
|
||||
return (
|
||||
<>
|
||||
<p className="text-sm text-white/60 my-2">
|
||||
You can get an API key{" "}
|
||||
<a
|
||||
href="https://exa.ai"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-300 underline"
|
||||
>
|
||||
from Exa.
|
||||
</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::AgentExaApiKey"
|
||||
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="Exa API Key"
|
||||
defaultValue={settings?.AgentExaApiKey ? "*".repeat(20) : ""}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
BIN
frontend/src/pages/Admin/Agents/WebSearchSelection/icons/exa.png
Normal file
BIN
frontend/src/pages/Admin/Agents/WebSearchSelection/icons/exa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -9,6 +9,7 @@ import SerplySearchIcon from "./icons/serply.png";
|
||||
import SearXNGSearchIcon from "./icons/searxng.png";
|
||||
import TavilySearchIcon from "./icons/tavily.svg";
|
||||
import DuckDuckGoIcon from "./icons/duckduckgo.png";
|
||||
import ExaIcon from "./icons/exa.png";
|
||||
import {
|
||||
CaretUpDown,
|
||||
MagnifyingGlass,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
SearXNGOptions,
|
||||
TavilySearchOptions,
|
||||
DuckDuckGoOptions,
|
||||
ExaSearchOptions,
|
||||
} from "./SearchProviderOptions";
|
||||
|
||||
const SEARCH_PROVIDERS = [
|
||||
@@ -42,8 +44,7 @@ const SEARCH_PROVIDERS = [
|
||||
value: "duckduckgo-engine",
|
||||
logo: DuckDuckGoIcon,
|
||||
options: () => <DuckDuckGoOptions />,
|
||||
description:
|
||||
"Free and privacy-focused web search using DuckDuckGo's HTML interface.",
|
||||
description: "Free and privacy-focused web search using DuckDuckGo.",
|
||||
},
|
||||
{
|
||||
name: "Google Search Engine",
|
||||
@@ -100,6 +101,13 @@ const SEARCH_PROVIDERS = [
|
||||
description:
|
||||
"Tavily Search API. Offers a free tier with 1000 queries per month.",
|
||||
},
|
||||
{
|
||||
name: "Exa Search",
|
||||
value: "exa-search",
|
||||
logo: ExaIcon,
|
||||
options: (settings) => <ExaSearchOptions settings={settings} />,
|
||||
description: "AI-powered search engine optimized for LLM use cases.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function AgentWebSearchSelection({
|
||||
|
||||
@@ -325,6 +325,9 @@ TTS_PROVIDER="native"
|
||||
#------ Tavily ----------- https://www.tavily.com/
|
||||
# AGENT_TAVILY_API_KEY=
|
||||
|
||||
#------ Exa Search ----------- https://www.exa.ai/
|
||||
# AGENT_EXA_API_KEY=
|
||||
|
||||
###########################################
|
||||
######## Other Configurations ############
|
||||
###########################################
|
||||
|
||||
@@ -113,6 +113,7 @@ const SystemSettings = {
|
||||
"searxng-engine",
|
||||
"tavily-search",
|
||||
"duckduckgo-engine",
|
||||
"exa-search",
|
||||
].includes(update)
|
||||
)
|
||||
throw new Error("Invalid SERP provider.");
|
||||
@@ -282,6 +283,7 @@ const SystemSettings = {
|
||||
AgentSerplyApiKey: !!process.env.AGENT_SERPLY_API_KEY || null,
|
||||
AgentSearXNGApiUrl: process.env.AGENT_SEARXNG_API_URL || null,
|
||||
AgentTavilyApiKey: !!process.env.AGENT_TAVILY_API_KEY || null,
|
||||
AgentExaApiKey: !!process.env.AGENT_EXA_API_KEY || null,
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Compliance Settings
|
||||
|
||||
@@ -90,6 +90,9 @@ const webBrowsing = {
|
||||
case "duckduckgo-engine":
|
||||
engine = "_duckDuckGoEngine";
|
||||
break;
|
||||
case "exa-search":
|
||||
engine = "_exaSearch";
|
||||
break;
|
||||
default:
|
||||
engine = "_googleSearchEngine";
|
||||
}
|
||||
@@ -643,6 +646,73 @@ const webBrowsing = {
|
||||
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;
|
||||
},
|
||||
_exaSearch: async function (query) {
|
||||
if (!process.env.AGENT_EXA_API_KEY) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: I can't use Exa searching because the user has not defined the required API key.\nVisit: https://exa.ai to create the API key.`
|
||||
);
|
||||
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 Exa to search for "${
|
||||
query.length > 100 ? `${query.slice(0, 100)}...` : query
|
||||
}"`
|
||||
);
|
||||
|
||||
const url = "https://api.exa.ai/search";
|
||||
const { response, error } = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": process.env.AGENT_EXA_API_KEY,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: query,
|
||||
type: "auto",
|
||||
numResults: 10,
|
||||
contents: {
|
||||
text: true,
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.ok) return res.json();
|
||||
throw new Error(
|
||||
`${res.status} - ${res.statusText}. params: ${JSON.stringify({ auth: this.middleTruncate(process.env.AGENT_EXA_API_KEY, 5), q: query })}`
|
||||
);
|
||||
})
|
||||
.then((data) => {
|
||||
return { response: data, error: null };
|
||||
})
|
||||
.catch((e) => {
|
||||
this.super.handlerProps.log(`Exa Search Error: ${e.message}`);
|
||||
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, url, text, publishedDate } = searchResult;
|
||||
data.push({
|
||||
title,
|
||||
link: url,
|
||||
snippet: text,
|
||||
publishedDate,
|
||||
});
|
||||
});
|
||||
|
||||
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)`
|
||||
|
||||
@@ -563,6 +563,10 @@ const KEY_MAPPING = {
|
||||
envKey: "AGENT_TAVILY_API_KEY",
|
||||
checks: [],
|
||||
},
|
||||
AgentExaApiKey: {
|
||||
envKey: "AGENT_EXA_API_KEY",
|
||||
checks: [],
|
||||
},
|
||||
|
||||
// TTS/STT Integration ENVS
|
||||
TextToSpeechProvider: {
|
||||
|
||||
Reference in New Issue
Block a user