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

Add Gmail Tool #3438

Merged
merged 65 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
d3fca11
Add GmailBaseTool
uthmanmoh Nov 10, 2023
4f4523a
Add GmailGetMessage
uthmanmoh Nov 10, 2023
0cbf52a
Fix eslint formatting errors
uthmanmoh Nov 10, 2023
f6fe06c
fix: _call returns stringified output now
HamoonZamiri Nov 10, 2023
8e5a2f0
feat: create gmail draft message complete
HamoonZamiri Nov 10, 2023
01904c9
fix: remove unused parameter
HamoonZamiri Nov 11, 2023
c351369
fix: removed unused import statement
HamoonZamiri Nov 11, 2023
7f87220
fix: reformatted file and made method private
HamoonZamiri Nov 11, 2023
717f999
Add GmailGetThread
saeedahsan Nov 10, 2023
2bd4c4e
Fixes formatting issues
saeedahsan Nov 11, 2023
8cfc9b7
Fix _call error
saeedahsan Nov 11, 2023
71636e6
Add GmailSearch
uthmanmoh Nov 11, 2023
84eea6f
Fix build error on types
uthmanmoh Nov 11, 2023
5279e40
Create sent_message.ts
SeannnX Nov 15, 2023
b2b3b4b
Update sent_message.ts
SeannnX Nov 16, 2023
8e8ae11
Update sent_message.ts
SeannnX Nov 16, 2023
370ff58
Move gmail object from children to parent GmailBaseTool
uthmanmoh Nov 19, 2023
c137228
Fix formatting in gmail/base.ts
uthmanmoh Nov 19, 2023
23dc04d
Merge pull request #6 from 1239uth/refactor
HamoonZamiri Nov 20, 2023
507479a
fix: switched to Buffer class for base64 encode
HamoonZamiri Nov 20, 2023
c5ad3cc
Merge pull request #7 from 1239uth/base64-draft-fix
HamoonZamiri Nov 21, 2023
3b8f3fd
Make fields optional and use defaults properly in constructor
uthmanmoh Nov 23, 2023
c087834
Use Zod to parse input of GmailBaseTool constructor
uthmanmoh Nov 24, 2023
eb07a2f
Update zod schema to be entirely optional for GmailBaseToolParams
uthmanmoh Nov 25, 2023
3a09b53
Merge pull request #8 from 1239uth/refactor
uthmanmoh Nov 26, 2023
27120bd
Create docs for Gmail Tool
saeedahsan Nov 27, 2023
7600b94
Add comment for default parameters, fix formatting
saeedahsan Nov 27, 2023
c377695
Remove model from default parameters comment
saeedahsan Nov 27, 2023
54addef
Merge pull request #9 from 1239uth/docs
saeedahsan Nov 27, 2023
52095f5
Add relavent tools in gmail example
saeedahsan Nov 27, 2023
67d153e
Merge pull request #10 from 1239uth/docs
saeedahsan Nov 27, 2023
1d473aa
Add index.ts for all exports and rename send_message
uthmanmoh Nov 28, 2023
c6f599f
Add unit tests for gmail tools
uthmanmoh Nov 28, 2023
25cde55
Merge pull request #11 from 1239uth/refactor
uthmanmoh Nov 28, 2023
25dd823
Add gmail type definitions to package.json
uthmanmoh Nov 28, 2023
29fe08d
Merge pull request #12 from 1239uth/refactor
uthmanmoh Nov 28, 2023
73256ee
Update typedoc.json
SeannnX Nov 30, 2023
b818710
Update create-entrypoints.js
SeannnX Nov 30, 2023
51b6f3a
add description for our function
SeannnX Nov 30, 2023
00cef2a
update .gitignore
SeannnX Nov 30, 2023
a2ef016
fix the entrypoint
SeannnX Nov 30, 2023
4c09668
change order
SeannnX Nov 30, 2023
f4a4c6f
change the zod
SeannnX Nov 30, 2023
16c4af5
fix the format
SeannnX Nov 30, 2023
98c0e82
Update base.ts
SeannnX Nov 30, 2023
5056391
Update base.ts
SeannnX Nov 30, 2023
5e9e802
Merge pull request #19 from 1239uth/gmail_add_entrypoint
SeannnX Nov 30, 2023
5885d7a
Merge pull request #16 from 1239uth/gmail_doc_only
SeannnX Nov 30, 2023
1d79258
add description for search
SeannnX Nov 30, 2023
fc10255
fix: gmail tools extend structured tool
HamoonZamiri Nov 30, 2023
a2b053f
Update descriptions.ts
SeannnX Nov 30, 2023
08b31a2
fix: tree shaking issues with zod fixed
HamoonZamiri Nov 30, 2023
12f92d4
fix: prettier formatting
HamoonZamiri Dec 1, 2023
eda0273
Add zod back to GmailBaseTool
uthmanmoh Dec 1, 2023
31c0439
Merge pull request #18 from 1239uth/add_description
SeannnX Dec 1, 2023
695bd7f
Fix gmail example to work for StructuredTool
uthmanmoh Dec 1, 2023
8f5ab7c
Add gmail API key setup instructions in docs
uthmanmoh Dec 1, 2023
b3ce7c4
Fix formatting
uthmanmoh Dec 1, 2023
1c112fc
Merge pull request #21 from 1239uth/docs
uthmanmoh Dec 1, 2023
3f5f234
Merge branch 'gmail_feature' into refactor
HamoonZamiri Dec 1, 2023
7123751
Merge pull request #20 from 1239uth/refactor
SeannnX Dec 1, 2023
61fdedd
Merge branch 'main' of github.com:langchain-ai/langchainjs into gmail…
uthmanmoh Dec 4, 2023
a50e8f7
Fix formatting
uthmanmoh Dec 4, 2023
676796f
Replace .call with .invoke in gmail example
uthmanmoh Dec 4, 2023
9d4e09e
Update gmail.ts
jacoblee93 Dec 4, 2023
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/typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"./langchain/src/tools/render.ts",
"./langchain/src/tools/sql.ts",
"./langchain/src/tools/webbrowser.ts",
"./langchain/src/tools/gmail/index.ts",
"./langchain/src/tools/google_calendar/index.ts",
"./langchain/src/tools/google_places.ts",
"./langchain/src/chains/index.ts",
Expand Down
27 changes: 27 additions & 0 deletions docs/core_docs/docs/integrations/tools/gmail.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
hide_table_of_contents: true
---

