Nasz pierwszy ChatBot

Nasz pierwszy ChatBot

Boty nie są nowym rozwiązaniem. Istnieją na rynku od wielu lat. Obecnie, ich znaczenie i wykorzystanie w komunikacji z klientami istotnie rośnie, między innymi za sprawą coraz większej popularności komunikatorów internetowych. Dzięki połączeniu ChatBota z używanymi na co dzień kanałami komunikacji typu Facebook Messenger, możemy zaproponować nasze usługi za pośrednictwem interfejsu, który użytkownicy już znają i który nie odbiega od sposobu komunikacji “międzyludzkiej”. Te zalety “interfejsów konwersacyjnych” powodują, że coraz więcej firm zaczyna oferować swoje produkty poprzez wirtualnych asystentów. Taki trend widać wyraźnie na przykład w branży bankowej.

Jakie obecnie dostępne technologie wykorzystywane są do budowy botów? Jakie mają możliwości, jaki jest próg wejścia do rozpoczęcia korzystania z tego typu narzędzi, łatwość użycia czy produktywność. Postanowiliśmy przyjrzeć się bliżej temu tematowi.

W tym celu rozpoczęliśmy eksperyment, polegający na stworzeniu ChatBota, który posłuży jako dodatkowy kanał komunikacji dla zbudowanego przez nas wcześniej uproszczonego systemu do sprzedaży ubezpieczeń. Jego zadaniem będzie przeprowadzenie z użytkownikiem konwersacji prowadzącej do sprzedaży ubezpieczenia. W trakcie rozmowy bot powinien pozyskać od klienta informacje niezbędne do przygotowania oferty i zawarcia umowy, a następnie przekazać je do systemu sprzedażowego w celu uzyskania wyceny i założenia polisy.

Linia dialogowa, którą chcemy zaimplementować wygląda następująco:

ChatBot

Logika ChatBota

Jako bazę postanowiliśmy wykorzystać bibliotekę Bot Builder SDK (v3). Pozwala ona na stworzenie logiki bota w dwóch językach: C# (.NET) oraz JavaScript (NodeJS). Na początek wybraliśmy drugą opcję.

Przed rozpoczęciem programowania bota musimy zdefiniować biblioteki, z których będziemy korzystać:

  • botbuilder – framework do tworzenia botów
  • restify – do utworzenia interfejsu REST dla naszego bota
  • request – do komunikacji z API systemu ubezpieczeniowego
  • dotenv-extended – biblioteka pomocnicza pozwalająca na definiowanie zmiennych środowiskowych w pliku tekstowym (.env). Będziemy z niej korzystali tylko na maszynie lokalnej. Docelowo zmienne te będą wstrzykiwane przez środowisko uruchomieniowe Azure.
package.json:
...
  "dependencies": {
    "botbuilder": "^3.15.0",
    "dotenv-extended": "^2.0.0",
    "request": "^2.88.0",
    "restify": "^7.2.1"
  },
...

Pracę rozpoczniemy od stworzenia kilku funkcji pomocniczych do komunikacji z API systemu sprzedażowego – w celu określenia pobrania parametrów produktu ubezpieczeniowego, wyceny oferty ubezpieczenia, zakupu polisy i pobrania dokumentu potwierdzającego:

backend.js:
[...]
function getPrice(params, auth) {
    return new Promise(function (resolve, reject) {
        request({
                method: 'POST',
                uri: `http://${backendHost}:8081/api/offers`,
                headers: {
                    'content-type': 'application/json',
                    'Authorization': 'Bearer ' + auth.accessToken
                },
                body:
                    JSON.stringify(params)

            },
            function (error, response, body) {
                if (error) {
                    reject(error);
                } else {
                    console.log(body);
                    resolve(JSON.parse(body));
                }
            }
        );
    });
}
[...]

Aby zainicjować bota musimy zdefiniować connector oraz określić miejsce przechowywania stanu konwersacji. Dla uproszczenia posłużymy się magazynem w pamięci operacyjnej.

bot.js
[...]
const connector = new builder.ChatConnector({
    appId: process.env.MicrosoftAppId,
    appPassword: process.env.MicrosoftAppPassword
});
const inMemoryStorage = new builder.MemoryBotStorage();
const bot = module.exports = new builder.UniversalBot(connector, getStartingSteps()).set('storage', inMemoryStorage);
[...]

Możemy teraz przejść do sedna, czyli programowania możliwości dialogowych bota. Punktem wejścia jest funkcja getStartingSteps wskazana przy inicjalizacji. Zwraca ona tablicę zawierającą dwie funkcje. Pierwsza z nich zawiera definicję powitania oraz pytania kierowanego przez bota do użytkownika. Druga funkcja określa reakcję na otrzymaną odpowiedź.

