Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(community): Added Google Trends Integration #7248

Merged
merged 8 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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",
Expand Down Expand Up @@ -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",
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");
});
});
Loading