Как стать автором
Обновить

Даём нейро-сотруднику на ChatGPT управлять календарем Google через Telegram бот

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров7.2K
Фото сгенерировано в OpenAI по запросу: "сгенери фото для статьи "ChatGPT управляет вашим календарем Google через персональный бот в Telegram"
Фото сгенерировано в OpenAI по запросу: "сгенери фото для статьи "ChatGPT управляет вашим календарем Google через персональный бот в Telegram"

Куда только уже этот ChatGPT не прикрутили с момента его появления. Возможно я изобрету велосипед, но мне показалось удобным сделать бота в Телеграм, который бы поддерживал голосовой ввод и управлял моим календарем в Google.

Итак, что мы будем делать?

  1. Создадим нейро-сотрудника на моей платформе

  2. Зарегистрируем Телеграм-бот и подключим его к нашему сотруднику

  3. Напишем API для интеграция с Google календарем

  4. Дадим функцию нашему сотруднику для работы с API

Шаг №1: Создаем нейро-сотрудника

На скриншоте начало квиза по созданию нейро-сотрудника
На скриншоте начало квиза по созданию нейро-сотрудника

Дабы не нарушать правила хабра я не буду писать название платформы по созданию нейро-сотрудников и дам только сам системный промпт на базе которого будет работать наш бот:

Цель:
Помогать пользователю вести его Google календарь.
Роль:йр
Ты - женщина.
Тебя зовут - Жанна
Ты работаешь в должности - Личный секретарь
Твоя задача помогать управлять календарем Google используя функцию "google_calendar".
Поведение:

Для твоей работы необходим ID календаря, который ты будешь передавать в функцию "google_calendar". ID календаря может быть предоставлен и в виде Email адреса. Без него ты не можешь отвечать ни на какие вопросы пользователя.
При показе запланированных событий после названия события указывай его "event_id".
Перед удалением событий запроси согласие пользователя на удаление событий и после подтверждения запусти "google_calendar" на удаление событий по интервалу дат или по списку "event_id".
Не используй в ответах никакие эмоджи, кроме: "✅" и "🗓️".

Возможно пока не очень ясны некоторые инструкции в промпте, но позже вы все поймете, а пока важно отметить что промпт очень небольшой и по сути всем полностью будет управлять интеллект ChatGPT.

Шаг №2: Регистрируем Telegram-bot и прикручиваем его к нашему нейро-сотруднику

Тут все очень просто:

  1. Зайдите в вашем Телеграме в бот @BotFather

  2. Выберите в меню бота /newbot и следуйте указаниям

  3. После регистрации бота скопируйте токен бота, он нужен для интеграции с нашим нейро-сотрудником

На скриншоте блок по настройке бота в Телеграм для работы нашего нейро-сотрудника
На скриншоте блок по настройке бота в Телеграм для работы нашего нейро-сотрудника
Так же вы можете сделать бота условно бесплатным и ограничить количество бесплатных токенов
Так же вы можете сделать бота условно бесплатным и ограничить количество бесплатных токенов

Шаг №3: Напишем API для интеграция с Google календарем

Для получения необходимого файла с ключами 'credentials.json' доступа прочитайте статью: «Настройка синхронизации google calendar с web приложением».

Вот класс, описывающий интеграцию с Google Calendar API:

from __future__ import print_function
import datetime
import googleapiclient
from google.oauth2 import service_account
from googleapiclient.discovery import build
import uuid
import json
import codecs
class GoogleCalendar(object):
SCOPES = ['<https://www.googleapis.com/auth/calendar>']
	CREDS_FILE = 'credentials.json'

def __init__(self, calendarId):
    credentials = service_account.Credentials.from_service_account_file(CREDS_FILE, scopes=GoogleCalendar.SCOPES)
    self.service = googleapiclient.discovery.build('calendar', 'v3', credentials=credentials)
    self.calendarId = calendarId


def create_event(self, event):
    e = self.service.events().insert(calendarId=self.calendarId,
                                     body=event).execute()
    print('Event created: %s' % (e.get('id')))
    return e


def get_events_list(self, start_date=None, end_date=None):
    
    now = datetime.datetime.utcnow().isoformat() + 'Z'
    if start_date is None and end_date is None:
        events_result = self.service.events().list(calendarId=self.calendarId,
                                                   timeMin=now,
                                                   maxResults=100, singleEvents=True,
                                                   orderBy='startTime').execute()
    else:
        events_result = self.service.events().list(calendarId=self.calendarId,
                                                   timeMin=start_date,
                                                   timeMax=end_date,
                                                   singleEvents=True).execute()
    events = events_result.get('items', [])
    if not events:
        print('No upcoming events found.')
        return json.dumps('No upcoming events found.')
    events_list = self.encode_events_list(events)
    return events_list

def encode_events_list(self, events):
    events_list = []
    for event in events:
        start = event['start'].get('dateTime', event['start'].get('date'))
        end = event['end'].get('endTime', event['end'].get('date'))
        events_list.append({
            'event_id' : event['id'],
            'event_summary' : event['summary'],
            'event_start' : start,
            'event_end' : end,
        })
    return events_list
    

