Skip to content

Commit

Permalink
Add Gmail Tool (#3438)
Browse files Browse the repository at this point in the history
* Add GmailBaseTool

* Add GmailGetMessage

* Fix eslint formatting errors

* fix: _call returns stringified output now

* feat: create gmail draft message complete

* fix: remove unused parameter

* fix: removed unused import statement

* fix: reformatted file and made method private

* Add GmailGetThread

* Fixes formatting issues

* Fix _call error

* Add GmailSearch

* Fix build error on types

* Create sent_message.ts

* Update sent_message.ts

run the prettier to format the document

* Update sent_message.ts

combine the sendMessage function into _call function

* Move gmail object from children to parent GmailBaseTool

* Fix formatting in gmail/base.ts

* fix: switched to Buffer class for base64 encode

* Make fields optional and use defaults properly in constructor

Previously the default values weren't being used in the constructor, this commit fixes that.

Also fields are now optional in each of the gmail tool constructors since they have defaults
as backups anyways

* Use Zod to parse input of GmailBaseTool constructor

* Update zod schema to be entirely optional for GmailBaseToolParams

* Create docs for Gmail Tool

* Add comment for default parameters, fix formatting

* Remove model from default parameters comment

* Add relavent tools in gmail example

* Add index.ts for all exports and rename send_message

* Add unit tests for gmail tools

* Add gmail type definitions to package.json

* Update typedoc.json

add gmail to typedoc.json

* Update create-entrypoints.js

add the entrypoints for gmail tool

* add description for our function

add example on our description

* update .gitignore

* fix the entrypoint

* change order

* change the zod

* fix the format

* Update base.ts

fix lint problem

* Update base.ts

remove the unuse comment

* add description for search

* fix: gmail tools extend structured tool

* Update descriptions.ts

* fix: tree shaking issues with zod fixed

* fix: prettier formatting

* Add zod back to GmailBaseTool

* Fix gmail example to work for StructuredTool

* Add gmail API key setup instructions in docs

* Fix formatting

* Fix formatting

* Replace .call with .invoke in gmail example

* Update gmail.ts

---------

Co-authored-by: Hamoon Zamiri <[email protected]>
Co-authored-by: saeedahsan <[email protected]>
Co-authored-by: SeannnX <[email protected]>
Co-authored-by: Hamoon <[email protected]>
Co-authored-by: Ahsan Saeed <[email protected]>
Co-authored-by: Jacob Lee <[email protected]>
  • Loading branch information
7 people authored Dec 5, 2023
1 parent d1b8a58 commit 03fa76a
Show file tree
Hide file tree
Showing 17 changed files with 866 additions and 2 deletions.
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:

```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";
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.invoke({ input: createInput });
// 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.invoke({ 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 @@ -9,6 +9,7 @@ export const optionalImportEntrypoints = [
"langchain/tools/calculator",
"langchain/tools/sql",
"langchain/tools/webbrowser",
"langchain/tools/gmail",
"langchain/tools/google_calendar",
"langchain/chains/load",
"langchain/chains/query_constructor",
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 @@ -25,6 +25,9 @@ export interface OptionalImportMap {
"langchain/tools/webbrowser"?:
| typeof import("../tools/webbrowser.js")
| Promise<typeof import("../tools/webbrowser.js")>;
"langchain/tools/gmail"?:
| typeof import("../tools/gmail/index.js")
| Promise<typeof import("../tools/gmail/index.js")>;
"langchain/tools/google_calendar"?:
| typeof import("../tools/google_calendar/index.js")
| Promise<typeof import("../tools/google_calendar/index.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";
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";
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

0 comments on commit 03fa76a

Please sign in to comment.