Jak dodać rezerwację spotkania do chatbota?

Dzisiejszy wpis będzie w formie poradnika. Dość częstym zastosowaniem chatbotów jest odciążenie obsługi klienta lub wyeliminowanie konieczności odpowiadania na te same zagadnienia w kółko przez agentów.

Jednym z takich zagadnień jest umówienie spotkania. Możemy sobie łatwo wyobrazić chatbota, którego jednym z zadań jest umawianie wizyty u fryzjera, kosmetyczki, rezerwacja stolika w restauracji na konkretną godzinę lub po prostu zaaranżowanie spotkania np. z agentem ubezpieczeniowym.

To dość powszechne zastosowanie chatbotów, dlatego chciałem Wam pokazać jak możecie taką funkcjonalność zaimplementować wykorzystują do tego:

  • Dialogflow
  • Google Calendar

Kroki, jakie należy wykonać będę starał się wytłumaczyć najlepiej jak mogę, ale warto w tym miejscu zaznaczyć, że wpis ten jest przeznaczony głównie dla tych z Was, którzy mają już podstawową wiedzę o tworzeniu chatbotów w Dialogflow.

Cały wyeksportowany agent zostanie umieszczony na moim repozytorium, do którego link znajdziecie na dole artykułu – będziecie mogli go zaimportować i na jego bazie tworzyć własnego.

Zatem do dzieła!

Scenariusz

To co dzisiaj zrobimy to pokrycie ścieżki umówienia spotkania oraz zapisanie tego spotkanie w Google Calendar. Przy okazji chatbot sprawdzi, czy wybrany termin jest dostępny i zwróci odpowiedź, czy udało się umówić spotkanie, czy należy wybrać inny termin.

Zaznaczam od razu, że to co pokażę dziś pokrywa najprostszą ścieżkę – nie będziemy walidować wprowadzonej godziny, nie będziemy sprawdzać czy podana godzina nie wykracza poza godziny otwarcia i nie będziemy też sprawdzać innych tego typu przypadków. Tylko prosty scenariusz integracji Dialogflow i Google Calendar w odpowiedzi na intent.

Oczywiście jeśli będziecie zainteresowani dalszą rozbudową tego przypadku i obsługę powyższych przypadków i zachowań, będziemy to robić w kolejnych wpisach.

W skrócie będzie to wyglądało tak:

  1. Użytkownik wpisuje frazę w stylu: Chciałbym umówić spotkanie 10 kwietnia o 19
  2. Agent rozpoznaje intent i sprawdza, czy ma wolny grafik w zaproponowanym terminie
  3. Jeśli tak – umawia spotkanie, zapisując je w kalendarzu i daje informację zwrotną
  4. Jeśli nie – poprosi o podanie innego terminu

Agent

Zacząć musimy od stworzenia nowego agenta. Agent to odpowiednik naszego chatbota, w rozumieniu Dialogflow. Każdy agent składa się natomiast m.in. z kolekcji intent, o których powiem więcej w następnej sekcji.

Wchodzimy więc do DIalogflow i tworzymy nowego agenta w języku polskim.

Jak dodać rezerwację spotkania do chatbota?
Tworzenie nowego agenta

Intent

Aby chatbot był w stanie odpowiedzieć na wiadomość od użytkownika musi mieć zdefiniowane tzw. intencje (intent).

Intent to obiekt, który definiuje intencję użytkownika na podstawie wpisanej frazy i potrafi zareagować na nią w określony sposób – np. generując odpowiedź, wysyłając zdjęcie czy zapisując coś do bazy danych.

Wszystkie intencje są dostępne w zakładce Intents. Każdy nowy agent ma stworzone 2 domyślne intencje:

  • Default Fallback Intent – uruchamiana jest wtedy, gdy agent nie rozpoznał wprowadzonej frazy
  • Default Welcome Intent – uruchamian jest wtedy, gdy użytkownik rozpoczyna rozmowę z agentem lub wpisze frazę „powitalną”, np. cześć, hej itp.

Aby nasz chatbot był w stanie umówić spotkanie potrzebujemy stworzyć intent, który będzie obsługiwał tę akcję. W tym celu przechodzimy do zakładki Intents i tworzymy intent, klikając w przycisk Create intent. Zostaniemy przeniesieni do ekranu konfiguracji intencji:

Jak dodać rezerwację spotkania do chatbota?
Tworzenie nowej intencji