def update_event(self, event_id, updated_event):
    current_event = self.service.events().get(calendarId=self.calendarId,
                                              eventId=event_id).execute()
    updated_title = updated_event.get('summary', current_event['summary'])
    updated_start = updated_event.get('start', current_event['start'])
    updated_end = updated_event.get('end', current_event['end'])
    updated_event = {
        'summary': updated_title,
        'start': updated_start,
        'end': updated_end
    }
    event = self.service.events().update(calendarId=self.calendarId,
                                         eventId=event_id,
                                         body=updated_event).execute()
    print('Event updated: %s' % event.get('id'))
    return json.dumps('Event updated.')


def delete_event(self, event_id):
    res = self.service.events().delete(calendarId=self.calendarId,
                                 eventId=event_id).execute()
    print('Event deleted: %s' % event_id)
    return json.dumps('Event deleted.')

def delete_events_in_range(self, start_date, end_date):
    events_result = self.service.events().list(calendarId=self.calendarId,
                                               timeMin=start_date,
                                               timeMax=end_date,
                                               singleEvents=True).execute()
    events = events_result.get('items', [])
    for event in events:
        self.delete_event(event['id'])
    events_list = self.encode_events_list(events)
    return json.dumps('Events deleted.')

А вот сам код API на базе Flask:

from flask import Flask, Response, render_template, redirect, url_for, request, session, jsonify
from flask_cors import CORS
from flask import send_from_directory

import google.calendar # !!! это наш класс, описанный в блоке выше

app = Flask(__name__)
app.static_folder = 'static'
CORS(app)

def GetJsonFromRequest(request):
    data = request.get_json(force=True)
    if type(data) is not dict:
        data = request.get_json()
        data = json.loads(data)
    return data

@app.route('/api/v1.0/google_calendar', methods=['POST'])
def google_calendar():
    data = GetJsonFromRequest(request)
    try:
        calendar = google.calendar.GoogleCalendar(data['calendarId'])
        if data['action'] == '+':
            res = calendar.create_event(data['event'])
        if data['action'] == '?':
            res = calendar.get_events_list(data['start_date'], data['end_date'])
        if data['action'] == '-':
            if data['start_date'] is None:
                res = calendar.delete_event(data['event_id'])
            else:
                res = calendar.delete_events_in_range(data['start_date'], data['end_date'])
        if data['action'] == '.':
            res = calendar.update_event(data['event_id'], data['event'])
        print('[END - google_calendar res]', res)
        return res
    except Exception as e:
        print('[END - google_calendar ERROR]', str(e))
        return 'Error'

if __name__ == "__main__":
    app.run(debug=False, host='0.0.0.0', port=5000)

Шаг №4: Напишем функцию для связи ChatGPT и нашего Google Calendar API

Для вызова функций в ChatGPT нам нужно дать описание всех переменных функции и сам код функции.

Вот описание параметров нашей функции:

{
    "name": "google_calendar",
    "description": "Управление календарем Google",
    "parameters": {
        "type": "object",
        "properties": {
            "calendarId": {
                "type": "string",
                "description": "ID календаря",
            },
            "action": {
                "type": "string",
                "enum": ["+", "?", '-', '.'],
                "description": "Тип действия. `+` - означает добавление события. `?` - означает получение всех событий календаря.. `-` - удалить событие в календаре. `.` - редактировать событие в календаре.",
            },
            "event_ids": {
                "type": "string",
                "description": "Список ID событый в календаре в формате json: ```[{\\"event_id\\": \\"3lsvlor3hjlgpgmv7er7e5juov\\"}]```. Передается при типах действия `-` или '.'",
            },
            "events": {
                "type": "string",
                "description": "Список событий в формате json: ```[{\\"summary\\": \\"test event\\", \\"description\\": \\"some info\\", \\"start\\": {\\"dateTime\\": \\"2024-09-04T03:00:00+03:00\\"}, \\"end\\": {\\"dateTime\\": \\"2024-09-04T05:30:00+03:00\\"}}]```. Передается при типах действия `+` или '.'",
            },
            "start_date": {
                "type": "string",
                "description": "Дата начала периода в формате `2024-09-04T03:00:00+03:00`. Передается при типах действия `?` или '-'",
            },
            "end_date": {
                "type": "string",
                "description": "Дата окончания периода в формате `2024-09-04T03:00:00+03:00`. Передается при типах действия `?` или '-'",
            },
        },
        "required": ["calendarId", "action"],
    },
},

А вот сам код функции:

def google_calendar(arguments): 
    import requests
    import json
data = {
    "sa_data" : None,
    "calendarId" : arguments['calendarId'],
    "action" : arguments['action'],
    'start_date' : None,
    'end_date' : None,
}
if 'start_date' in arguments.keys():
    data['start_date'] = arguments['start_date']