bot.js:
[...]
function getStartingSteps() {
    return  [
        function (session) {
            session.send('Hello in ASC LAB Insurance Agent Bot!');
            builder.Prompts.choice(
                session,
                'What do you want? (currently you can only buy insurance)',
                [IntentType.BUY_POLICY],
                {
                    maxRetries: 3,
                    retryPrompt: 'Not a valid option'
                });
        },
        function (session, result) {
[...]
            const selection = result.response.entity;
            switch (selection) {
                case IntentType.BUY_POLICY:
                    return session.beginDialog('policy-sell');
            }
        }
    ];
}
[...]

Kluczowym elementem drugiej funkcji jest linia zawierająca wywołanie:

return session.beginDialog('policy-sell');

Powoduje ona rozpoczęcie nowego dialogu ‘policy-sell’ zdefiniowanego za pomocą wywołania:

bot.dialog('policy-sell', getPolicySellSteps());

oraz funkcji getCreatePolicySteps zwracającej tablicę funkcji określających kolejne kroki dialogu – analogicznie jak w poprzednim przykładzie.

Przesyłane przez użytkownika odpowiedzi możemy również zapisać w sesji:

function getCreatePolicySteps() {
    return [
        function (session) {
            session.send('OK, lets sign papers!');
            builder.Prompts.text(session, 'What is your first name?');
        },
        function (session, results, next) {
            session.userData.firstName = results.response;
            next();
        },
[...]

Dzięki temu możemy do nich sięgnąć w dalszej części konwersacji, aby przykładowo na ich podstawie skonstruować i wycenić ofertę:

function _addCalculatePriceSteps(product, steps) {
    steps.push(function (session) {
        session.send("Calculating price. Please wait...");

        const params = {
            "productCode": product.code,
            "policyFrom": session.dialogData.policyStart,
            "policyTo": session.dialogData.policyEnd,
            "selectedCovers": product.covers.map(c => c.code),
            "answers": session.dialogData.answers
        };

        console.log(params);
        backend.calculatePrice(params).then(function (offer) {
            session.send("Your insurance will cost %s EUR. Offer ID: %s", offer.totalPrice, offer.offerNumber);
            session.conversationData.offer = offer;
            builder.Prompts.choice(
                session,
                'Are you interested?',
                ['Yes', 'No']
            );
        });

SDK posiada również funkcjonalność pozwalającą na przesyłanie plików do użytkownika. Za jej pomocą, na koniec rozmowy bot może wysłać klientowi certyfikat potwierdzający zawarcie polisy w postaci pliku PDF:

backend.createPolicy(params).then(function (policy) {
        console.log(policy);
        session.send('Your policy has been created: %s', policy.policyNumber);
        waitForDocumentCreation()
            .then(function () {
                return backend.getPolicyAttachments(policy.policyNumber)
            })
            .then(function (response) {
                console.log("processing attachments ");
                if (response != null && response.documents != null) {
                    console.log(response.documents);
                    response.documents.forEach(function (doc) {
                        sendInline(session, {
                                content: doc.content,
                                contentType: 'application/pdf',
                                name: 'policy.pdf'
                            }
                        );
                    });
                }
                session.endDialog();
            });
    });


[...]

function sendInline(session, document) {
    const msg = new builder.Message(session)
        .addAttachment({
            contentUrl: util.format('data:%s;base64,%s', document.contentType, document.content),
            contentType: document.contentType,
            name: document.name
        });

    session.send(msg);
}

Na koniec musimy jeszcze utworzyć interfejs HTTP, za pomocą którego będzie można komunikować się z naszym botem:

app.js:
const restify = require('restify');
const bot = require('./bot.js');

const server = restify.createServer();
server.post('/api/messages', bot.connector('*').listen());
server.listen(process.env.PORT || 3978, () => {
        console.log(`${server.name} listening to ${server.url}`);
});

Pełny kod bota można znaleźć w repozytorium.

W celu uruchomienia bota lokalnie trzeba utworzyć plik .env i zdefiniować w nim zmienną BACKEND_HOST (zawierającą adres IP hosta na którym uruchomiony jest API Gateway naszego uproszczonego systemu do sprzedaży ubezpieczeń) a następnie wykonać polecenie:

node app.js

Działanie naszego bota możemy przetestować lokalnie za pomocą stworzonego przez Microsoft Emulatora.

 

Instalacja na Azure

Aby uruchomić naszego Bota na Azure wystarczy kilka prostych kroków.

W pierwszym kroku tworzymy instancję bota:

ChatBot

W tym celu musimy określić kilka parametrów. Najważniejsze z nich to:

  • nazwa bota – w naszym przypadku: asc-insurance-bot
  • lokalizacja – np. West Europe (ze względu na bliskość geograficzną)
  • warstwa cenowa – wariant F0 zawiera pakiet darmowych komunikatów
  • szablon bota: wybieramy NodeJS Basic, ponieważ kod bota będziemy chcieli tworzyć w języku JavaScript

ChatBot

Po kilku (2-3) minutach bot zostanie utworzony:

ChatBot

W drugim kroku dodajemy adres API Gateway’a systemu ubezpieczeniowego w zmiennej środowiskowej. W tym celu otwieramy ekran konfiguracji usługi web (trzecia od góry na ekranie listy zasobów):

ChatBot

Przechodzimy na zakładkę “Ustawienia aplikacji” i dodajemy zmienną BACKEND_HOST zawierającą adres IP hosta, na którym uruchomiony jest API Gateway systemu ubezpieczeniowego, a następnie wciskamy przycisk “Zapisz”:

ChatBot

Ostatnim krokiem jest skonfigurowanie ciągłego wdrażania (continuous deployment). Zaczynamy od otwarcia  ekranu do konfiguracji usługi bota (druga od góry na ekranie listy zasobów):

ChatBot

Przechodzimy na zakładkę “Kompiluj” i wybieramy opcję “Skonfiguruj ciągłe wdrażanie”:

ChatBot

Naciskamy przycisk “Instalacja” -> “Skonfiguruj wymagane ustawienia” i podajemy dane dostępowe do repozytorium kodu:

ChatBot

Po zatwierdzeniu ustawień za pomocą przycisku “OK” zostanie automatycznie uruchomiony proces instalacji, który powinien zakończyć się w ciągu kilkudziesięciu sekund:

ChatBot

Na koniec możemy sprawdzić poprawność wdrożenia. Zamykamy okno konfiguracji ciągłego wdrażania i otwieramy zakładkę “Testuj w funkcji rozmowy w sieci Web”. Z tego miejsca możemy przeprowadzić próbną konwersację z naszym botem:

ChatBot

Próbowaliśmy również wykonać instalację bota na Azure z poziomu linii poleceń. Niestety obecna wersja CLI (azure-cli: 2.0.45, botservice extension: 0.4.0) nie pozwala na poprawne wykonanie opisanych powyżej operacji.

 

Integracja z zewnętrznymi kanałami komunikacji

WebChat

Najprostszym do skonfigurowania zewnętrznym kanałem komunikacji jest czat. Aby go uruchomić otwieramy zakładkę kanały:

ChatBot

a następnie w pozycji “Web Chat” wybieramy akcję “Edytuj”:

ChatBot

Wyświetlony kod osadzania (wraz z kluczem tajnym), możemy umieścić na dowolnej stronie www. Na potrzeby demonstracji posłużymy się tu pierwszym z brzegu edytorem HTML online:

ChatBot

 

Slack

Szczegółowy opis sposobu integracji bota ze Slackiem znajduje się w dokumentacji Microsoftu.

Po wykonaniu kilku kroków opisanych w powyższej instrukcji możemy porozmawiać z naszym botem za pośrednictwem slacka:

ChatBot

 

Facebook Messenger

W podobny sposób możemy zintegrować naszego bota z Facebook Messengerem.

 

Podsumowanie

Obserwacje, jakie poczyniliśmy w trakcie niniejszego eksperymentu:

  • Wykorzystana przez nas biblioteka Bot Builder SDK (v3) posiada intuicyjne API, które pozwoliło nam łatwo i szybko rozpocząć pracę jak i doprowadzić do uruchomienia prostego bota.
  • Portal Azure pozwolił nam błyskawicznie uruchomić naszego bota w chmurze. Niestety nie udało nam się uzyskać tego samego rezultatu za pomocą obecnej wersji CLI.
  • Integracja bota ze Slackiem jest stosunkowo prosta, jednak wydaje się nie działać do końca stabilnie. Zdarzały nam się przypadki problemów takich jak brak dostarczenia wiadomości czy zerwanie konwersacji.

Mamy świadomość, że testowany przez nas framework jest technologią stosunkowo nową i liczymy, że niedociągnięcia, na które natrafiliśmy, zostaną niedługo usunięte.

Następnym krokiem w rozwoju naszego bota będzie próba rozszerzenia jego funkcji dialogowych o zdolność rozumienia języka naturalnego, ale o tym – w kolejnym artykule!

Autor: Robert Kuśmierek, Lead Software Engineer, ASC LAB