Skip to content

Commit 8f783fd

Browse files
authored
Google oauth support for Google calender tool (#7788)
1 parent 807e3de commit 8f783fd

File tree

8 files changed

+123
-63
lines changed

8 files changed

+123
-63
lines changed

libs/langchain-community/src/tools/google_calendar/base.ts

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { google } from "googleapis";
1+
import { google, calendar_v3 } from "googleapis";
22
import { Tool } from "@langchain/core/tools";
33
import { getEnvironmentVariable } from "@langchain/core/utils/env";
44
import { BaseLanguageModel } from "@langchain/core/language_models/base";
@@ -7,6 +7,9 @@ export interface GoogleCalendarAgentParams {
77
credentials?: {
88
clientEmail?: string;
99
privateKey?: string;
10+
keyfile?: string;
11+
subject?: string;
12+
accessToken?: string | (() => Promise<string>);
1013
calendarId?: string;
1114
};
1215
scopes?: string[];
@@ -19,22 +22,23 @@ export class GoogleCalendarBase extends Tool {
1922
description =
2023
"A tool to lookup Google Calendar events and create events in Google Calendar";
2124

22-
protected clientEmail: string;
23-
24-
protected privateKey: string;
25-
2625
protected calendarId: string;
2726

28-
protected scopes: string[];
29-
3027
protected llm: BaseLanguageModel;
3128

29+
protected params: GoogleCalendarAgentParams;
30+
31+
protected calendar?: calendar_v3.Calendar;
32+
3233
constructor(
33-
fields: GoogleCalendarAgentParams = {
34+
{ credentials, scopes, model }: GoogleCalendarAgentParams = {
3435
credentials: {
3536
clientEmail: getEnvironmentVariable("GOOGLE_CALENDAR_CLIENT_EMAIL"),
3637
privateKey: getEnvironmentVariable("GOOGLE_CALENDAR_PRIVATE_KEY"),
37-
calendarId: getEnvironmentVariable("GOOGLE_CALENDAR_CALENDAR_ID"),
38+
keyfile: getEnvironmentVariable("GOOGLE_CALENDAR_KEYFILE"),
39+
subject: getEnvironmentVariable("GOOGLE_CALENDAR_SUBJECT"),
40+
calendarId:
41+
getEnvironmentVariable("GOOGLE_CALENDAR_CALENDAR_ID") || "primary",
3842
},
3943
scopes: [
4044
"https://www.googleapis.com/auth/calendar",
@@ -44,52 +48,76 @@ export class GoogleCalendarBase extends Tool {
4448
) {
4549
super(...arguments);
4650

47-
if (!fields.model) {
51+
if (!model) {
4852
throw new Error("Missing llm instance to interact with Google Calendar");
4953
}
5054

51-
if (!fields.credentials) {
55+
if (!credentials) {
5256
throw new Error("Missing credentials to authenticate to Google Calendar");
5357
}
5458

55-
if (!fields.credentials.clientEmail) {
56-
throw new Error(
57-
"Missing GOOGLE_CALENDAR_CLIENT_EMAIL to interact with Google Calendar"
58-
);
59+
if (!credentials.accessToken) {
60+
if (!credentials.clientEmail) {
61+
throw new Error(
62+
"Missing GOOGLE_CALENDAR_CLIENT_EMAIL to interact with Google Calendar"
63+
);
64+
}
65+
66+
if (!credentials.privateKey && !credentials.keyfile) {
67+
throw new Error(
68+
"Missing GOOGLE_CALENDAR_PRIVATE_KEY or GOOGLE_CALENDAR_KEYFILE or accessToken to interact with Google Calendar"
69+
);
70+
}
5971
}
6072

61-
if (!fields.credentials.privateKey) {
62-
throw new Error(
63-
"Missing GOOGLE_CALENDAR_PRIVATE_KEY to interact with Google Calendar"
64-
);
65-
}
66-
67-
if (!fields.credentials.calendarId) {
73+
if (!credentials.calendarId) {
6874
throw new Error(
6975
"Missing GOOGLE_CALENDAR_CALENDAR_ID to interact with Google Calendar"
7076
);
7177
}
7278

73-
this.clientEmail = fields.credentials.clientEmail;
74-
this.privateKey = fields.credentials.privateKey;
75-
this.calendarId = fields.credentials.calendarId;
76-
this.scopes = fields.scopes || [];
77-
this.llm = fields.model;
79+
this.params = { credentials, scopes };
80+
this.calendarId = credentials.calendarId;
81+
this.llm = model;
7882
}
7983

8084
getModel() {
8185
return this.llm;
8286
}
8387

84-
async getAuth() {
88+
async getCalendarClient() {
89+
const { credentials, scopes } = this.params;
90+
91+
if (credentials?.accessToken) {
92+
// always return a new instance so that we don't end up using expired access tokens
93+
const auth = new google.auth.OAuth2();
94+
const accessToken =
95+
typeof credentials.accessToken === "function"
96+
? await credentials.accessToken()
97+
: credentials.accessToken;
98+
99+
auth.setCredentials({
100+
// get fresh access token if a function is provided
101+
access_token: accessToken,
102+
});
103+
return google.calendar({ version: "v3", auth });
104+
}
105+
106+
// when not using access token its ok to use singleton instance
107+
if (this.calendar) {
108+
return this.calendar;
109+
}
110+
85111
const auth = new google.auth.JWT(
86-
this.clientEmail,
87-
undefined,
88-
this.privateKey,
89-
this.scopes
112+
credentials?.clientEmail,
113+
credentials?.keyfile,
114+
credentials?.privateKey,
115+
scopes,
116+
credentials?.subject
90117
);
91118

92-
return auth;
119+
this.calendar = google.calendar({ version: "v3", auth });
120+
return this.calendar;
93121
}
94122

95123
async _call(input: string) {

libs/langchain-community/src/tools/google_calendar/commands/run-create-events.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { google, calendar_v3 } from "googleapis";
2-
import type { JWT, GaxiosResponse } from "googleapis-common";
1+
import { calendar_v3 } from "googleapis";
2+
import type { GaxiosResponse } from "googleapis-common";
33
import { PromptTemplate } from "@langchain/core/prompts";
44
import { CallbackManagerForToolRun } from "@langchain/core/callbacks/manager";
55
import { BaseLanguageModel } from "@langchain/core/language_models/base";
@@ -26,9 +26,8 @@ const createEvent = async (
2626
eventDescription = "",
2727
}: CreateEventParams,
2828
calendarId: string,
29-
auth: JWT
29+
calendar: calendar_v3.Calendar
3030
) => {
31-
const calendar = google.calendar("v3");
3231
const event = {
3332
summary: eventSummary,
3433
location: eventLocation,
@@ -45,7 +44,6 @@ const createEvent = async (
4544

4645
try {
4746
const createdEvent = await calendar.events.insert({
48-
auth,
4947
calendarId,
5048
requestBody: event,
5149
});
@@ -60,13 +58,13 @@ const createEvent = async (
6058

6159
type RunCreateEventParams = {
6260
calendarId: string;
63-
auth: JWT;
61+
calendar: calendar_v3.Calendar;
6462
model: BaseLanguageModel;
6563
};
6664

6765
const runCreateEvent = async (
6866
query: string,
69-
{ calendarId, auth, model }: RunCreateEventParams,
67+
{ calendarId, calendar, model }: RunCreateEventParams,
7068
runManager?: CallbackManagerForToolRun
7169
) => {
7270
const prompt = new PromptTemplate({
@@ -109,7 +107,7 @@ const runCreateEvent = async (
109107
eventDescription,
110108
} as CreateEventParams,
111109
calendarId,
112-
auth
110+
calendar
113111
);
114112

115113
if (!(event as { error: string }).error) {

libs/langchain-community/src/tools/google_calendar/commands/run-view-events.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { calendar_v3 } from "googleapis";
2-
import type { JWT } from "googleapis-common";
32
import { PromptTemplate } from "@langchain/core/prompts";
43
import { BaseLanguageModel } from "@langchain/core/language_models/base";
54
import { CallbackManagerForToolRun } from "@langchain/core/callbacks/manager";
@@ -10,17 +9,15 @@ import { getTimezoneOffsetInHours } from "../utils/get-timezone-offset-in-hours.
109

1110
type RunViewEventParams = {
1211
calendarId: string;
13-
auth: JWT;
12+
calendar: calendar_v3.Calendar;
1413
model: BaseLanguageModel;
1514
};
1615

1716
const runViewEvents = async (
1817
query: string,
19-
{ model, auth, calendarId }: RunViewEventParams,
18+
{ model, calendar, calendarId }: RunViewEventParams,
2019
runManager?: CallbackManagerForToolRun
2120
) => {
22-
const calendar = new calendar_v3.Calendar({});
23-
2421
const prompt = new PromptTemplate({
2522
template: VIEW_EVENTS_PROMPT,
2623
inputVariables: ["date", "query", "u_timezone", "dayName"],
@@ -45,7 +42,6 @@ const runViewEvents = async (
4542

4643
try {
4744
const response = await calendar.events.list({
48-
auth,
4945
calendarId,
5046
...loaded,
5147
});

libs/langchain-community/src/tools/google_calendar/create.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ export class GoogleCalendarCreateTool extends GoogleCalendarBase {
3535
}
3636

3737
async _call(query: string, runManager?: CallbackManagerForToolRun) {
38-
const auth = await this.getAuth();
38+
const calendar = await this.getCalendarClient();
3939
const model = this.getModel();
4040

4141
return runCreateEvent(
4242
query,
4343
{
44-
auth,
44+
calendar,
4545
model,
4646
calendarId: this.calendarId,
4747
},

libs/langchain-community/src/tools/google_calendar/prompts/create-event-prompt.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export const CREATE_EVENT_PROMPT = `
22
Date format: YYYY-MM-DDThh:mm:ss+00:00
33
Based on this event description: "Joey birthday tomorrow at 7 pm",
4-
output a json of the following parameters:
4+
output a JSON string of the following parameters. Do not include any other text or comments:
55
Today's datetime on UTC time 2023-05-02T10:00:00+00:00, it's Tuesday and timezone
66
of the user is -5, take into account the timezone of the user and today's date.
77
1. event_summary
@@ -10,7 +10,7 @@ of the user is -5, take into account the timezone of the user and today's date.
1010
4. event_location
1111
5. event_description
1212
6. user_timezone
13-
event_summary:
13+
output:
1414
{{
1515
"event_summary": "Joey birthday",
1616
"event_start_time": "2023-05-03T19:00:00-05:00",
@@ -22,7 +22,7 @@ event_summary:
2222
2323
Date format: YYYY-MM-DDThh:mm:ss+00:00
2424
Based on this event description: "Create a meeting for 5 pm on Saturday with Joey",
25-
output a json of the following parameters:
25+
output a JSON string of the following parameters. Do not include any other text or comments:
2626
Today's datetime on UTC time 2023-05-04T10:00:00+00:00, it's Thursday and timezone
2727
of the user is -5, take into account the timezone of the user and today's date.
2828
1. event_summary
@@ -31,7 +31,7 @@ of the user is -5, take into account the timezone of the user and today's date.
3131
4. event_location
3232
5. event_description
3333
6. user_timezone
34-
event_summary:
34+
output:
3535
{{
3636
"event_summary": "Meeting with Joey",
3737
"event_start_time": "2023-05-06T17:00:00-05:00",
@@ -42,8 +42,8 @@ event_summary:
4242
}}
4343
4444
Date format: YYYY-MM-DDThh:mm:ss+00:00
45-
Based on this event description: "{query}", output a json of the
46-
following parameters:
45+
Based on this event description: "{query}", output a JSON string of the
46+
following parameters. Do not include any other text or comments:
4747
Today's datetime on UTC time {date}, it's {dayName} and timezone of the user {u_timezone},
4848
take into account the timezone of the user and today's date.
4949
1. event_summary
@@ -52,5 +52,5 @@ take into account the timezone of the user and today's date.
5252
4. event_location
5353
5. event_description
5454
6. user_timezone
55-
event_summary:
55+
output:
5656
`;
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export const VIEW_EVENTS_PROMPT = `
22
Date format: YYYY-MM-DDThh:mm:ss+00:00
33
Based on this event description: 'View my events on Thursday',
4-
output a json of the following parameters:
4+
output a JSON string of the following parameters. Do not include any other text or comments:
55
Today's datetime on UTC time 2023-05-02T10:00:00+00:00, it's Tuesday and timezone
66
of the user is -5, take into account the timezone of the user and today's date.
77
If the user is searching for events with a specific title, person or location, put it into the search_query parameter.
@@ -10,7 +10,7 @@ If the user is searching for events with a specific title, person or location, p
1010
3. user_timezone
1111
4. max_results
1212
5. search_query
13-
event_summary:
13+
output:
1414
{{
1515
"time_min": "2023-05-04T00:00:00-05:00",
1616
"time_max": "2023-05-04T23:59:59-05:00",
@@ -20,8 +20,7 @@ event_summary:
2020
}}
2121
2222
Date format: YYYY-MM-DDThh:mm:ss+00:00
23-
Based on this event description: '{query}', output a json of the
24-
following parameters:
23+
Based on this event description: '{query}', output a JSON string of the following parameters. Do not include any other text or comments:
2524
Today's datetime on UTC time {date}, today it's {dayName} and timezone of the user {u_timezone},
2625
take into account the timezone of the user and today's date.
2726
If the user is searching for events with a specific title, person or location, put it into the search_query parameter.
@@ -30,5 +29,5 @@ If the user is searching for events with a specific title, person or location, p
3029
3. user_timezone
3130
4. max_results
3231
5. search_query
33-
event_summary:
32+
output:
3433
`;

libs/langchain-community/src/tools/google_calendar/view.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ export class GoogleCalendarViewTool extends GoogleCalendarBase {
3434
}
3535

3636
async _call(query: string, runManager?: CallbackManagerForToolRun) {
37-
const auth = await this.getAuth();
37+
const calendar = await this.getCalendarClient();
3838
const model = this.getModel();
3939

4040
return runViewEvents(
4141
query,
4242
{
43-
auth,
43+
calendar,
4444
model,
4545
calendarId: this.calendarId,
4646
},

0 commit comments

Comments
 (0)