W pierwszej kolejności nadajmy nazwę, np. appointment-scheduling i zapiszmy intent, aby nam nie zniknął, klikając Save.

Teraz musimy nauczyć agenta, jakie frazy wprowadzone przez użytkownika będą kierować do stworzonej właśnie intencji. W tym celu powinniśmy uzupełnić frazy treningowe (Training phrases) zwrotami, których spodziewamy się od użytkownika.

Ja dodałem następujące:

  • chcę się umówić w piatek na 13
  • chce umowic spotkanie 10.04.2020 o godzinie 15
  • umów spotkanie jutro na 19
  • chce umowic spotkanie
  • umów spotkanie
  • spotkanie w piatek

Wy możecie dodawać swoje, zmieniać je czy modyfikować.

Dobór fraz treningowych uzależniony jest od odbiorców chatbota – inne będą wiadomości użytkowników nastoletnich, a inne klientów przychodzi.

Jak dodać rezerwację spotkania do chatbota?

Istotną rzeczą podczas definiowania fraz treningowych jest określenie parametrów (parameters).

 Parametry to fragmenty wyrażenia, które możemy wyodrębnić z wypowiedzi użytkownika, a które pełnią określoną rolę. Parametrem może być godzina, data, email, imię czy nazwisko. Każdy z takich parametrów, który wymieniłem jest określonego typu i posiada określoną nazwę. Parametry możemy wykorzystywać następnie do przetwarzania czy generowania dynamicznych odpowiedzi. Uff. 

W naszym przypadku przewidziałem 2 parametry – data i czas, czyli informacje, które są mi potrzebne, aby móc zapisać spotkanie w kalendarzu.

W jaki sposób definiujemy parametry?

Częściowo Dialogflow robi to za nas na etapie wpisywania frazy, widzimy po wpisaniu sugerowane parametry, zaznaczonego kolorem. Możemy je tak zostawić lub edytować czy dodawać nowe – wg. naszej wiedzy. Aby to zrobić należy zaznaczyć myszką odpowiedni wyraz i wybrać z menu rozwijalnego typ naszego parametru.

W przykładzie, który wrzuciłem na screenie powyżej data jest zaznaczona kolorem pomarańczowym, a godzina różowym.

Aby przejrzeć wszystkie zdefiniowane parametry i skonfigurować je dodatkowo rozwijamy zakładkę Action and parameters.

Zgodnie z tym co miałem pozaznaczane na frazach treningowych widzimy tutaj 2 parametry:

  • date, który jest typu @sys.date
  • time, który jest typu @sys.time

W naszym przypadku bardzo istotne jest, aby oba parametry były wypełnione. W związku z tym należy zaznaczyć flagi IsRequired przy obu parametrach oraz zdefiniować pytania uzupełniające, które zostaną zadane, gdy parametr nie będzie podany przez użytkownika ( Prompts ).

Dzięki takiej konfiguracji parametrów oraz fraz treningowych agenta zachowa się następująco:

  • gdy użytkownik wpisze chcę się umówić w piątek na 13 , wtedy agent ustawi jako parametr date najbliższy piątek (datę), a jako time godzinę 13.
  • gdy użytkownik wpisze chcę się umówić agent dopyta o datę i godzinę pytaniami, które ustawiliśmy (w moim przypadku W jaki dzień? i O której godzinie?)

Zapisujemy intent i tę część mamy za sobą.

Fulfillment

Teraz przejdziemy do najważniejszej części zadania – do Fulfillment’u.

Co to jest? W klasycznym scenariuszu na wybrany intent, możemy dodać odpowiedź, która zostania wygenerowana w odpowiedzi na wiadomość użytkownika. Ograniczenie się agenta tylko do takich statycznych odpowiedzi, ograniczałoby jednocześnie możliwości chatbota.

A co jeśli chcemy wygenerować dynamiczną odpowiedź w zależności od parametrów? Albo chcemy zapisać jakieś dane do naszej bazy danych?

Wtedy z pomocą przychodzi nam Fulfillment. Funkcjonalność ta pozwala przekierować obsługę intent’u do zewnętrznego serwisu, który możliwości ma już nieograniczone – może integrować się z bazą danych, z zewnętrznymi serwerami, generować dynamiczne wiadomości, uzupełniać parametry itp. itd.

Kilka słów wprowadzenie o tej funkcjonalności znajdziecie też w innym moim artykule: TU.

W jaki sposób w naszym bocie wykorzystam Fulfillment?

