Skip to content

Google oauth support for Google calender tool #7788

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

Merged
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
94 changes: 61 additions & 33 deletions libs/langchain-community/src/tools/google_calendar/base.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { google } from "googleapis";
import { google, calendar_v3 } from "googleapis";
import { Tool } from "@langchain/core/tools";
import { getEnvironmentVariable } from "@langchain/core/utils/env";
import { BaseLanguageModel } from "@langchain/core/language_models/base";
Expand All @@ -7,6 +7,9 @@ export interface GoogleCalendarAgentParams {
credentials?: {
clientEmail?: string;
privateKey?: string;
keyfile?: string;
subject?: string;
accessToken?: string | (() => Promise<string>);
calendarId?: string;
};
scopes?: string[];
Expand All @@ -19,22 +22,23 @@ export class GoogleCalendarBase extends Tool {
description =
"A tool to lookup Google Calendar events and create events in Google Calendar";

protected clientEmail: string;

protected privateKey: string;

protected calendarId: string;

protected scopes: string[];

protected llm: BaseLanguageModel;

protected params: GoogleCalendarAgentParams;

protected calendar?: calendar_v3.Calendar;

constructor(
fields: GoogleCalendarAgentParams = {
{ credentials, scopes, model }: GoogleCalendarAgentParams = {
credentials: {
clientEmail: getEnvironmentVariable("GOOGLE_CALENDAR_CLIENT_EMAIL"),
privateKey: getEnvironmentVariable("GOOGLE_CALENDAR_PRIVATE_KEY"),
calendarId: getEnvironmentVariable("GOOGLE_CALENDAR_CALENDAR_ID"),
keyfile: getEnvironmentVariable("GOOGLE_CALENDAR_KEYFILE"),
subject: getEnvironmentVariable("GOOGLE_CALENDAR_SUBJECT"),
calendarId:
getEnvironmentVariable("GOOGLE_CALENDAR_CALENDAR_ID") || "primary",
},
scopes: [
"https://www.googleapis.com/auth/calendar",
Expand All @@ -44,52 +48,76 @@ export class GoogleCalendarBase extends Tool {
) {
super(...arguments);

if (!fields.model) {
if (!model) {
throw new Error("Missing llm instance to interact with Google Calendar");
}

if (!fields.credentials) {
if (!credentials) {
throw new Error("Missing credentials to authenticate to Google Calendar");
}

if (!fields.credentials.clientEmail) {
throw new Error(
"Missing GOOGLE_CALENDAR_CLIENT_EMAIL to interact with Google Calendar"
);
if (!credentials.accessToken) {
if (!credentials.clientEmail) {
throw new Error(
"Missing GOOGLE_CALENDAR_CLIENT_EMAIL to interact with Google Calendar"
);
}

if (!credentials.privateKey && !credentials.keyfile) {
throw new Error(
"Missing GOOGLE_CALENDAR_PRIVATE_KEY or GOOGLE_CALENDAR_KEYFILE or accessToken to interact with Google Calendar"
);
}
}

if (!fields.credentials.privateKey) {
throw new Error(
"Missing GOOGLE_CALENDAR_PRIVATE_KEY to interact with Google Calendar"
);
}

if (!fields.credentials.calendarId) {
if (!credentials.calendarId) {
throw new Error(
"Missing GOOGLE_CALENDAR_CALENDAR_ID to interact with Google Calendar"
);
}

this.clientEmail = fields.credentials.clientEmail;
this.privateKey = fields.credentials.privateKey;
this.calendarId = fields.credentials.calendarId;
this.scopes = fields.scopes || [];
this.llm = fields.model;
this.params = { credentials, scopes };
this.calendarId = credentials.calendarId;
this.llm = model;
}

getModel() {
return this.llm;
}

async getAuth() {
async getCalendarClient() {
const { credentials, scopes } = this.params;

if (credentials?.accessToken) {
// always return a new instance so that we don't end up using expired access tokens
const auth = new google.auth.OAuth2();
const accessToken =
typeof credentials.accessToken === "function"
? await credentials.accessToken()
: credentials.accessToken;

auth.setCredentials({
// get fresh access token if a function is provided
access_token: accessToken,
});
return google.calendar({ version: "v3", auth });
}

// when not using access token its ok to use singleton instance
if (this.calendar) {
return this.calendar;
}

const auth = new google.auth.JWT(
this.clientEmail,
undefined,
this.privateKey,
this.scopes
credentials?.clientEmail,
credentials?.keyfile,
credentials?.privateKey,
scopes,
credentials?.subject
);

return auth;
this.calendar = google.calendar({ version: "v3", auth });
return this.calendar;
}

async _call(input: string) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { google, calendar_v3 } from "googleapis";
import type { JWT, GaxiosResponse } from "googleapis-common";
import { calendar_v3 } from "googleapis";
import type { GaxiosResponse } from "googleapis-common";
import { PromptTemplate } from "@langchain/core/prompts";
import { CallbackManagerForToolRun } from "@langchain/core/callbacks/manager";
import { BaseLanguageModel } from "@langchain/core/language_models/base";
Expand All @@ -26,9 +26,8 @@ const createEvent = async (
eventDescription = "",
}: CreateEventParams,
calendarId: string,
auth: JWT
calendar: calendar_v3.Calendar
) => {
const calendar = google.calendar("v3");
const event = {
summary: eventSummary,
location: eventLocation,
Expand All @@ -45,7 +44,6 @@ const createEvent = async (

try {
const createdEvent = await calendar.events.insert({
auth,
calendarId,
requestBody: event,
});
Expand All @@ -60,13 +58,13 @@ const createEvent = async (

type RunCreateEventParams = {
calendarId: string;
auth: JWT;
calendar: calendar_v3.Calendar;
model: BaseLanguageModel;
};

const runCreateEvent = async (
query: string,
{ calendarId, auth, model }: RunCreateEventParams,
{ calendarId, calendar, model }: RunCreateEventParams,
runManager?: CallbackManagerForToolRun
) => {
const prompt = new PromptTemplate({
Expand Down Expand Up @@ -109,7 +107,7 @@ const runCreateEvent = async (
eventDescription,
} as CreateEventParams,
calendarId,
auth
calendar
);

if (!(event as { error: string }).error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { calendar_v3 } from "googleapis";
import type { JWT } from "googleapis-common";
import { PromptTemplate } from "@langchain/core/prompts";
import { BaseLanguageModel } from "@langchain/core/language_models/base";
import { CallbackManagerForToolRun } from "@langchain/core/callbacks/manager";
Expand All @@ -10,17 +9,15 @@ import { getTimezoneOffsetInHours } from "../utils/get-timezone-offset-in-hours.

type RunViewEventParams = {
calendarId: string;
auth: JWT;
calendar: calendar_v3.Calendar;
model: BaseLanguageModel;
};

const runViewEvents = async (
query: string,
{ model, auth, calendarId }: RunViewEventParams,
{ model, calendar, calendarId }: RunViewEventParams,
runManager?: CallbackManagerForToolRun
) => {
const calendar = new calendar_v3.Calendar({});

const prompt = new PromptTemplate({
template: VIEW_EVENTS_PROMPT,
inputVariables: ["date", "query", "u_timezone", "dayName"],
Expand All @@ -45,7 +42,6 @@ const runViewEvents = async (

try {
const response = await calendar.events.list({
auth,
calendarId,
...loaded,
});
Expand Down
4 changes: 2 additions & 2 deletions libs/langchain-community/src/tools/google_calendar/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ export class GoogleCalendarCreateTool extends GoogleCalendarBase {
}

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

return runCreateEvent(
query,
{
auth,
calendar,
model,
calendarId: this.calendarId,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const CREATE_EVENT_PROMPT = `
Date format: YYYY-MM-DDThh:mm:ss+00:00
Based on this event description: "Joey birthday tomorrow at 7 pm",
output a json of the following parameters:
output a JSON string of the following parameters. Do not include any other text or comments:
Today's datetime on UTC time 2023-05-02T10:00:00+00:00, it's Tuesday and timezone
of the user is -5, take into account the timezone of the user and today's date.
1. event_summary
Expand All @@ -10,7 +10,7 @@ of the user is -5, take into account the timezone of the user and today's date.
4. event_location
5. event_description
6. user_timezone
event_summary:
output:
{{
"event_summary": "Joey birthday",
"event_start_time": "2023-05-03T19:00:00-05:00",
Expand All @@ -22,7 +22,7 @@ event_summary:

Date format: YYYY-MM-DDThh:mm:ss+00:00
Based on this event description: "Create a meeting for 5 pm on Saturday with Joey",
output a json of the following parameters:
output a JSON string of the following parameters. Do not include any other text or comments:
Today's datetime on UTC time 2023-05-04T10:00:00+00:00, it's Thursday and timezone
of the user is -5, take into account the timezone of the user and today's date.
1. event_summary
Expand All @@ -31,7 +31,7 @@ of the user is -5, take into account the timezone of the user and today's date.
4. event_location
5. event_description
6. user_timezone
event_summary:
output:
{{
"event_summary": "Meeting with Joey",
"event_start_time": "2023-05-06T17:00:00-05:00",
Expand All @@ -42,8 +42,8 @@ event_summary:
}}

Date format: YYYY-MM-DDThh:mm:ss+00:00
Based on this event description: "{query}", output a json of the
following parameters:
Based on this event description: "{query}", output a JSON string of the
following parameters. Do not include any other text or comments:
Today's datetime on UTC time {date}, it's {dayName} and timezone of the user {u_timezone},
take into account the timezone of the user and today's date.
1. event_summary
Expand All @@ -52,5 +52,5 @@ take into account the timezone of the user and today's date.
4. event_location
5. event_description
6. user_timezone
event_summary:
output:
`;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const VIEW_EVENTS_PROMPT = `
Date format: YYYY-MM-DDThh:mm:ss+00:00
Based on this event description: 'View my events on Thursday',
output a json of the following parameters:
output a JSON string of the following parameters. Do not include any other text or comments:
Today's datetime on UTC time 2023-05-02T10:00:00+00:00, it's Tuesday and timezone
of the user is -5, take into account the timezone of the user and today's date.
If the user is searching for events with a specific title, person or location, put it into the search_query parameter.
Expand All @@ -10,7 +10,7 @@ If the user is searching for events with a specific title, person or location, p
3. user_timezone
4. max_results
5. search_query
event_summary:
output:
{{
"time_min": "2023-05-04T00:00:00-05:00",
"time_max": "2023-05-04T23:59:59-05:00",
Expand All @@ -20,8 +20,7 @@ event_summary:
}}

Date format: YYYY-MM-DDThh:mm:ss+00:00
Based on this event description: '{query}', output a json of the
following parameters:
Based on this event description: '{query}', output a JSON string of the following parameters. Do not include any other text or comments:
Today's datetime on UTC time {date}, today it's {dayName} and timezone of the user {u_timezone},
take into account the timezone of the user and today's date.
If the user is searching for events with a specific title, person or location, put it into the search_query parameter.
Expand All @@ -30,5 +29,5 @@ If the user is searching for events with a specific title, person or location, p
3. user_timezone
4. max_results
5. search_query
event_summary:
output:
`;
4 changes: 2 additions & 2 deletions libs/langchain-community/src/tools/google_calendar/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ export class GoogleCalendarViewTool extends GoogleCalendarBase {
}

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

return runViewEvents(
query,
{
auth,
calendar,
model,
calendarId: this.calendarId,
},
Expand Down
Loading
Loading