if 'end_date' in arguments.keys():
    data['end_date'] = arguments['end_date']

if arguments['action'] == '?':
    response = requests.post('https://___АДРЕС_АПИ_СЕРВИСА___/api/v1.0/google_calendar', data=json.dumps(data))
    return response.text
else:
    responses = []
    if arguments['action'] == '+' or arguments['action'] == '.':
        if 'events' in arguments.keys():
            events = json.loads(arguments['events'])
            for i in range(len(events)):
                e = events[i]
                data['event'] = e
                if 'event_ids' in arguments.keys():
                    event_ids = json.loads(arguments['event_ids'])
                    data['event_id'] = event_ids[i]
                response = requests.post('https://___АДРЕС_АПИ_СЕРВИСА___/api/v1.0/google_calendar', data=json.dumps(data))
                responses.append(response.text)
    if arguments['action'] == '-':
        if 'event_ids' in arguments.keys():
            event_ids = json.loads(arguments['event_ids'])
            for i in range(len(event_ids)):
                data['event_id'] = event_ids[i]['event_id']
                response = requests.post('https://___АДРЕС_АПИ_СЕРВИСА___/api/v1.0/google_calendar', data=json.dumps(data))
                responses.append(response.text)
        if 'start_date' in arguments.keys():
            response = requests.post('https://___АДРЕС_АПИ_СЕРВИСА___/api/v1.0/google_calendar', data=json.dumps(data))
            responses.append(response.text)
    return json.dumps(str(responses))

Тестирование бота

Поскольку в промпте у нашего нейро-сотрудника есть такая инструкция:

Поведение:
1. Для твоей работы необходим ID календаря, который ты будешь передавать в функцию "google_calendar". ID календаря может быть предоставлен и в виде Email адреса. Без него ты не можешь отвечать ни на какие вопросы пользователя.

При старте общения с нашим ботом мы видим следующий диалог:

На скрине начало диалога с нейро-сотрудником, управляющим Google календарем
На скрине начало диалога с нейро-сотрудником, управляющим Google календарем

Если мы укажем наш ID календаря, то бот ответит примерно так:

После получения ID Google календаря наш нейро-сотрудник готов им управлять
После получения ID Google календаря наш нейро-сотрудник готов им управлять

Давайте для примера запланируем отпуск в Рио в нашем боте. Для начала спросим:

Просим нейро-сотрудника проверить события за заданный период
Просим нейро-сотрудника проверить события за заданный период

Поскольку у нас ничего пока нет в календаре, то попросим спланировать посещение достопримечательностей в Рио на этот период:

Просим нашего бота с помощью ChatGPT придумать интересные для посещения места в Рио и сформировать наш план посещения их во время отпуска
Просим нашего бота с помощью ChatGPT придумать интересные для посещения места в Рио и сформировать наш план посещения их во время отпуска

Посмотрим теперь в наш календарь Google:

Проверяем как события опубликовались в Google календаре
Проверяем как события опубликовались в Google календаре

Отлично! Получилось! Давайте немного посложнее попросим бота:

Просим бота удалить одно из событий
Просим бота удалить одно из событий

Как мы видим бот спросил подтверждение при удалении события из календаря, это было в его инструкции:

Поведение:
...
3. Перед удалением событий запроси согласие пользователя на удаление событий и после подтверждения запусти "google_calendar" на удаление событий по интервалу дат или по списку "event_id".
...

Проверяем календарь и видим, что событие было удалено:

Видно, что нужное событие было удалено
Видно, что нужное событие было удалено

Думаю идею вы уже уловили. Бота можно просить корректировать название событий, переносить события на нужную дату и время и т.д.

Да, и самое важное - боту можно отправлять голосовые и он так же будет помогать вам вести Google календарь.

Наш получившийся бот/сотрудник понимает команды голосом и даже озвучивает свой ответ
Наш получившийся бот/сотрудник понимает команды голосом и даже озвучивает свой ответ

Итоги

В целом мозгов у ChatGPT 4 вполне хватает на управление календарем.

Что можно еще попробовать?

  1. Разбиение сложных задач на подзадачи

  2. Помощь с распределением задач

  3. Ежедневное уведомление о важных делах на сегодня/неделю/месяц

  4. Просить бота фокусировать внимание пользователя на важных событиях и говорить об этом при постановке задач

  5. Дать боту Яндекс.Календарь и предлагать в начале выбрать с каким календарем работать

Сам бот можно протестировать тут.

Идеи и вопросы пишите мне в телеграм.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Каким облачным календарем Вы пользуетесь?
70.21% Google33
6.38% Яндекс3
10.64% Microsoft Outlook5
12.77% Другой6
Проголосовали 47 пользователей. Воздержались 6 пользователей.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 9: ↑8 и ↓1+7
Комментарии14

Публикации

Истории

Работа

Data Scientist
78 вакансий
Python разработчик
121 вакансия

Ближайшие события

Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
OTUS CONF: GameDev
Дата30 мая
Время19:00 – 20:30
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область