From 728d6bb3c1c97d8c9fc4175075fa852b12f61a17 Mon Sep 17 00:00:00 2001 From: A-Jatana Date: Sun, 17 Nov 2024 20:08:15 -0500 Subject: [PATCH 1/8] feat: add required files for google trends integration --- .../docs/integrations/tools/google_trends.mdx | 37 ++++++ examples/src/tools/google_trends.ts | 22 ++++ .../src/tools/google_trends.ts | 117 ++++++++++++++++++ .../src/tools/tests/google_trends.int.test.ts | 35 ++++++ 4 files changed, 211 insertions(+) create mode 100644 docs/core_docs/docs/integrations/tools/google_trends.mdx create mode 100644 examples/src/tools/google_trends.ts create mode 100644 libs/langchain-community/src/tools/google_trends.ts create mode 100644 libs/langchain-community/src/tools/tests/google_trends.int.test.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..9bc5b3e0fe1e --- /dev/null +++ b/docs/core_docs/docs/integrations/tools/google_trends.mdx @@ -0,0 +1,37 @@ +--- +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 to retrieve and analyze search interest data. +This can be useful for understanding trending topics, regional search interest, and historical popularity of search terms. + +## Setup + +To use this tool, you'll need to configure access to the Google Trends API. The integration relies on the unofficial +Google Trends library, as Google does not provide a formal API for accessing trends data. + +Get an API key from [SERPApi](https://serpapi.com/users/sign_in) + +Then, set your API key as `process.env.GOOGLE_PLACES_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 [https://serpapi.com/google-trends-api] \ No newline at end of file diff --git a/examples/src/tools/google_trends.ts b/examples/src/tools/google_trends.ts new file mode 100644 index 000000000000..63aa926ebddf --- /dev/null +++ b/examples/src/tools/google_trends.ts @@ -0,0 +1,22 @@ +import { GoogleTrendsAPI } from "@langchain/community/tools/google_trends"; +import { OpenAI } from "@langchain/openai"; +import { initializeAgentExecutorWithOptions } from "langchain/agents"; + +export async function run() { + const model = new OpenAI({ + temperature: 0, + }); + + const tools = [new GoogleTrendsAPI()]; + + const executor = await initializeAgentExecutorWithOptions(tools, model, { + agentType: "zero-shot-react-description", + verbose: true, + }); + + const res = await executor.invoke({ + input: "What are the recent trends in AI research? ", + }); + + console.log(res.output); +} \ No newline at end of file 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..ac5f6cae0d6a --- /dev/null +++ b/libs/langchain-community/src/tools/google_trends.ts @@ -0,0 +1,117 @@ +import { getEnvironmentVariable } from "@langchain/core/utils/env"; +import { Tool } from "@langchain/core/tools"; + +/** + * Interface for parameters required by GoogleTrendsAPI class. + */ +export interface GoogleTrendsAPIParams { + apiKey?: string; + } + + +/** + * Tool that queries the Google Trends API + */ +export class GoogleTrendsAPI extends Tool { + static lc_name() { + return "GoogleTrendsAPI"; + } + + get lc_secrets(): { [key: string]: string } | undefined { + return { + apiKey: "GOOGLE_TRENDS_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?: GoogleTrendsAPIParams) { + super(...arguments); + const apiKey = + fields?.apiKey ?? getEnvironmentVariable("GOOGLE_TRENDS_API_KEY"); + if (apiKey === undefined) { + throw new Error( + `Google Trends API key not set. You can set it as "GOOGLE_TRENDS_API_KEY" in your environment variables.` + ); + } + this.apiKey = apiKey; + } + + async run(query: string): Promise { + 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: any) => 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_TOPICS", + q: query, + }); + + const relatedRes = await fetch(`https://serpapi.com/search.json?${relatedParams.toString()}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!relatedRes.ok) { + throw new Error(`Error fetching related queries from SerpAPI: ${relatedRes.statusText}`); + } + + const relatedDict = await relatedRes.json(); + const rising = relatedDict.related_queries?.rising?.map((result: any) => result.query) ?? []; + const top = relatedDict.related_queries?.top?.map((result: any) => 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"); + } + +} \ No newline at end of file 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..ed9bfdfdd1d0 --- /dev/null +++ b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts @@ -0,0 +1,35 @@ +import { expect, describe } from "@jest/globals"; +import { GoogleTrendsAPI } from "../google_trends.js"; + +describe("GoogleTrendsAPI", () => { + test("should be setup with correct parameters", async () => { + const instance = new GoogleTrendsAPI(); + expect(instance.name).toBe("google_trends"); + }); + + test("GoogleTrendsAPI returns expected result for valid query", async () => { + const tool = new GoogleTrendsAPI(); + + const result = await tool.run("Island"); + + 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("GoogleTrendsAPI returns 'No good Trend Result was found' for a non-existent query", async () => { + const tool = new GoogleTrendsAPI(); + + const result = await tool.run("hhdhbfsjbfwl"); + + expect(result).toBe("No good Trend Result was found"); + }); + +}); From 3118d8ac7eeb3f01bfb35d0eb454fb377b8253d8 Mon Sep 17 00:00:00 2001 From: A-Jatana Date: Mon, 18 Nov 2024 16:33:08 -0500 Subject: [PATCH 2/8] docs: make modifications to required files --- docs/api_refs/blacklisted-entrypoints.json | 1 + examples/src/tools/google_trends.ts | 18 +++--------------- libs/langchain-community/.gitignore | 4 ++++ libs/langchain-community/langchain.config.js | 1 + libs/langchain-community/package.json | 13 +++++++++++++ .../langchain-community/src/load/import_map.ts | 1 + .../src/load/import_type.ts | 1 + libs/langchain-scripts/src/_data/importMap.ts | 10 ++++++++++ 8 files changed, 34 insertions(+), 15 deletions(-) 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/examples/src/tools/google_trends.ts b/examples/src/tools/google_trends.ts index 63aa926ebddf..e2a69748bdf2 100644 --- a/examples/src/tools/google_trends.ts +++ b/examples/src/tools/google_trends.ts @@ -1,22 +1,10 @@ import { GoogleTrendsAPI } from "@langchain/community/tools/google_trends"; -import { OpenAI } from "@langchain/openai"; -import { initializeAgentExecutorWithOptions } from "langchain/agents"; export async function run() { - const model = new OpenAI({ - temperature: 0, - }); - const tools = [new GoogleTrendsAPI()]; + const tool = new GoogleTrendsAPI(); - const executor = await initializeAgentExecutorWithOptions(tools, model, { - agentType: "zero-shot-react-description", - verbose: true, - }); + const res = await tool.run("Monster"); - const res = await executor.invoke({ - input: "What are the recent trends in AI research? ", - }); - - console.log(res.output); + console.log(res); } \ No newline at end of file diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore index b554afb6f1ec..c805375cf29e 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 631c2a12879e..acd50ade7cf7 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 8de0a386cb34..43e8a0266d23 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -869,6 +869,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", @@ -3150,6 +3159,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/load/import_map.ts b/libs/langchain-community/src/load/import_map.ts index 8b3b734a82c1..53551b8da006 100644 --- a/libs/langchain-community/src/load/import_map.ts +++ b/libs/langchain-community/src/load/import_map.ts @@ -11,6 +11,7 @@ export * as tools__dynamic from "../tools/dynamic.js"; export * as tools__dataforseo_api_search from "../tools/dataforseo_api_search.js"; export * as tools__google_custom_search from "../tools/google_custom_search.js"; export * as tools__google_places from "../tools/google_places.js"; +export * as tools__google_trends from "../tools/google_trends.js"; export * as tools__google_routes from "../tools/google_routes.js"; export * as tools__ifttt from "../tools/ifttt.js"; export * as tools__searchapi from "../tools/searchapi.js"; diff --git a/libs/langchain-community/src/load/import_type.ts b/libs/langchain-community/src/load/import_type.ts index 51536706142c..65b6b689a7da 100644 --- a/libs/langchain-community/src/load/import_type.ts +++ b/libs/langchain-community/src/load/import_type.ts @@ -26,6 +26,7 @@ export interface SecretMap { GOOGLE_API_KEY?: string; GOOGLE_PALM_API_KEY?: string; GOOGLE_PLACES_API_KEY?: string; + GOOGLE_TRENDS_API_KEY?: string; GOOGLE_ROUTES_API_KEY?: string; GRADIENT_ACCESS_TOKEN?: string; GRADIENT_WORKSPACE_ID?: string; diff --git a/libs/langchain-scripts/src/_data/importMap.ts b/libs/langchain-scripts/src/_data/importMap.ts index 1bbb79ef46e9..7a21bd31c957 100644 --- a/libs/langchain-scripts/src/_data/importMap.ts +++ b/libs/langchain-scripts/src/_data/importMap.ts @@ -2920,6 +2920,16 @@ export const importMap: Array = [ new: "@langchain/community/tools/google_places", namedImport: "GooglePlacesAPI", }, + { + old: "langchain/tools/google_trends", + new: "@langchain/community/tools/google_trends", + namedImport: "GoogleTrendsAPIParams", + }, + { + old: "langchain/tools/google_trends", + new: "@langchain/community/tools/google_trends", + namedImport: "GoogleTrendsAPI", + }, { old: "langchain/tools/*", new: "@langchain/community/tools/google_custom_search", From b50cf3cf3e16dfa5d6bcb273582fc388a62ca2f2 Mon Sep 17 00:00:00 2001 From: A-Jatana Date: Tue, 19 Nov 2024 22:07:51 -0500 Subject: [PATCH 3/8] fix: update function name --- examples/src/tools/google_trends.ts | 2 +- libs/langchain-community/src/tools/google_trends.ts | 2 +- .../src/tools/tests/google_trends.int.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/src/tools/google_trends.ts b/examples/src/tools/google_trends.ts index e2a69748bdf2..af669c993846 100644 --- a/examples/src/tools/google_trends.ts +++ b/examples/src/tools/google_trends.ts @@ -4,7 +4,7 @@ export async function run() { const tool = new GoogleTrendsAPI(); - const res = await tool.run("Monster"); + const res = await tool._call("Monster"); console.log(res); } \ No newline at end of file diff --git a/libs/langchain-community/src/tools/google_trends.ts b/libs/langchain-community/src/tools/google_trends.ts index ac5f6cae0d6a..858f0381eb97 100644 --- a/libs/langchain-community/src/tools/google_trends.ts +++ b/libs/langchain-community/src/tools/google_trends.ts @@ -42,7 +42,7 @@ export class GoogleTrendsAPI extends Tool { this.apiKey = apiKey; } - async run(query: string): Promise { + async _call(query: string): Promise { const serpapiApiKey = this.apiKey; const params = new URLSearchParams({ engine: "google_trends", 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 index ed9bfdfdd1d0..fbbe1492071e 100644 --- a/libs/langchain-community/src/tools/tests/google_trends.int.test.ts +++ b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts @@ -10,7 +10,7 @@ describe("GoogleTrendsAPI", () => { test("GoogleTrendsAPI returns expected result for valid query", async () => { const tool = new GoogleTrendsAPI(); - const result = await tool.run("Island"); + const result = await tool._call("Island"); expect(result).toContain("Query: Coffee"); expect(result).toContain("Date From:"); @@ -27,7 +27,7 @@ describe("GoogleTrendsAPI", () => { test("GoogleTrendsAPI returns 'No good Trend Result was found' for a non-existent query", async () => { const tool = new GoogleTrendsAPI(); - const result = await tool.run("hhdhbfsjbfwl"); + const result = await tool._call("hhdhbfsjbfwl"); expect(result).toBe("No good Trend Result was found"); }); From f1e4788a72cf9069a329efe9185b050f5393185e Mon Sep 17 00:00:00 2001 From: Roger Zhao Date: Wed, 20 Nov 2024 05:33:52 -0500 Subject: [PATCH 4/8] fix: minor test and other changes --- docs/core_docs/docs/integrations/tools/google_trends.mdx | 2 +- libs/langchain-community/src/load/import_type.ts | 1 - libs/langchain-community/src/tools/google_trends.ts | 6 +++--- .../src/tools/tests/google_trends.int.test.ts | 7 +++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/core_docs/docs/integrations/tools/google_trends.mdx b/docs/core_docs/docs/integrations/tools/google_trends.mdx index 9bc5b3e0fe1e..14e833047f06 100644 --- a/docs/core_docs/docs/integrations/tools/google_trends.mdx +++ b/docs/core_docs/docs/integrations/tools/google_trends.mdx @@ -6,7 +6,7 @@ import CodeBlock from "@theme/CodeBlock"; # Google Trends Tool -The Google Trends Tool allows your agent to utilize the Google Trends API to retrieve and analyze search interest data. +The Google Trends Tool allows your agent to utilize the Google Trends API from Serp API to retrieve and analyze search interest data. This can be useful for understanding trending topics, regional search interest, and historical popularity of search terms. ## Setup diff --git a/libs/langchain-community/src/load/import_type.ts b/libs/langchain-community/src/load/import_type.ts index 65b6b689a7da..51536706142c 100644 --- a/libs/langchain-community/src/load/import_type.ts +++ b/libs/langchain-community/src/load/import_type.ts @@ -26,7 +26,6 @@ export interface SecretMap { GOOGLE_API_KEY?: string; GOOGLE_PALM_API_KEY?: string; GOOGLE_PLACES_API_KEY?: string; - GOOGLE_TRENDS_API_KEY?: string; GOOGLE_ROUTES_API_KEY?: string; GRADIENT_ACCESS_TOKEN?: string; GRADIENT_WORKSPACE_ID?: string; diff --git a/libs/langchain-community/src/tools/google_trends.ts b/libs/langchain-community/src/tools/google_trends.ts index 858f0381eb97..c8bf4a55554d 100644 --- a/libs/langchain-community/src/tools/google_trends.ts +++ b/libs/langchain-community/src/tools/google_trends.ts @@ -19,7 +19,7 @@ export class GoogleTrendsAPI extends Tool { get lc_secrets(): { [key: string]: string } | undefined { return { - apiKey: "GOOGLE_TRENDS_API_KEY", + apiKey: "SERPAPI_API_KEY", }; } @@ -33,10 +33,10 @@ export class GoogleTrendsAPI extends Tool { constructor(fields?: GoogleTrendsAPIParams) { super(...arguments); const apiKey = - fields?.apiKey ?? getEnvironmentVariable("GOOGLE_TRENDS_API_KEY"); + fields?.apiKey ?? getEnvironmentVariable("SERPAPI_API_KEY"); if (apiKey === undefined) { throw new Error( - `Google Trends API key not set. You can set it as "GOOGLE_TRENDS_API_KEY" in your environment variables.` + `Google Trends API key not set. You can set it as "SERPAPI_API_KEY" in your environment variables.` ); } this.apiKey = apiKey; 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 index fbbe1492071e..14c1ba9f7955 100644 --- a/libs/langchain-community/src/tools/tests/google_trends.int.test.ts +++ b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts @@ -10,7 +10,7 @@ describe("GoogleTrendsAPI", () => { test("GoogleTrendsAPI returns expected result for valid query", async () => { const tool = new GoogleTrendsAPI(); - const result = await tool._call("Island"); + const result = await tool._call("Coffee"); expect(result).toContain("Query: Coffee"); expect(result).toContain("Date From:"); @@ -26,10 +26,9 @@ describe("GoogleTrendsAPI", () => { test("GoogleTrendsAPI returns 'No good Trend Result was found' for a non-existent query", async () => { const tool = new GoogleTrendsAPI(); - + const result = await tool._call("hhdhbfsjbfwl"); - + expect(result).toBe("No good Trend Result was found"); }); - }); From 58f159730d694ac47cbb827925dc53fb64624e65 Mon Sep 17 00:00:00 2001 From: nishus24 <128431351+nishus24@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:29:14 -0500 Subject: [PATCH 5/8] fix: remove related topics endpoint implementation --- .../src/tools/google_trends.ts | 44 +++++++++---------- .../src/tools/tests/google_trends.int.test.ts | 6 +-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/libs/langchain-community/src/tools/google_trends.ts b/libs/langchain-community/src/tools/google_trends.ts index c8bf4a55554d..2ebc9a940a2c 100644 --- a/libs/langchain-community/src/tools/google_trends.ts +++ b/libs/langchain-community/src/tools/google_trends.ts @@ -76,27 +76,27 @@ export class GoogleTrendsAPI extends Tool { 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_TOPICS", - q: query, - }); + // const relatedParams = new URLSearchParams({ + // engine: "google_trends", + // api_key: serpapiApiKey, + // data_type: "RELATED_TOPICS", + // q: query, + // }); - const relatedRes = await fetch(`https://serpapi.com/search.json?${relatedParams.toString()}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); + // const relatedRes = await fetch(`https://serpapi.com/search.json?${relatedParams.toString()}`, { + // method: "GET", + // headers: { + // "Content-Type": "application/json", + // }, + // }); - if (!relatedRes.ok) { - throw new Error(`Error fetching related queries from SerpAPI: ${relatedRes.statusText}`); - } + // if (!relatedRes.ok) { + // throw new Error(`Error fetching related queries from SerpAPI: ${relatedRes.statusText}`); + // } - const relatedDict = await relatedRes.json(); - const rising = relatedDict.related_queries?.rising?.map((result: any) => result.query) ?? []; - const top = relatedDict.related_queries?.top?.map((result: any) => result.query) ?? []; + // const relatedDict = await relatedRes.json(); + // const rising = relatedDict.related_queries?.rising?.map((result: any) => result.query) ?? []; + // const top = relatedDict.related_queries?.top?.map((result: any) => result.query) ?? []; const doc = [ `Query: ${query}`, @@ -106,12 +106,12 @@ export class GoogleTrendsAPI extends Tool { `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(", ")}`, + // `Trend values: ${values.join(", ")}`, + // `Rising Related Queries: ${rising.join(", ")}`, + // `Top Related Queries: ${top.join(", ")}`, ]; return doc.join("\n\n"); } -} \ No newline at end of file +} 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 index 14c1ba9f7955..fcfbf4e60476 100644 --- a/libs/langchain-community/src/tools/tests/google_trends.int.test.ts +++ b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts @@ -19,9 +19,9 @@ describe("GoogleTrendsAPI", () => { 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:"); + // expect(result).toContain("Trend values:"); + // expect(result).toContain("Rising Related Queries:"); + // expect(result).toContain("Top Related Queries:"); }); test("GoogleTrendsAPI returns 'No good Trend Result was found' for a non-existent query", async () => { From 67d7168ddbc2f1e60b299ffbd533c86ff355dd61 Mon Sep 17 00:00:00 2001 From: Roger Zhao Date: Fri, 22 Nov 2024 05:00:56 -0500 Subject: [PATCH 6/8] fix: fix related queries --- .../docs/integrations/tools/google_trends.mdx | 6 +- examples/src/tools/google_trends.ts | 5 +- .../src/tools/google_trends.ts | 218 ++++++++++-------- .../src/tools/tests/google_trends.int.test.ts | 8 +- 4 files changed, 130 insertions(+), 107 deletions(-) diff --git a/docs/core_docs/docs/integrations/tools/google_trends.mdx b/docs/core_docs/docs/integrations/tools/google_trends.mdx index 14e833047f06..9c8214b90866 100644 --- a/docs/core_docs/docs/integrations/tools/google_trends.mdx +++ b/docs/core_docs/docs/integrations/tools/google_trends.mdx @@ -6,12 +6,12 @@ import CodeBlock from "@theme/CodeBlock"; # Google Trends Tool -The Google Trends Tool allows your agent to utilize the Google Trends API from Serp API to retrieve and analyze search interest data. +The Google Trends Tool allows your agent to utilize the Google Trends API from Serp API to retrieve and analyze search interest data. This can be useful for understanding trending topics, regional search interest, and historical popularity of search terms. ## Setup -To use this tool, you'll need to configure access to the Google Trends API. The integration relies on the unofficial +To use this tool, you'll need to configure access to the Google Trends API. The integration relies on the unofficial Google Trends library, as Google does not provide a formal API for accessing trends data. Get an API key from [SERPApi](https://serpapi.com/users/sign_in) @@ -34,4 +34,4 @@ import ToolExample from "@examples/tools/google_trends.ts"; ## Related -- Tool [https://serpapi.com/google-trends-api] \ No newline at end of file +- Tool [https://serpapi.com/google-trends-api] diff --git a/examples/src/tools/google_trends.ts b/examples/src/tools/google_trends.ts index af669c993846..2934b9a1a7f7 100644 --- a/examples/src/tools/google_trends.ts +++ b/examples/src/tools/google_trends.ts @@ -1,10 +1,9 @@ import { GoogleTrendsAPI } from "@langchain/community/tools/google_trends"; export async function run() { - - const tool = new GoogleTrendsAPI(); + const tool = new GoogleTrendsAPI(); const res = await tool._call("Monster"); console.log(res); -} \ No newline at end of file +} diff --git a/libs/langchain-community/src/tools/google_trends.ts b/libs/langchain-community/src/tools/google_trends.ts index 2ebc9a940a2c..461dd9594661 100644 --- a/libs/langchain-community/src/tools/google_trends.ts +++ b/libs/langchain-community/src/tools/google_trends.ts @@ -5,113 +5,137 @@ import { Tool } from "@langchain/core/tools"; * Interface for parameters required by GoogleTrendsAPI class. */ export interface GoogleTrendsAPIParams { - apiKey?: string; - } - + apiKey?: string; +} /** - * Tool that queries the Google Trends API + * Tool that queries the Google Trends API. Uses default interest over time. */ export class GoogleTrendsAPI extends Tool { - static lc_name() { - return "GoogleTrendsAPI"; - } - - get lc_secrets(): { [key: string]: string } | undefined { - return { - apiKey: "SERPAPI_API_KEY", - }; - } - - name = "google_trends"; - - protected apiKey: string; + static lc_name() { + return "GoogleTrendsAPI"; + } - description = `A wrapper around Google Trends API. Useful for analyzing and retrieving trending search data based on keywords, + 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?: GoogleTrendsAPIParams) { - 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.` - ); + constructor(fields?: GoogleTrendsAPIParams) { + 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. + */ + 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", + }, } - this.apiKey = apiKey; + ); + + 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"; } - async _call(query: string): Promise { - 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: any) => 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_TOPICS", - // q: query, - // }); - - // const relatedRes = await fetch(`https://serpapi.com/search.json?${relatedParams.toString()}`, { - // method: "GET", - // headers: { - // "Content-Type": "application/json", - // }, - // }); - - // if (!relatedRes.ok) { - // throw new Error(`Error fetching related queries from SerpAPI: ${relatedRes.statusText}`); - // } - - // const relatedDict = await relatedRes.json(); - // const rising = relatedDict.related_queries?.rising?.map((result: any) => result.query) ?? []; - // const top = relatedDict.related_queries?.top?.map((result: any) => 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"); + const startDate = totalResults[0].date.split(" "); + const endDate = totalResults[totalResults.length - 1].date.split(" "); + const values = totalResults.map( + (result: any) => 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) { + console.error( + `Error fetching related queries from SerpAPI: ${relatedRes.statusText}` + ); + } else { + const relatedDict = await relatedRes.json(); + rising = + relatedDict.related_queries?.rising?.map( + (result: any) => result.query + ) ?? []; + top = + relatedDict.related_queries?.top?.map((result: any) => 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 index fcfbf4e60476..e51175be998b 100644 --- a/libs/langchain-community/src/tools/tests/google_trends.int.test.ts +++ b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts @@ -19,15 +19,15 @@ describe("GoogleTrendsAPI", () => { 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:"); + expect(result).toContain("Trend values:"); + expect(result).toContain("Rising Related Queries:"); + expect(result).toContain("Top Related Queries:"); }); test("GoogleTrendsAPI returns 'No good Trend Result was found' for a non-existent query", async () => { const tool = new GoogleTrendsAPI(); - const result = await tool._call("hhdhbfsjbfwl"); + const result = await tool._call("earghajgpajrpgjaprgag"); expect(result).toBe("No good Trend Result was found"); }); From 510433b8237e775d295212ffaf99366e132ecc92 Mon Sep 17 00:00:00 2001 From: Roger Zhao Date: Sat, 30 Nov 2024 06:19:10 -0500 Subject: [PATCH 7/8] docs: update docs --- .../docs/integrations/tools/google_trends.mdx | 14 ++++++++------ libs/langchain-community/src/load/import_map.ts | 1 - libs/langchain-scripts/src/_data/importMap.ts | 10 ---------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/docs/core_docs/docs/integrations/tools/google_trends.mdx b/docs/core_docs/docs/integrations/tools/google_trends.mdx index 9c8214b90866..f5fc69985e8e 100644 --- a/docs/core_docs/docs/integrations/tools/google_trends.mdx +++ b/docs/core_docs/docs/integrations/tools/google_trends.mdx @@ -6,17 +6,18 @@ import CodeBlock from "@theme/CodeBlock"; # Google Trends Tool -The Google Trends Tool allows your agent to utilize the Google Trends API from Serp API to retrieve and analyze search interest data. +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) + ## Setup -To use this tool, you'll need to configure access to the Google Trends API. The integration relies on the unofficial -Google Trends library, as Google does not provide a formal API for accessing trends data. +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) +Get an API key from [SerpApi](https://serpapi.com/users/sign_in) -Then, set your API key as `process.env.GOOGLE_PLACES_API_KEY` or pass it in as an `apiKey` constructor argument. +Then, set your API key as `process.env.SERPAPI_API_KEY` or pass it in as an `apiKey` constructor argument. ## Usage @@ -34,4 +35,5 @@ import ToolExample from "@examples/tools/google_trends.ts"; ## Related -- Tool [https://serpapi.com/google-trends-api] +- Tool [conceptual guide](/docs/concepts/tools) +- Tool [how-to guides](/docs/how_to/#tools) diff --git a/libs/langchain-community/src/load/import_map.ts b/libs/langchain-community/src/load/import_map.ts index 53551b8da006..8b3b734a82c1 100644 --- a/libs/langchain-community/src/load/import_map.ts +++ b/libs/langchain-community/src/load/import_map.ts @@ -11,7 +11,6 @@ export * as tools__dynamic from "../tools/dynamic.js"; export * as tools__dataforseo_api_search from "../tools/dataforseo_api_search.js"; export * as tools__google_custom_search from "../tools/google_custom_search.js"; export * as tools__google_places from "../tools/google_places.js"; -export * as tools__google_trends from "../tools/google_trends.js"; export * as tools__google_routes from "../tools/google_routes.js"; export * as tools__ifttt from "../tools/ifttt.js"; export * as tools__searchapi from "../tools/searchapi.js"; diff --git a/libs/langchain-scripts/src/_data/importMap.ts b/libs/langchain-scripts/src/_data/importMap.ts index 7a21bd31c957..1bbb79ef46e9 100644 --- a/libs/langchain-scripts/src/_data/importMap.ts +++ b/libs/langchain-scripts/src/_data/importMap.ts @@ -2920,16 +2920,6 @@ export const importMap: Array = [ new: "@langchain/community/tools/google_places", namedImport: "GooglePlacesAPI", }, - { - old: "langchain/tools/google_trends", - new: "@langchain/community/tools/google_trends", - namedImport: "GoogleTrendsAPIParams", - }, - { - old: "langchain/tools/google_trends", - new: "@langchain/community/tools/google_trends", - namedImport: "GoogleTrendsAPI", - }, { old: "langchain/tools/*", new: "@langchain/community/tools/google_custom_search", From f314c199d3f9fbfa321060d5f6b3dd6d5ff31d69 Mon Sep 17 00:00:00 2001 From: Roger Zhao Date: Tue, 3 Dec 2024 20:40:19 -0500 Subject: [PATCH 8/8] refactor: rename GoogleTrendsAPI to SERPGoogleTrendsTool. Also Add new interfaces for lint warnings, and minor documentation updates --- .../docs/integrations/tools/google_trends.mdx | 3 ++ examples/src/tools/google_trends.ts | 6 +-- .../src/tools/google_trends.ts | 38 ++++++++++++++----- .../src/tools/tests/google_trends.int.test.ts | 14 +++---- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/docs/core_docs/docs/integrations/tools/google_trends.mdx b/docs/core_docs/docs/integrations/tools/google_trends.mdx index f5fc69985e8e..bbbdec5d70c0 100644 --- a/docs/core_docs/docs/integrations/tools/google_trends.mdx +++ b/docs/core_docs/docs/integrations/tools/google_trends.mdx @@ -11,6 +11,9 @@ This can be useful for understanding trending topics, regional search interest, 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. diff --git a/examples/src/tools/google_trends.ts b/examples/src/tools/google_trends.ts index 2934b9a1a7f7..25cf5f174ba3 100644 --- a/examples/src/tools/google_trends.ts +++ b/examples/src/tools/google_trends.ts @@ -1,9 +1,9 @@ -import { GoogleTrendsAPI } from "@langchain/community/tools/google_trends"; +import { SERPGoogleTrendsTool } from "@langchain/community/tools/google_trends"; export async function run() { - const tool = new GoogleTrendsAPI(); + const tool = new SERPGoogleTrendsTool(); - const res = await tool._call("Monster"); + const res = await tool.invoke("Monster"); console.log(res); } diff --git a/libs/langchain-community/src/tools/google_trends.ts b/libs/langchain-community/src/tools/google_trends.ts index 461dd9594661..18f063e791e8 100644 --- a/libs/langchain-community/src/tools/google_trends.ts +++ b/libs/langchain-community/src/tools/google_trends.ts @@ -2,18 +2,33 @@ import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { Tool } from "@langchain/core/tools"; /** - * Interface for parameters required by GoogleTrendsAPI class. + * Interfaces for the response from the SerpApi Google Trends API. */ -export interface GoogleTrendsAPIParams { +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 GoogleTrendsAPI extends Tool { +export class SERPGoogleTrendsTool extends Tool { static lc_name() { - return "GoogleTrendsAPI"; + return "SERPGoogleTrendsTool"; } get lc_secrets(): { [key: string]: string } | undefined { @@ -29,7 +44,7 @@ export class GoogleTrendsAPI extends Tool { 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?: GoogleTrendsAPIParams) { + constructor(fields?: SERPGoogleTrendsToolParams) { super(...arguments); const apiKey = fields?.apiKey ?? getEnvironmentVariable("SERPAPI_API_KEY"); if (apiKey === undefined) { @@ -45,6 +60,9 @@ export class GoogleTrendsAPI extends Tool { * 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"); @@ -80,7 +98,7 @@ export class GoogleTrendsAPI extends Tool { const startDate = totalResults[0].date.split(" "); const endDate = totalResults[totalResults.length - 1].date.split(" "); const values = totalResults.map( - (result: any) => result.values[0].extracted_value + (result: TimelineData) => result.values[0].extracted_value ); const minValue = Math.min(...values); const maxValue = Math.max(...values); @@ -109,6 +127,7 @@ export class GoogleTrendsAPI extends Tool { 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}` ); @@ -116,11 +135,12 @@ export class GoogleTrendsAPI extends Tool { const relatedDict = await relatedRes.json(); rising = relatedDict.related_queries?.rising?.map( - (result: any) => result.query + (result: { query: string }) => result.query ) ?? []; top = - relatedDict.related_queries?.top?.map((result: any) => result.query) ?? - []; + relatedDict.related_queries?.top?.map( + (result: { query: string }) => result.query + ) ?? []; } const doc = [ 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 index e51175be998b..49be189bd7ad 100644 --- a/libs/langchain-community/src/tools/tests/google_trends.int.test.ts +++ b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts @@ -1,14 +1,14 @@ import { expect, describe } from "@jest/globals"; -import { GoogleTrendsAPI } from "../google_trends.js"; +import { SERPGoogleTrendsTool } from "../google_trends.js"; -describe("GoogleTrendsAPI", () => { +describe("SERPGoogleTrendsTool", () => { test("should be setup with correct parameters", async () => { - const instance = new GoogleTrendsAPI(); + const instance = new SERPGoogleTrendsTool(); expect(instance.name).toBe("google_trends"); }); - test("GoogleTrendsAPI returns expected result for valid query", async () => { - const tool = new GoogleTrendsAPI(); + test("SERPGoogleTrendsTool returns expected result for valid query", async () => { + const tool = new SERPGoogleTrendsTool(); const result = await tool._call("Coffee"); @@ -24,8 +24,8 @@ describe("GoogleTrendsAPI", () => { expect(result).toContain("Top Related Queries:"); }); - test("GoogleTrendsAPI returns 'No good Trend Result was found' for a non-existent query", async () => { - const tool = new GoogleTrendsAPI(); + 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");