import CodeBlock from "@theme/CodeBlock";

# Gmail Tool

The Gmail Tool allows your agent to create and view messages from a linked email account.

## Setup

You will need to get an API key from [Google here](https://developers.google.com/gmail/api/guides)
and [enable the new Gmail API](https://console.cloud.google.com/apis/library/gmail.googleapis.com).
Then, set the environment variables for `GMAIL_CLIENT_EMAIL`, and either `GMAIL_PRIVATE_KEY`, or `GMAIL_KEYFILE`.

To use the Gmail Tool you need to install the following official peer dependency:
uthmanmoh marked this conversation as resolved.
Show resolved Hide resolved

```bash npm2yarn
npm install googleapis
```

## Usage

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

<CodeBlock language="typescript">{ToolExample}</CodeBlock>
56 changes: 56 additions & 0 deletions examples/src/tools/gmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { initializeAgentExecutorWithOptions } from "langchain/agents";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds code that explicitly accesses an environment variable via process.env. Please review this change to ensure proper handling and security of environment variables.

import { OpenAI } from "langchain/llms/openai";
import { StructuredTool } from "langchain/tools";
import {
GmailCreateDraft,
GmailGetMessage,
GmailGetThread,
GmailSearch,
GmailSendMessage,
} from "langchain/tools/gmail";

export async function run() {
const model = new OpenAI({
temperature: 0,
openAIApiKey: process.env.OPENAI_API_KEY,
});

// These are the default parameters for the Gmail tools
// const gmailParams = {
// credentials: {
// clientEmail: process.env.GMAIL_CLIENT_EMAIL,
// privateKey: process.env.GMAIL_PRIVATE_KEY,
// },
// scopes: ["https://mail.google.com/"],
// };

// For custom parameters, uncomment the code above, replace the values with your own, and pass it to the tools below
const tools: StructuredTool[] = [
new GmailCreateDraft(),
new GmailGetMessage(),
new GmailGetThread(),
new GmailSearch(),
new GmailSendMessage(),
];

const gmailAgent = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "structured-chat-zero-shot-react-description",
verbose: true,
});

const createInput = `Create a gmail draft for me to edit of a letter from the perspective of a sentient parrot who is looking to collaborate on some research with her estranged friend, a cat. Under no circumstances may you send the message, however.`;

const createResult = await gmailAgent.call({ input: createInput });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer .invoke for all of these - we're moving everything to that single syntax

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, made that change

// Create Result {
// output: 'I have created a draft email for you to edit. The draft Id is r5681294731961864018.'
// }
console.log("Create Result", createResult);

const viewInput = `Could you search in my drafts for the latest email?`;

const viewResult = await gmailAgent.call({ input: viewInput });
// View Result {
// output: "The latest email in your drafts is from [email protected] with the subject 'Collaboration Opportunity'. The body of the email reads: 'Dear [Friend], I hope this letter finds you well. I am writing to you in the hopes of rekindling our friendship and to discuss the possibility of collaborating on some research together. I know that we have had our differences in the past, but I believe that we can put them aside and work together for the greater good. I look forward to hearing from you. Sincerely, [Parrot]'"
// }
console.log("View Result", viewResult);
}
3 changes: 3 additions & 0 deletions langchain/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ tools/sql.d.ts
tools/webbrowser.cjs
tools/webbrowser.js
tools/webbrowser.d.ts
tools/gmail.cjs
tools/gmail.js
tools/gmail.d.ts
tools/google_calendar.cjs
tools/google_calendar.js
tools/google_calendar.d.ts
Expand Down
8 changes: 8 additions & 0 deletions langchain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@
"tools/webbrowser.cjs",
"tools/webbrowser.js",
"tools/webbrowser.d.ts",
"tools/gmail.cjs",
"tools/gmail.js",
"tools/gmail.d.ts",
"tools/google_calendar.cjs",
"tools/google_calendar.js",
"tools/google_calendar.d.ts",
Expand Down Expand Up @@ -1571,6 +1574,11 @@
"import": "./tools/webbrowser.js",
"require": "./tools/webbrowser.cjs"
},
"./tools/gmail": {
"types": "./tools/gmail.d.ts",
"import": "./tools/gmail.js",
"require": "./tools/gmail.cjs"
},
"./tools/google_calendar": {
"types": "./tools/google_calendar.d.ts",
"import": "./tools/google_calendar.js",
Expand Down
7 changes: 5 additions & 2 deletions langchain/scripts/create-entrypoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const entrypoints = {
"agents/toolkits/aws_sfn": "agents/toolkits/aws_sfn",
"agents/toolkits/sql": "agents/toolkits/sql/index",
"agents/format_scratchpad": "agents/format_scratchpad/openai_functions",
"agents/format_scratchpad/openai_tools": "agents/format_scratchpad/openai_tools",
"agents/format_scratchpad/openai_tools":
"agents/format_scratchpad/openai_tools",
"agents/format_scratchpad/log": "agents/format_scratchpad/log",
"agents/format_scratchpad/xml": "agents/format_scratchpad/xml",
"agents/format_scratchpad/log_to_message":
Expand All @@ -35,6 +36,7 @@ const entrypoints = {
"tools/render": "tools/render",
"tools/sql": "tools/sql",
"tools/webbrowser": "tools/webbrowser",
"tools/gmail": "tools/gmail/index",
"tools/google_calendar": "tools/google_calendar/index",
"tools/google_places": "tools/google_places",
// chains
Expand Down Expand Up @@ -324,7 +326,7 @@ const entrypoints = {
// evaluation
evaluation: "evaluation/index",
// runnables
"runnables": "runnables/index",
runnables: "runnables/index",
"runnables/remote": "runnables/remote",
};

Expand Down Expand Up @@ -352,6 +354,7 @@ const requiresOptionalDependency = [
"tools/sql",
"tools/webbrowser",
"tools/google_calendar",
"tools/gmail",
"callbacks/handlers/llmonitor",
"chains/load",
"chains/sql_db",
Expand Down
1 change: 1 addition & 0 deletions langchain/src/load/import_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const optionalImportEntrypoints = [
"langchain/tools/sql",
"langchain/tools/webbrowser",
"langchain/tools/google_calendar",
"langchain/tools/gmail",
"langchain/chains/load",
"langchain/chains/query_constructor",
"langchain/chains/query_constructor/ir",
Expand Down
3 changes: 3 additions & 0 deletions langchain/src/load/import_type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export interface OptionalImportMap {
"langchain/tools/google_calendar"?:
| typeof import("../tools/google_calendar/index.js")
| Promise<typeof import("../tools/google_calendar/index.js")>;
"langchain/tools/gmail"?:
| typeof import("../tools/gmail/index.js")
| Promise<typeof import("../tools/gmail/index.js")>;
"langchain/chains/load"?:
| typeof import("../chains/load.js")
| Promise<typeof import("../chains/load.js")>;
Expand Down
75 changes: 75 additions & 0 deletions langchain/src/tools/gmail/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { gmail_v1, google } from "googleapis";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds code that explicitly accesses and reads environment variables via getEnvironmentVariable. Please review this change to ensure it is implemented correctly and securely.

import { z } from "zod";
import { StructuredTool } from "../base.js";
import { getEnvironmentVariable } from "../../util/env.js";

export interface GmailBaseToolParams {
credentials?: {
clientEmail?: string;
privateKey?: string;
keyfile?: string;
};
scopes?: string[];
}

export abstract class GmailBaseTool extends StructuredTool {
private CredentialsSchema = z
.object({
clientEmail: z
.string()
.min(1)
.default(getEnvironmentVariable("GMAIL_CLIENT_EMAIL") ?? ""),
privateKey: z
.string()
.default(getEnvironmentVariable("GMAIL_PRIVATE_KEY") ?? ""),
keyfile: z
.string()
.default(getEnvironmentVariable("GMAIL_KEYFILE") ?? ""),
})
.refine(
(credentials) =>
credentials.privateKey !== "" || credentials.keyfile !== "",
{
message:
"Missing GMAIL_PRIVATE_KEY or GMAIL_KEYFILE to interact with Gmail",
}
);

private GmailBaseToolParamsSchema = z
.object({
credentials: this.CredentialsSchema.default({}),
scopes: z.array(z.string()).default(["https://mail.google.com/"]),
})
.default({});

name = "Gmail";

description = "A tool to send and view emails through Gmail";

protected gmail: gmail_v1.Gmail;

constructor(fields?: Partial<GmailBaseToolParams>) {
super(...arguments);

const { credentials, scopes } =
this.GmailBaseToolParamsSchema.parse(fields);

this.gmail = this.getGmail(
scopes,
credentials.clientEmail,
credentials.privateKey,
credentials.keyfile
);
}

private getGmail(
scopes: string[],
email: string,
key?: string,
keyfile?: string
) {
const auth = new google.auth.JWT(email, keyfile, key, scopes);

return google.gmail({ version: "v1", auth });
}
}
74 changes: 74 additions & 0 deletions langchain/src/tools/gmail/create_draft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { z } from "zod";
import { GmailBaseTool, GmailBaseToolParams } from "./base.js";
jacoblee93 marked this conversation as resolved.
Show resolved Hide resolved
import { CREATE_DRAFT_DESCRIPTION } from "./descriptions.js";

export class GmailCreateDraft extends GmailBaseTool {
name = "create_gmail_draft";

schema = z.object({
message: z.string(),
to: z.array(z.string()),
subject: z.string(),
cc: z.array(z.string()).optional(),
bcc: z.array(z.string()).optional(),
});

description = CREATE_DRAFT_DESCRIPTION;

constructor(fields?: GmailBaseToolParams) {
super(fields);
}

private prepareDraftMessage(
message: string,
to: string[],
subject: string,
cc?: string[],
bcc?: string[]
) {
const draftMessage = {
message: {
raw: "",
},
};

const email = [
`To: ${to.join(", ")}`,
`Subject: ${subject}`,
cc ? `Cc: ${cc.join(", ")}` : "",
bcc ? `Bcc: ${bcc.join(", ")}` : "",
"",
message,
].join("\n");

draftMessage.message.raw = Buffer.from(email).toString("base64url");

return draftMessage;
}

async _call(arg: z.output<typeof this.schema>) {
const { message, to, subject, cc, bcc } = arg;
const create_message = this.prepareDraftMessage(
message,
to,
subject,
cc,
bcc
);

const response = await this.gmail.users.drafts.create({
userId: "me",
requestBody: create_message,
});

return `Draft created. Draft Id: ${response.data.id}`;
}
}

export type CreateDraftSchema = {
message: string;
to: string[];
subject: string;
cc?: string[];
bcc?: string[];
};
Loading