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:
Neha Prasad
2025-08-11 22:16:50 +05:30
committed by GitHub
parent 0fb33736da
commit a230a44f5c
8 changed files with 127 additions and 2 deletions

View File

@@ -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 ############
###########################################

View File

@@ -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>
</>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -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({

View File

@@ -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 ############
###########################################

View File

@@ -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

View File

@@ -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)`

View File

@@ -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: {