163 lines
5.5 KiB
JavaScript
163 lines
5.5 KiB
JavaScript
|
import { getEnvironmentVariable } from "@langchain/core/utils/env";
|
||
|
import { Tool } from "@langchain/core/tools";
|
||
|
function isJSONObject(value) {
|
||
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||
|
}
|
||
|
/**
|
||
|
* SearchApi Class Definition.
|
||
|
*
|
||
|
* Provides a wrapper around the SearchApi.
|
||
|
*
|
||
|
* Ensure you've set the SEARCHAPI_API_KEY environment variable for authentication.
|
||
|
* You can obtain a free API key from https://www.searchapi.io/.
|
||
|
* @example
|
||
|
* ```typescript
|
||
|
* const searchApi = new SearchApi("your-api-key", {
|
||
|
* engine: "google_news",
|
||
|
* });
|
||
|
* const agent = RunnableSequence.from([
|
||
|
* ChatPromptTemplate.fromMessages([
|
||
|
* ["ai", "Answer the following questions using a bulleted list markdown format.""],
|
||
|
* ["human", "{input}"],
|
||
|
* ]),
|
||
|
* new ChatOpenAI({ temperature: 0 }),
|
||
|
* (input: BaseMessageChunk) => ({
|
||
|
* log: "test",
|
||
|
* returnValues: {
|
||
|
* output: input,
|
||
|
* },
|
||
|
* }),
|
||
|
* ]);
|
||
|
* const executor = AgentExecutor.fromAgentAndTools({
|
||
|
* agent,
|
||
|
* tools: [searchApi],
|
||
|
* });
|
||
|
* const res = await executor.invoke({
|
||
|
* input: "What's happening in Ukraine today?"",
|
||
|
* });
|
||
|
* console.log(res);
|
||
|
* ```
|
||
|
*/
|
||
|
export class SearchApi extends Tool {
|
||
|
static lc_name() {
|
||
|
return "SearchApi";
|
||
|
}
|
||
|
/**
|
||
|
* Converts the SearchApi instance to JSON. This method is not implemented
|
||
|
* and will throw an error if called.
|
||
|
* @returns Throws an error.
|
||
|
*/
|
||
|
toJSON() {
|
||
|
return this.toJSONNotImplemented();
|
||
|
}
|
||
|
constructor(apiKey = getEnvironmentVariable("SEARCHAPI_API_KEY"), params = {}) {
|
||
|
super(...arguments);
|
||
|
Object.defineProperty(this, "apiKey", {
|
||
|
enumerable: true,
|
||
|
configurable: true,
|
||
|
writable: true,
|
||
|
value: void 0
|
||
|
});
|
||
|
Object.defineProperty(this, "params", {
|
||
|
enumerable: true,
|
||
|
configurable: true,
|
||
|
writable: true,
|
||
|
value: void 0
|
||
|
});
|
||
|
Object.defineProperty(this, "name", {
|
||
|
enumerable: true,
|
||
|
configurable: true,
|
||
|
writable: true,
|
||
|
value: "search"
|
||
|
});
|
||
|
Object.defineProperty(this, "description", {
|
||
|
enumerable: true,
|
||
|
configurable: true,
|
||
|
writable: true,
|
||
|
value: "a search engine. useful for when you need to answer questions about current events. input should be a search query."
|
||
|
});
|
||
|
if (!apiKey) {
|
||
|
throw new Error("SearchApi requires an API key. Please set it as SEARCHAPI_API_KEY in your .env file, or pass it as a parameter to the SearchApi constructor.");
|
||
|
}
|
||
|
this.apiKey = apiKey;
|
||
|
this.params = params;
|
||
|
}
|
||
|
/**
|
||
|
* Builds a URL for the SearchApi request.
|
||
|
* @param parameters The parameters for the request.
|
||
|
* @returns A string representing the built URL.
|
||
|
*/
|
||
|
buildUrl(searchQuery) {
|
||
|
const preparedParams = Object.entries({
|
||
|
engine: "google",
|
||
|
api_key: this.apiKey,
|
||
|
...this.params,
|
||
|
q: searchQuery,
|
||
|
})
|
||
|
.filter(([key, value]) => value !== undefined && value !== null && key !== "apiKey")
|
||
|
.map(([key, value]) => [key, `${value}`]);
|
||
|
const searchParams = new URLSearchParams(preparedParams);
|
||
|
return `https://www.searchapi.io/api/v1/search?${searchParams}`;
|
||
|
}
|
||
|
/** @ignore */
|
||
|
/**
|
||
|
* Calls the SearchAPI.
|
||
|
*
|
||
|
* Accepts an input query and fetches the result from SearchApi.
|
||
|
*
|
||
|
* @param {string} input - Search query.
|
||
|
* @returns {string} - Formatted search results or an error message.
|
||
|
*
|
||
|
* NOTE: This method is the core search handler and processes various types
|
||
|
* of search results including Google organic results, videos, jobs, and images.
|
||
|
*/
|
||
|
async _call(input) {
|
||
|
const resp = await fetch(this.buildUrl(input));
|
||
|
const json = await resp.json();
|
||
|
if (json.error) {
|
||
|
throw new Error(`Failed to load search results from SearchApi due to: ${json.error}`);
|
||
|
}
|
||
|
// Google Search results
|
||
|
if (json.answer_box?.answer) {
|
||
|
return json.answer_box.answer;
|
||
|
}
|
||
|
if (json.answer_box?.snippet) {
|
||
|
return json.answer_box.snippet;
|
||
|
}
|
||
|
if (json.knowledge_graph?.description) {
|
||
|
return json.knowledge_graph.description;
|
||
|
}
|
||
|
// Organic results (Google, Google News)
|
||
|
if (json.organic_results) {
|
||
|
const snippets = json.organic_results
|
||
|
.filter((r) => r.snippet)
|
||
|
.map((r) => r.snippet);
|
||
|
return snippets.join("\n");
|
||
|
}
|
||
|
// Google Jobs results
|
||
|
if (json.jobs) {
|
||
|
const jobDescriptions = json.jobs
|
||
|
.slice(0, 1)
|
||
|
.filter((r) => r.description)
|
||
|
.map((r) => r.description);
|
||
|
return jobDescriptions.join("\n");
|
||
|
}
|
||
|
// Google Videos results
|
||
|
if (json.videos) {
|
||
|
const videoInfo = json.videos
|
||
|
.filter((r) => r.title && r.link)
|
||
|
.map((r) => `Title: "${r.title}" Link: ${r.link}`);
|
||
|
return videoInfo.join("\n");
|
||
|
}
|
||
|
// Google Images results
|
||
|
if (json.images) {
|
||
|
const image_results = json.images.slice(0, 15);
|
||
|
const imageInfo = image_results
|
||
|
.filter((r) => r.title && r.original && isJSONObject(r.original) && r.original.link)
|
||
|
.map((r) => `Title: "${r.title}" Link: ${r.original.link}`);
|
||
|
return imageInfo.join("\n");
|
||
|
}
|
||
|
return "No good search result found";
|
||
|
}
|
||
|
}
|