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"; } }