diff --git a/docs/api_refs/blacklisted-entrypoints.json b/docs/api_refs/blacklisted-entrypoints.json index ea3491ec2294..419d8800827d 100644 --- a/docs/api_refs/blacklisted-entrypoints.json +++ b/docs/api_refs/blacklisted-entrypoints.json @@ -7,6 +7,7 @@ "../../langchain/src/tools/connery.ts", "../../langchain/src/tools/gmail.ts", "../../langchain/src/tools/google_places.ts", + "../../langchain/src/tools/google_trends.ts", "../../langchain/src/embeddings/bedrock.ts", "../../langchain/src/embeddings/cloudflare_workersai.ts", "../../langchain/src/embeddings/ollama.ts", diff --git a/docs/core_docs/docs/integrations/tools/google_trends.mdx b/docs/core_docs/docs/integrations/tools/google_trends.mdx new file mode 100644 index 000000000000..bbbdec5d70c0 --- /dev/null +++ b/docs/core_docs/docs/integrations/tools/google_trends.mdx @@ -0,0 +1,42 @@ +--- +hide_table_of_contents: true +--- + +import CodeBlock from "@theme/CodeBlock"; + +# Google Trends Tool + +The Google Trends Tool allows your agent to utilize the Google Trends API from SerpApi to retrieve and analyze search interest data. +This can be useful for understanding trending topics, regional search interest, and historical popularity of search terms. + +For API details see [here](https://serpapi.com/google-trends-api) + +SerpApi caches queries, so the first query will be slower while subsequent identical queries will be fast. +Occasionally, related queries will not work while interest over time will be fine. You can check your query [here](https://serpapi.com/playground?engine=google_trends&q=monster&data_type=RELATED_QUERIES). + +## Setup + +To use this tool, you will need to configure access to the Google Trends API from SerpApi. + +Get an API key from [SerpApi](https://serpapi.com/users/sign_in) + +Then, set your API key as `process.env.SERPAPI_API_KEY` or pass it in as an `apiKey` constructor argument. + +## Usage + +import IntegrationInstallTooltip from "@mdx_components/integration_install_tooltip.mdx"; + + + +```bash npm2yarn +npm install @langchain/openai @langchain/community @langchain/core +``` + +import ToolExample from "@examples/tools/google_trends.ts"; + +{ToolExample} + +## Related + +- Tool [conceptual guide](/docs/concepts/tools) +- Tool [how-to guides](/docs/how_to/#tools) diff --git a/examples/src/tools/google_trends.ts b/examples/src/tools/google_trends.ts new file mode 100644 index 000000000000..25cf5f174ba3 --- /dev/null +++ b/examples/src/tools/google_trends.ts @@ -0,0 +1,9 @@ +import { SERPGoogleTrendsTool } from "@langchain/community/tools/google_trends"; + +export async function run() { + const tool = new SERPGoogleTrendsTool(); + + const res = await tool.invoke("Monster"); + + console.log(res); +} diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore index 5064c1f14c79..8dc708cbe23e 100644 --- a/libs/langchain-community/.gitignore +++ b/libs/langchain-community/.gitignore @@ -70,6 +70,10 @@ tools/google_places.cjs tools/google_places.js tools/google_places.d.ts tools/google_places.d.cts +tools/google_trends.cjs +tools/google_trends.js +tools/google_trends.d.ts +tools/google_trends.d.cts tools/google_routes.cjs tools/google_routes.js tools/google_routes.d.ts diff --git a/libs/langchain-community/langchain.config.js b/libs/langchain-community/langchain.config.js index b0207b8612ab..547960384372 100644 --- a/libs/langchain-community/langchain.config.js +++ b/libs/langchain-community/langchain.config.js @@ -51,6 +51,7 @@ export const config = { "tools/google_calendar": "tools/google_calendar/index", "tools/google_custom_search": "tools/google_custom_search", "tools/google_places": "tools/google_places", + "tools/google_trends": "tools/google_trends", "tools/google_routes": "tools/google_routes", "tools/ifttt": "tools/ifttt", "tools/searchapi": "tools/searchapi", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 6cdbe97e2664..108a51c174ce 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -874,6 +874,15 @@ "import": "./tools/google_places.js", "require": "./tools/google_places.cjs" }, + "./tools/google_trends": { + "types": { + "import": "./tools/google_trends.d.ts", + "require": "./tools/google_trends.d.cts", + "default": "./tools/google_trends.d.ts" + }, + "import": "./tools/google_trends.js", + "require": "./tools/google_trends.cjs" + }, "./tools/google_routes": { "types": { "import": "./tools/google_routes.d.ts", @@ -3191,6 +3200,10 @@ "tools/google_places.js", "tools/google_places.d.ts", "tools/google_places.d.cts", + "tools/google_trends.cjs", + "tools/google_trends.js", + "tools/google_trends.d.ts", + "tools/google_trends.d.cts", "tools/google_routes.cjs", "tools/google_routes.js", "tools/google_routes.d.ts", diff --git a/libs/langchain-community/src/tools/google_trends.ts b/libs/langchain-community/src/tools/google_trends.ts new file mode 100644 index 000000000000..18f063e791e8 --- /dev/null +++ b/libs/langchain-community/src/tools/google_trends.ts @@ -0,0 +1,161 @@ +import { getEnvironmentVariable } from "@langchain/core/utils/env"; +import { Tool } from "@langchain/core/tools"; + +/** + * Interfaces for the response from the SerpApi Google Trends API. + */ +interface TimelineValue { + query: string; + value: string; + extracted_value: number; +} + +interface TimelineData { + date: string; + timestamp: string; + values: TimelineValue[]; +} + +/** + * Interface for parameters required by SERPGoogleTrendsTool class. + */ +export interface SERPGoogleTrendsToolParams { + apiKey?: string; +} + +/** + * Tool that queries the Google Trends API. Uses default interest over time. + */ +export class SERPGoogleTrendsTool extends Tool { + static lc_name() { + return "SERPGoogleTrendsTool"; + } + + get lc_secrets(): { [key: string]: string } | undefined { + return { + apiKey: "SERPAPI_API_KEY", + }; + } + + name = "google_trends"; + + protected apiKey: string; + + description = `A wrapper around Google Trends API. Useful for analyzing and retrieving trending search data based on keywords, + categories, or regions. Input should be a search query or specific parameters for trends analysis.`; + + constructor(fields?: SERPGoogleTrendsToolParams) { + super(...arguments); + const apiKey = fields?.apiKey ?? getEnvironmentVariable("SERPAPI_API_KEY"); + if (apiKey === undefined) { + throw new Error( + `Google Trends API key not set. You can set it as "SERPAPI_API_KEY" in your environment variables.` + ); + } + this.apiKey = apiKey; + } + + async _call(query: string): Promise { + /** + * Related queries only accepts one at a time, and multiple + * queries at once on interest over time (default) is effectively the same as + * each query one by one. + * + * SerpApi caches queries, so the first time will be slower + * and subsequent identical queries will be very fast. + */ + if (query.split(",").length > 1) { + throw new Error("Please do one query at a time"); + } + const serpapiApiKey = this.apiKey; + const params = new URLSearchParams({ + engine: "google_trends", + api_key: serpapiApiKey, + q: query, + }); + + const res = await fetch( + `https://serpapi.com/search.json?${params.toString()}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!res.ok) { + throw new Error(`Error fetching data from SerpAPI: ${res.statusText}`); + } + + const clientDict = await res.json(); + const totalResults = clientDict.interest_over_time?.timeline_data ?? []; + + if (totalResults.length === 0) { + return "No good Trend Result was found"; + } + + const startDate = totalResults[0].date.split(" "); + const endDate = totalResults[totalResults.length - 1].date.split(" "); + const values = totalResults.map( + (result: TimelineData) => result.values[0].extracted_value + ); + const minValue = Math.min(...values); + const maxValue = Math.max(...values); + const avgValue = + values.reduce((a: number, b: number) => a + b, 0) / values.length; + const percentageChange = + ((values[values.length - 1] - values[0]) / (values[0] || 1)) * 100; + + const relatedParams = new URLSearchParams({ + engine: "google_trends", + api_key: serpapiApiKey, + data_type: "RELATED_QUERIES", + q: query, + }); + + const relatedRes = await fetch( + `https://serpapi.com/search.json?${relatedParams.toString()}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + + let rising = []; + let top = []; + if (!relatedRes.ok) { + // Sometimes related queries from SerpAPI will fail, but interest over time will be fine + console.error( + `Error fetching related queries from SerpAPI: ${relatedRes.statusText}` + ); + } else { + const relatedDict = await relatedRes.json(); + rising = + relatedDict.related_queries?.rising?.map( + (result: { query: string }) => result.query + ) ?? []; + top = + relatedDict.related_queries?.top?.map( + (result: { query: string }) => result.query + ) ?? []; + } + + const doc = [ + `Query: ${query}`, + `Date From: ${startDate[0]} ${startDate[1]}, ${startDate[2]}`, + `Date To: ${endDate[0]} ${endDate[1]} ${endDate[2]}`, + `Min Value: ${minValue}`, + `Max Value: ${maxValue}`, + `Average Value: ${avgValue}`, + `Percent Change: ${percentageChange.toFixed(2)}%`, + `Trend values: ${values.join(", ")}`, + `Rising Related Queries: ${rising.join(", ")}`, + `Top Related Queries: ${top.join(", ")}`, + ]; + + return doc.join("\n\n"); + } +} diff --git a/libs/langchain-community/src/tools/tests/google_trends.int.test.ts b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts new file mode 100644 index 000000000000..49be189bd7ad --- /dev/null +++ b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts @@ -0,0 +1,34 @@ +import { expect, describe } from "@jest/globals"; +import { SERPGoogleTrendsTool } from "../google_trends.js"; + +describe("SERPGoogleTrendsTool", () => { + test("should be setup with correct parameters", async () => { + const instance = new SERPGoogleTrendsTool(); + expect(instance.name).toBe("google_trends"); + }); + + test("SERPGoogleTrendsTool returns expected result for valid query", async () => { + const tool = new SERPGoogleTrendsTool(); + + const result = await tool._call("Coffee"); + + expect(result).toContain("Query: Coffee"); + expect(result).toContain("Date From:"); + expect(result).toContain("Date To:"); + expect(result).toContain("Min Value:"); + expect(result).toContain("Max Value:"); + expect(result).toContain("Average Value:"); + expect(result).toContain("Percent Change:"); + expect(result).toContain("Trend values:"); + expect(result).toContain("Rising Related Queries:"); + expect(result).toContain("Top Related Queries:"); + }); + + test("SERPGoogleTrendsTool returns 'No good Trend Result was found' for a non-existent query", async () => { + const tool = new SERPGoogleTrendsTool(); + + const result = await tool._call("earghajgpajrpgjaprgag"); + + expect(result).toBe("No good Trend Result was found"); + }); +});