Skip to content

Commit

Permalink
feat(community): Added Google Trends Integration (#7248)
Browse files Browse the repository at this point in the history
Co-authored-by: Roger Zhao <[email protected]>
Co-authored-by: nishus24 <[email protected]>
  • Loading branch information
3 people authored Dec 14, 2024
1 parent 3dfaa37 commit f8175ef
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/api_refs/blacklisted-entrypoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
42 changes: 42 additions & 0 deletions docs/core_docs/docs/integrations/tools/google_trends.mdx
Original file line number Diff line number Diff line change
@@ -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";

<IntegrationInstallTooltip></IntegrationInstallTooltip>

```bash npm2yarn
npm install @langchain/openai @langchain/community @langchain/core
```

import ToolExample from "@examples/tools/google_trends.ts";

<CodeBlock language="typescript">{ToolExample}</CodeBlock>

## Related

- Tool [conceptual guide](/docs/concepts/tools)
- Tool [how-to guides](/docs/how_to/#tools)
9 changes: 9 additions & 0 deletions examples/src/tools/google_trends.ts
Original file line number Diff line number Diff line change
@@ -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);
}
4 changes: 4 additions & 0 deletions libs/langchain-community/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions libs/langchain-community/langchain.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions libs/langchain-community/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
161 changes: 161 additions & 0 deletions libs/langchain-community/src/tools/google_trends.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
/**
* 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");
}
}
34 changes: 34 additions & 0 deletions libs/langchain-community/src/tools/tests/google_trends.int.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});

0 comments on commit f8175ef

Please sign in to comment.