Po wprowadzeniu przez użytkownika daty i godziny, serwis sprawdzi czy wybrany termin jest wolny w kalendarzu i jeśli tak, doda zdarzenie do niego, a użytkownikowi zwróci wiadomość, że spotkanie zostało umówione. W przeciwnym wypadku poinformuje użytkownika, że wybrany termin jest już zajęty i powinien wybrać inny.

Fulfillment – włączenie

Aby włączyć Fulfillment dla naszej intencji przechodzimy do jej konfiguracji ( appointment-scheduling ) i rozwijamy sekcję Fulfillment na dole strony.

Następnie należy kliknąć Enable fulfillment.

Jak dodać rezerwację spotkania do chatbota?

Po uruchomieniu funkcjonalności zaznaczamy kontrolkę Enable webhook call for this intent na true i zapisujemy intent. Od tego momentu, gdy agent rozpozna naszą intencję zapisze parametry wiadomości, a następnie obsługę przekieruje do zewnętrznego serwisu, którym za chwilę się zajmiemy.

Jak dodać rezerwację spotkania do chatbota?

Teraz przechodzimy do zakładki Fulfillment, którą znajdziemy z lewej strony i uruchamiamy Inline Editor. Aby uruchomić konsolę będziecie musieli stworzyć konto w Google Cloud Platform, jeśli go jeszcze nie macie. Alternatywnym sposobem na Fulfillment jest implementacja własnego serwisu, wtedy konta na platformie Google zakładać nie musicie, ale to wymaga utrzymywanie serwera po waszej stronie.

Edytor, który właśnie włączyliśmy, pozwala bezpośrednio w przeglądarce tworzyć kod, który będzie uruchamiany w odpowiedzi na znaleziony intent. Rozwiązanie to jest dobre to prostych i krótkich zastosowań, ale nie zalecałbym go używać do bardziej skomplikowanych rozwiązań, ze względu na to, że jest dość toporny w używaniu.

Jesteśmy już gotowi do napisania kodu, który zapisze naszego użytkownika na spotkanie w wybranym terminie. Ale zanim to zrobimy – jeszcze jedna rzecz – włączenie API kalendarza Google, po to abyśmy mogli integrować naszego chatbota z naszym kalendarzem.

Włączenie API kalendarza Google

Aby włączyć API, umożliwiające komunikację z kalendarzem musimy przejść do konsoli Google Cloud Platform , a następnie wybrać z górnego menu projekt naszego chatbota.

Jak dodać rezerwację spotkania do chatbota?
Włączenie API kalendarza Google

Gdy jesteśmy już w kontekście naszego projektu przechodzimy do wyszukiwania bibliotek: APIs & Services -> Library, wyszukujemy Google Calendar API i włączamy je dla naszego projektu (Enabled).

Następnie musimy pobrać klucz, który pozwoli nam porozumiewać się z usługą. W tym celu przechodzimy do zakładki z uprawnieniami: APIs & Services -> Credentials i tworzymy nowy Service account na końcu pobierając klucz w postaci pliku json.

Jak dodać rezerwację spotkania do chatbota?
Tworzenie Service Account

Klucz, który zapisze nam się na dysku będzie użyty na późniejszym etapie więc nie kasujcie go. Przyda Wam się na pewno.

Kolejnym krokiem będzie dodanie kalendarza oraz nadanie do niego uprawnień naszemu projektowi. W tym celu przechodzicie do Google Calendar i tworzycie nowy kalendarz, który służyć będzie umawianiu spotkań. (Add other calendar).

Po utworzeniu kalendarza przechodzimy do jego ustawień (Settings and sharing) i skrolujemy do sekcji Share with specific people.

Nadawanie uprawnień do kalendarza

Klikamy w Add people i wklejamy tam emaila, którego znajdziecie w pliku z kluczem, który wcześniej wygenerowaliśmy w polu client_name.

Pamiętajcie o ustawieniu odpowiednich uprawnień dla dodawanego emaila. Minimalne uprawnienia to: MAKE CHANGES TO EVENTS.

Gdy wszystko pójdzie ok, kalendarz powinien być już dostępny dla naszego projektu. Pozostaje zakodować naszą funkcjonalność. Do dzieła!

Fulfillment – implementacja

Przechodzimy teraz do zakładki, na której byliśmy już w momencie, gdy włączaliśmy tę opcję. Następnie wklejcie gotowy kod do edytora:

'use strict';

