Mamy już naszego bota. Mamy też wytrenowany (w mniejszym lub większym stopniu) model w Wit.ai. Przyszedł więc czas na połączenie tych dwóch bytów we wspólnie działającą całość.
Wit.ai ma wystawione na świat zwykłe API. Znajduje się ono pod adresem: api.wit.ai.
Aby się do niego dostać skorzystamy ze zwykłego HttpClient-a. Zanim to zrobimy musimy pobrać token dla naszej aplikacji. Zrobimy to w ustawieniach naszej aplikacji Wit.ai.
Konfiguracja HttpClient-a
Po stworzeniu instancji httpClient-a:
var httpClient = new HttpClient()
musimy dodać ten token jako header requestu:
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer [token aplikacji Wit.ai]");
Zapytanie w URI
Uri który wysyłamy jako zapytanie ma postać: https://api.wit.ai/message?v=[date]&q=[message]
, gdzie [date]
to data w formacie yyyyMMdd, natomiast [message]
to treść wiadomości która skierował użytkownik do naszego bota.
Data
Na początek zajmijmy się datą:
var date = DateTime.Now.ToString("yyyyMMdd");
Konwertujemy aktualną datę na string w formacie yyyyMMdd
(4 cyfry roku, 2 miesiąca i 2 na dzień).
Wiadomość
Teraz czas na dodanie wiadomości. Jak już pewnie wiesz z poprzedniej części, wiadomość wyłuskujemy za pomocą activity.Text
. Natomiast dodając ją do URI musimy zadbać o to, żeby wszystkie spacje czy inne znaki specjalne zostały przekonwertowane na odpowiedni format. Użyję do tego metody UrlEncode
z klasy HttpUtility
. Odpowiednie formatowanie wiadomości w kodzie będzie więc wyglądać tak:
var message = HttpUtility.UrlEncode(activity.Text);
Sklejanie całości w URI
Aby skleić powyższe dane w odpowiedni URI posłużę się interpolacją w stringu:
var url = $"https://api.wit.ai/message?v={date}&q={message}";
Teraz wystarczy pobrać odpowiedź na nasze zapytanie:
var response = httpClient.GetStringAsync(new Uri(url)).Result;
Odpowiedź
Jeżeli wszystko poszło zgodnie z planem, otrzymamy odpowiedź w formacie JSON. Jednak żeby skutecznie z nim pracować zdałoby się serializować tą odpowiedź na klasy. W tym celu potrzebny będzie nam JSON który otrzymujemy jako odpowiedź. Możemy pobrać go za pomocą debuggera (kopiując wartość zmiennej response
) lub za pomocą np. Postmana. Gdy będziemy mieli już odpowiedź serwera w schowku, tworzymy nową klasę w naszej aplikacji. Następnie kasujemy linijki:
public class NazwaKlasy { }
Teraz wykorzystamy bardzo pomocną funkcję Visual Studio 2017. W tym celu otwieramy menu Edit
-> Paste Special
-> Paste JSON as classes
. Struktura JSONa zostanie zachowana w klasach. Jakie to wygodne 🙂
Sprawdzanie poprawności
Sposób jaki wymyśliłem na sprawdzenie czy request został zidentyfikowany to sprawdzenie czy znajdują się w nim jakiekolwiek entities. Niestety nie wystarczy sprawdzić, czy propertis public Entities entities
jest nullem. Trzeba sprawdzić czy każdy z propertisów klasy Entities
jest nullem. Jest to dosyć uciążliwe, ale skuteczne.
Intencja (zamiar) użytkownika
Jak pisałem w poprzednim wpisie, w Wit.ai nie ma już dedykowanego pola Intent. Teraz zamiarem jest nazwa Entity. Żeby dobrze określić zamiar użytkownika należy sprawdzać, które entity są podane, a które nie.
Dla przykładu. Mój bot ma za zadanie:
- znaleźć drogę z jakiegoś miejsca do innego miejsca
- znaleźć drogę z lokalizacji użytkownika do wskazanego w zapytaniu miejsca
- pokazać miejsce na mapie
W wytrenowanym modelu Wit.ai posiadam 3 entities: from, to, location
. W pierwszym przypadku wylistowanym wyżej Wit.ai zwraca mi enities from
i to
.
W drugim przypadku zwracana wartość jest tylko w entity to
.
Natomiast w przypadku pokazania miejsca na mapie, w odpowiedzi dostaje entity location
.
Obsługiwanie intencji
Obsługą intencji w powyższym przypadku zajmuje się kod:
if (json.entities.from == null && json.entities.to == null) { return ReturnLocation(json); } if (json.entities.from != null && json.entities.to != null) { return RouteFromTo(json); } if (json.entities.from == null && json.entities.to != null) { return RouteTo(json); } return "Nie rozumiem. Możesz inaczej sformułować pytanie?";
W powyższym kodzie rozważane są przypadki:
- jeżeli from i to są nullami to znaczy, że użytkownik chce znać położenie jakiegoś miejsca;
- jeżeli from i to NIE są nullami to znaczy, że użytkownik chce dostać wskazówki dojścia z miejsca
from
do miejscato
- jeżeli
from
jest nullem ato
NIE jest nullem podaj wskazówki dojścia z aktualnego miejsca do wskazanej przez użytkownika lokalizacji.
Jeżeli żaden z powyższych warunków nie jest spełniony, to znaczy, że nie ma przewidzianej dla niego akcji.
Trochę nie podoba mi się taka ifologia, ale nie znalazłem innego – bardziej eleganckiego – rozwiązania.
Moim zdaniem dużo lepsze jest, gdy serwis NLU1 jawnie zwraca zamiar (intent) użytkownika. Dlatego jeżeli nie chcesz koniecznie korzystać z języka polskiego, zalecam skorzystanie np. z LUIS.ai. Jest on dużo przyjemniejszy niż Wit.ai. Ja skorzystałem w tym cyklu z tego drugiego, gdyż chciałem sprawdzić jego możliwości w rozpoznawaniu języka Polskiego.
Natural Language Understanding↩