mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
* 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>
731 lines
28 KiB
JavaScript
731 lines
28 KiB
JavaScript
const { SystemSettings } = require("../../../../models/systemSettings");
|
|
const { TokenManager } = require("../../../helpers/tiktoken");
|
|
const tiktoken = new TokenManager();
|
|
|
|
const webBrowsing = {
|
|
name: "web-browsing",
|
|
startupConfig: {
|
|
params: {},
|
|
},
|
|
plugin: function () {
|
|
return {
|
|
name: this.name,
|
|
setup(aibitat) {
|
|
aibitat.function({
|
|
super: aibitat,
|
|
name: this.name,
|
|
countTokens: (string) =>
|
|
tiktoken
|
|
.countFromString(string)
|
|
.toString()
|
|
.replace(/\B(?=(\d{3})+(?!\d))/g, ","),
|
|
description:
|
|
"Searches for a given query using a search engine to get better results for the user query.",
|
|
examples: [
|
|
{
|
|
prompt: "Who won the world series today?",
|
|
call: JSON.stringify({ query: "Winner of today's world series" }),
|
|
},
|
|
{
|
|
prompt: "What is AnythingLLM?",
|
|
call: JSON.stringify({ query: "AnythingLLM" }),
|
|
},
|
|
{
|
|
prompt: "Current AAPL stock price",
|
|
call: JSON.stringify({ query: "AAPL stock price today" }),
|
|
},
|
|
],
|
|
parameters: {
|
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
type: "object",
|
|
properties: {
|
|
query: {
|
|
type: "string",
|
|
description: "A search query.",
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
handler: async function ({ query }) {
|
|
try {
|
|
if (query) return await this.search(query);
|
|
return "There is nothing we can do. This function call returns no information.";
|
|
} catch (error) {
|
|
return `There was an error while calling the function. No data or response was found. Let the user know this was the error: ${error.message}`;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Use Google Custom Search Engines
|
|
* Free to set up, easy to use, 100 calls/day!
|
|
* https://programmablesearchengine.google.com/controlpanel/create
|
|
*/
|
|
search: async function (query) {
|
|
const provider =
|
|
(await SystemSettings.get({ label: "agent_search_provider" }))
|
|
?.value ?? "unknown";
|
|
let engine;
|
|
switch (provider) {
|
|
case "google-search-engine":
|
|
engine = "_googleSearchEngine";
|
|
break;
|
|
case "searchapi":
|
|
engine = "_searchApi";
|
|
break;
|
|
case "serper-dot-dev":
|
|
engine = "_serperDotDev";
|
|
break;
|
|
case "bing-search":
|
|
engine = "_bingWebSearch";
|
|
break;
|
|
case "serply-engine":
|
|
engine = "_serplyEngine";
|
|
break;
|
|
case "searxng-engine":
|
|
engine = "_searXNGEngine";
|
|
break;
|
|
case "tavily-search":
|
|
engine = "_tavilySearch";
|
|
break;
|
|
case "duckduckgo-engine":
|
|
engine = "_duckDuckGoEngine";
|
|
break;
|
|
case "exa-search":
|
|
engine = "_exaSearch";
|
|
break;
|
|
default:
|
|
engine = "_googleSearchEngine";
|
|
}
|
|
return await this[engine](query);
|
|
},
|
|
|
|
/**
|
|
* Utility function to truncate a string to a given length for debugging
|
|
* calls to the API while keeping the actual values mostly intact
|
|
* @param {string} str - The string to truncate
|
|
* @param {number} length - The length to truncate the string to
|
|
* @returns {string} The truncated string
|
|
*/
|
|
middleTruncate(str, length = 5) {
|
|
if (str.length <= length) return str;
|
|
return `${str.slice(0, length)}...${str.slice(-length)}`;
|
|
},
|
|
|
|
/**
|
|
* Use Google Custom Search Engines
|
|
* Free to set up, easy to use, 100 calls/day
|
|
* https://programmablesearchengine.google.com/controlpanel/create
|
|
*/
|
|
_googleSearchEngine: async function (query) {
|
|
if (!process.env.AGENT_GSE_CTX || !process.env.AGENT_GSE_KEY) {
|
|
this.super.introspect(
|
|
`${this.caller}: I can't use Google searching because the user has not defined the required API keys.\nVisit: https://programmablesearchengine.google.com/controlpanel/create to create the API keys.`
|
|
);
|
|
return `Search is disabled and no content was found. This functionality is disabled because the user has not set it up yet.`;
|
|
}
|
|
|
|
const searchURL = new URL(
|
|
"https://www.googleapis.com/customsearch/v1"
|
|
);
|
|
searchURL.searchParams.append("key", process.env.AGENT_GSE_KEY);
|
|
searchURL.searchParams.append("cx", process.env.AGENT_GSE_CTX);
|
|
searchURL.searchParams.append("q", query);
|
|
|
|
this.super.introspect(
|
|
`${this.caller}: Searching on Google for "${
|
|
query.length > 100 ? `${query.slice(0, 100)}...` : query
|
|
}"`
|
|
);
|
|
const data = await fetch(searchURL)
|
|
.then((res) => {
|
|
if (res.ok) return res.json();
|
|
throw new Error(
|
|
`${res.status} - ${res.statusText}. params: ${JSON.stringify({ key: this.middleTruncate(process.env.AGENT_GSE_KEY, 5), cx: this.middleTruncate(process.env.AGENT_GSE_CTX, 5), q: query })}`
|
|
);
|
|
})
|
|
.then((searchResult) => searchResult?.items || [])
|
|
.then((items) => {
|
|
return items.map((item) => {
|
|
return {
|
|
title: item.title,
|
|
link: item.link,
|
|
snippet: item.snippet,
|
|
};
|
|
});
|
|
})
|
|
.catch((e) => {
|
|
this.super.handlerProps.log(
|
|
`${this.name}: Google Search Error: ${e.message}`
|
|
);
|
|
return [];
|
|
});
|
|
|
|
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
|
|
* SearchApi supports multiple search engines like Google Search, Bing Search, Baidu Search, Google News, YouTube, and many more.
|
|
* https://www.searchapi.io/
|
|
*/
|
|
_searchApi: async function (query) {
|
|
if (!process.env.AGENT_SEARCHAPI_API_KEY) {
|
|
this.super.introspect(
|
|
`${this.caller}: I can't use SearchApi searching because the user has not defined the required API key.\nVisit: https://www.searchapi.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 SearchApi to search for "${
|
|
query.length > 100 ? `${query.slice(0, 100)}...` : query
|
|
}"`
|
|
);
|
|
|
|
const engine = process.env.AGENT_SEARCHAPI_ENGINE;
|
|
const params = new URLSearchParams({
|
|
engine: engine,
|
|
q: query,
|
|
});
|
|
|
|
const url = `https://www.searchapi.io/api/v1/search?${params.toString()}`;
|
|
const { response, error } = await fetch(url, {
|
|
method: "GET",
|
|
headers: {
|
|
Authorization: `Bearer ${process.env.AGENT_SEARCHAPI_API_KEY}`,
|
|
"Content-Type": "application/json",
|
|
"X-SearchApi-Source": "AnythingLLM",
|
|
},
|
|
})
|
|
.then((res) => {
|
|
if (res.ok) return res.json();
|
|
throw new Error(
|
|
`${res.status} - ${res.statusText}. params: ${JSON.stringify({ auth: this.middleTruncate(process.env.AGENT_SEARCHAPI_API_KEY, 5), q: query })}`
|
|
);
|
|
})
|
|
.then((data) => {
|
|
return { response: data, error: null };
|
|
})
|
|
.catch((e) => {
|
|
this.super.handlerProps.log(`SearchApi Error: ${e.message}`);
|
|
return { response: null, error: e.message };
|
|
});
|
|
if (error)
|
|
return `There was an error searching for content. ${error}`;
|
|
|
|
const data = [];
|
|
if (response.hasOwnProperty("knowledge_graph"))
|
|
data.push(response.knowledge_graph?.description);
|
|
if (response.hasOwnProperty("answer_box"))
|
|
data.push(response.answer_box?.answer);
|
|
response.organic_results?.forEach((searchResult) => {
|
|
const { title, link, snippet } = searchResult;
|
|
data.push({
|
|
title,
|
|
link,
|
|
snippet,
|
|
});
|
|
});
|
|
|
|
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 Serper.dev
|
|
* Free to set up, easy to use, 2,500 calls for free one-time
|
|
* https://serper.dev
|
|
*/
|
|
_serperDotDev: async function (query) {
|
|
if (!process.env.AGENT_SERPER_DEV_KEY) {
|
|
this.super.introspect(
|
|
`${this.caller}: I can't use Serper.dev searching because the user has not defined the required API key.\nVisit: https://serper.dev 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 Serper.dev to search for "${
|
|
query.length > 100 ? `${query.slice(0, 100)}...` : query
|
|
}"`
|
|
);
|
|
const { response, error } = await fetch(
|
|
"https://google.serper.dev/search",
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"X-API-KEY": process.env.AGENT_SERPER_DEV_KEY,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ q: query }),
|
|
redirect: "follow",
|
|
}
|
|
)
|
|
.then((res) => {
|
|
if (res.ok) return res.json();
|
|
throw new Error(
|
|
`${res.status} - ${res.statusText}. params: ${JSON.stringify({ auth: this.middleTruncate(process.env.AGENT_SERPER_DEV_KEY, 5), q: query })}`
|
|
);
|
|
})
|
|
.then((data) => {
|
|
return { response: data, error: null };
|
|
})
|
|
.catch((e) => {
|
|
this.super.handlerProps.log(`Serper.dev Error: ${e.message}`);
|
|
return { response: null, error: e.message };
|
|
});
|
|
if (error)
|
|
return `There was an error searching for content. ${error}`;
|
|
|
|
const data = [];
|
|
if (response.hasOwnProperty("knowledgeGraph"))
|
|
data.push(response.knowledgeGraph);
|
|
response.organic?.forEach((searchResult) => {
|
|
const { title, link, snippet } = searchResult;
|
|
data.push({
|
|
title,
|
|
link,
|
|
snippet,
|
|
});
|
|
});
|
|
|
|
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;
|
|
},
|
|
_bingWebSearch: async function (query) {
|
|
if (!process.env.AGENT_BING_SEARCH_API_KEY) {
|
|
this.super.introspect(
|
|
`${this.caller}: I can't use Bing Web Search because the user has not defined the required API key.\nVisit: https://portal.azure.com/ 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.`;
|
|
}
|
|
|
|
const searchURL = new URL(
|
|
"https://api.bing.microsoft.com/v7.0/search"
|
|
);
|
|
searchURL.searchParams.append("q", query);
|
|
|
|
this.super.introspect(
|
|
`${this.caller}: Using Bing Web Search to search for "${
|
|
query.length > 100 ? `${query.slice(0, 100)}...` : query
|
|
}"`
|
|
);
|
|
|
|
const searchResponse = await fetch(searchURL, {
|
|
headers: {
|
|
"Ocp-Apim-Subscription-Key":
|
|
process.env.AGENT_BING_SEARCH_API_KEY,
|
|
},
|
|
})
|
|
.then((res) => {
|
|
if (res.ok) return res.json();
|
|
throw new Error(
|
|
`${res.status} - ${res.statusText}. params: ${JSON.stringify({ auth: this.middleTruncate(process.env.AGENT_BING_SEARCH_API_KEY, 5), q: query })}`
|
|
);
|
|
})
|
|
.then((data) => {
|
|
const searchResults = data.webPages?.value || [];
|
|
return searchResults.map((result) => ({
|
|
title: result.name,
|
|
link: result.url,
|
|
snippet: result.snippet,
|
|
}));
|
|
})
|
|
.catch((e) => {
|
|
this.super.handlerProps.log(
|
|
`Bing Web Search Error: ${e.message}`
|
|
);
|
|
return [];
|
|
});
|
|
|
|
if (searchResponse.length === 0)
|
|
return `No information was found online for the search query.`;
|
|
|
|
const result = JSON.stringify(searchResponse);
|
|
this.super.introspect(
|
|
`${this.caller}: I found ${searchResponse.length} results - reviewing the results now. (~${this.countTokens(result)} tokens)`
|
|
);
|
|
return result;
|
|
},
|
|
_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) => {
|
|
if (res.ok) return res.json();
|
|
throw new Error(
|
|
`${res.status} - ${res.statusText}. params: ${JSON.stringify({ auth: this.middleTruncate(process.env.AGENT_SERPLY_API_KEY, 5), q: query })}`
|
|
);
|
|
})
|
|
.then((data) => {
|
|
if (data?.message === "Unauthorized")
|
|
throw new Error(
|
|
"Unauthorized. Please double check your AGENT_SERPLY_API_KEY"
|
|
);
|
|
return { response: data, error: null };
|
|
})
|
|
.catch((e) => {
|
|
this.super.handlerProps.log(`Serply 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, link, description } = searchResult;
|
|
data.push({
|
|
title,
|
|
link,
|
|
snippet: description,
|
|
});
|
|
});
|
|
|
|
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;
|
|
},
|
|
_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) => {
|
|
if (res.ok) return res.json();
|
|
throw new Error(
|
|
`${res.status} - ${res.statusText}. params: ${JSON.stringify({ url: searchURL.toString() })}`
|
|
);
|
|
})
|
|
.then((data) => {
|
|
return { response: data, error: null };
|
|
})
|
|
.catch((e) => {
|
|
this.super.handlerProps.log(
|
|
`SearXNG 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 { 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.`;
|
|
|
|
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;
|
|
},
|
|
_tavilySearch: async function (query) {
|
|
if (!process.env.AGENT_TAVILY_API_KEY) {
|
|
this.super.introspect(
|
|
`${this.caller}: I can't use Tavily searching because the user has not defined the required API key.\nVisit: https://tavily.com/ 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 Tavily to search for "${
|
|
query.length > 100 ? `${query.slice(0, 100)}...` : query
|
|
}"`
|
|
);
|
|
|
|
const url = "https://api.tavily.com/search";
|
|
const { response, error } = await fetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
api_key: process.env.AGENT_TAVILY_API_KEY,
|
|
query: query,
|
|
}),
|
|
})
|
|
.then((res) => {
|
|
if (res.ok) return res.json();
|
|
throw new Error(
|
|
`${res.status} - ${res.statusText}. params: ${JSON.stringify({ auth: this.middleTruncate(process.env.AGENT_TAVILY_API_KEY, 5), q: query })}`
|
|
);
|
|
})
|
|
.then((data) => {
|
|
return { response: data, error: null };
|
|
})
|
|
.catch((e) => {
|
|
this.super.handlerProps.log(
|
|
`Tavily 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, content } = searchResult;
|
|
data.push({
|
|
title,
|
|
link: url,
|
|
snippet: content,
|
|
});
|
|
});
|
|
|
|
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;
|
|
},
|
|
_duckDuckGoEngine: async function (query) {
|
|
this.super.introspect(
|
|
`${this.caller}: Using DuckDuckGo to search for "${
|
|
query.length > 100 ? `${query.slice(0, 100)}...` : query
|
|
}"`
|
|
);
|
|
|
|
const searchURL = new URL("https://html.duckduckgo.com/html");
|
|
searchURL.searchParams.append("q", query);
|
|
|
|
const response = await fetch(searchURL.toString())
|
|
.then((res) => {
|
|
if (res.ok) return res.text();
|
|
throw new Error(
|
|
`${res.status} - ${res.statusText}. params: ${JSON.stringify({ url: searchURL.toString() })}`
|
|
);
|
|
})
|
|
.catch((e) => {
|
|
this.super.handlerProps.log(
|
|
`DuckDuckGo Search Error: ${e.message}`
|
|
);
|
|
return null;
|
|
});
|
|
|
|
if (!response) return `There was an error searching DuckDuckGo.`;
|
|
const html = response;
|
|
const data = [];
|
|
const results = html.split('<div class="result results_links');
|
|
|
|
// Skip first element since it's before the first result
|
|
for (let i = 1; i < results.length; i++) {
|
|
const result = results[i];
|
|
|
|
// Extract title
|
|
const titleMatch = result.match(
|
|
/<a[^>]*class="result__a"[^>]*>(.*?)<\/a>/
|
|
);
|
|
const title = titleMatch ? titleMatch[1].trim() : "";
|
|
|
|
// Extract URL
|
|
const urlMatch = result.match(
|
|
/<a[^>]*class="result__a"[^>]*href="([^"]*)">/
|
|
);
|
|
const link = urlMatch ? urlMatch[1] : "";
|
|
|
|
// Extract snippet
|
|
const snippetMatch = result.match(
|
|
/<a[^>]*class="result__snippet"[^>]*>(.*?)<\/a>/
|
|
);
|
|
const snippet = snippetMatch
|
|
? snippetMatch[1].replace(/<\/?b>/g, "").trim()
|
|
: "";
|
|
|
|
if (title && link && snippet) {
|
|
data.push({ title, link, snippet });
|
|
}
|
|
}
|
|
|
|
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;
|
|
},
|
|
_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)`
|
|
);
|
|
return result;
|
|
},
|
|
});
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
module.exports = {
|
|
webBrowsing,
|
|
};
|