// Import the Dialogflow module from Google client libraries.
const functions = require('firebase-functions');
const {google} = require('googleapis');
const {WebhookClient} = require('dialogflow-fulfillment');

// Enter your calendar ID below and service account JSON below
const calendarId = "{TU WKLEJ ID KALENDARZA}";
const serviceAccount = "{TU WKLEJ SWÓJ KLUCZ}"
; // Starts with {"type": "service_account",...

// Set up Google Calendar Service account credentials
const serviceAccountAuth = new google.auth.JWT({
 email: serviceAccount.client_email,
 key: serviceAccount.private_key,
 scopes: 'https://www.googleapis.com/auth/calendar'
});

const calendar = google.calendar('v3');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements

const timeZone = 'Europe/Warsaw';
// Set the DialogflowApp object to handle the HTTPS POST request.
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
 const agent = new WebhookClient({ request, response });

 function getStartDate(agent){
  const dateTimeStart = new Date(agent.parameters.date.split('T')[0] + 'T' + agent.parameters.time.split('T')[1].split('-')[0]);
  return dateTimeStart;
 }

 function getEndDate(dateTimeStart){
  return new Date(new Date(dateTimeStart).setHours(dateTimeStart.getHours() + 1));
 }

 function makeAppointment (agent) {

   const dateTimeStart = getStartDate(agent);
   const dateTimeEnd = getEndDate(dateTimeStart);

   const appointmentTimeString = dateTimeStart.toLocaleString('pl-PL');

   return createCalendarEvent(dateTimeStart, dateTimeEnd).then(() => {
     agent.add(`Ok, sprawdziłem czy termin jest dostępny. ${appointmentTimeString} jest wolny, Umówiłem Cię na spotkanie.`);
   }).catch(function(e) {
     agent.add(`Przykro mi, nie mamy wolnego terminu w tym czasie: ${appointmentTimeString}.`);
   });
 }


 let intentMap = new Map();
 intentMap.set('appointment-scheduling', makeAppointment);
 agent.handleRequest(intentMap);
});

function createCalendarEvent (dateTimeStart, dateTimeEnd) {
 return new Promise((resolve, reject) => {
   calendar.events.list({
     auth: serviceAccountAuth, 
     calendarId: calendarId,
     timeMin: dateTimeStart.toISOString(),
     timeMax: dateTimeEnd.toISOString()
   }, (err, calendarResponse) => {
     if (err || calendarResponse.data.items.length > 0) {
       reject(err || new Error('Requested time conflicts with another appointment'));
     } else {
       calendar.events.insert({ auth: serviceAccountAuth,
         calendarId: calendarId,
         resource: {summary: 'Spotkanie',
           start: {dateTime: dateTimeStart},
           end: {dateTime: dateTimeEnd}}
       }, (err, event) => {
         if(err){reject(err);
                }
         else{
          resolve(event); 
         }
       }
       );
     }
   });
 });
}

Teraz trochę wyjaśnienia:

  • linia 9: wklej tu
  • linia 10: należy tu wkleić klucz, który ściągnęliśmy w postaci pliku JSON (należy tam wkleić jego zawartość)
  • linia 23: strefa czasowa agenta
  • linia 37: metoda getAppointment odpalana w odpowiedzi na intencję. W pierwszej kolejności z parametrów pobiera proponowany termin spotkania, jako zakończenie spotkania ustawia czas o godzinę późniejszy i sprawdza czy jest taki slot wolny w kalendarzu – jeśli tak – zapisuje, jeśli nie, zwraca informację do użytkownika.

Tym prostym sposobem stworzyliśmy agenta, który dodaje spotkanie do kalendarza, jeśli tylko termin jest wolny. Dowód?

Testowanie agenta
Testowanie agenta

Podsumowanie

Dzisiaj pokazałem Wam nieco bardziej zaawansowany scenariusz, który można pokryć w Dialogflow, czyli umówienia spotkania oraz integracja chatbota z kalendarzem Google. Chatbot sprawdza czy podany termin jest wolny i w jeśli tak jest – dodaje wydarzenie do kalendarza. Użyliśmy tu 2 ważnych elementów – fulfillment i API kalendarza, które pozwoliły sprawić, że chatbot wykonuje czynność, która może być w łatwy sposób zautomatyzowana.

Lab, którym się inspirowałem znajdziecie TUTAJ.

Kod do agenta, którego stworzyłem, a którego możecie zaimportować jest TUTAJ.