📜 ⬆️ ⬇️

As we built the constructor of Telegram bots in 24 hours, and then threw half of them and rewrote

It all started on June 20, when I saw this tweet on Twitter of a popular blogger Varlamov:

image
At the same time, I thought: after all, the messenger in general, and Telegram in particular, is an ideal way to interact with the client. Why do we need an application to deliver the latest news if it can just send them to the chat?

Why do you need an application for ordering a taxi, when you can chat with your favorite taxi operator “I want a taxi to Domodedovo from metro station Yuzhnoye in 35 minutes” - and the taxi has been ordered. Why do you need an application for ordering from a cafe, when you can write to the chat “I want a double espresso and a bagel with a sturgeon” - and it remains only to send your address. Such examples of the use of chat can be a huge variety.
')
On the same day, I wrote a small post in the TJournal club news community, where I offered to write a product and create a demo bot within the upcoming hackathon AngelHack : to subscribe to news and notifications from this community. Four days later, Pavel Durov officially launched support for bots, and two weeks later we won the AngelHack nomination from IBM with the Leecero project. Under the cut a big story ...



Prologue: What did we do about this?


Leecero is a product that makes it easy to create a Telegram bot using a script presented as a state machine, support for natural language and third-party REST APIs. In a nutshell: designer bots.

Here is an example of a simple bot script that sends currency rates on demand:



Leecero is a web application built on the basis of ASP.NET MVC Web API, MSSQL and deployed in Azure. Initially, we planned to build it, and built it, based on the Azure Storage Queue and WebJobs. In this we are terribly wrong, why - I will tell later.

As part of the hackathon, we not only developed a product, but also several demo bots: a currency bot, a news bot, a bot that demonstrates food orders from cafes, and a bot that demonstrates customer support for the bank.


All the bots were written in different YAPs: we had a python, server-side JS, C # - communication with the server takes place through the REST API, and thanks to IBM Bluemix it was not a problem to get rid of them. At their core, “bots” are simply a set of endpoints that are called at the right moment from Leecero. The "bot" itself, its script is on the server.

One bot was without any code at all, only the script: this is a bot simulating banking support:



Act 1: Oh, what plans


It is necessary to prepare for hackathons. At least with the technologies used there should be no problems. Of course, in the framework of the hackathon, you can try to learn something fundamentally new, but you shouldn’t get better - it will be a shame to lose in the fight against the crookedly connected library. At that moment I was working with ASP.NET WebAPI, as well as Azure Storage - I decided to use them to create Leecero. I designed it this way:



The idea was that if the load on the service grows, some messages, for example from a large client, are allocated to a separate webjob and are processed by an independent queue - such obvious load balancing, as we thought. In addition, webjobs are free.

At first it was necessary to write or find a ready client Telegram Bot Api. Fortunately, last year I participated in the Pavel Durov contest for the creation of a Telegram on Android. I used Xamarin and C #, so I have some things left. It was quite easy to turn them into the client library for Telegram Bot Api. I decided to open its code under GPLv3: TelegramBot.Client

WebJob is either a console application that runs on an Azure web app server on a schedule, or a function that can be triggered by an event from Azure Storage. We were interested in the second option.

public static async Task ProcessMessage([QueueTrigger("telegram-messages")] TelegramMessage update, TextWriter logger, CancellationToken token) { if (update.Update.Message.Text == null) return; var client = new TelegramBotApiClient(update.Token); var message = update.Update.Message; await client.SendChatAction(message.Chat.Id, "typing"); await ProcessTelegramMessage(update, client, recognizeResult, logger); } 


[QueueTrigger("telegram-messages")] is a key attribute. Azure takes over most of the work, and when new messages appear in the telegram-messages queue, the ProcessMessage function will be called and executed. Our task in the future only put the message in the queue.

Messages from Telegram bot can be received either through a server poll, or using a webhook. A webhook requires a link with https , i.e. with a secure certificate. Moreover, self-signed certificates are not suitable, we need a certificate from the certification authority. Fortunately, * .azurewebsites.com is protected by a certificate, so a webhook is created in a couple of minutes:

  // POST: api/Message/token [Route(@"api/Message/{tokenf}/{tokens}")] public async Task<HttpResponseMessage> Post(string tokenf, string tokens, [FromBody]Update value) { var token = string.Concat(tokenf, ":", tokens); System.Diagnostics.Debug.WriteLine("Message from {0}: {1}", token, value.Message.Text); // Create a message and add it to the queue. var tm = new TelegramMessage { Token = token, Update = value }; var message = new CloudQueueMessage(JsonConvert.SerializeObject(tm)); await _queue.AddMessageAsync(message); return new HttpResponseMessage(HttpStatusCode.OK); } 

I will not go into the details of ASP.NET WebApi, I can only say that it is very powerful, flexible and convenient, but at the same time quite confusing - since it is almost entirely based on internal naming conventions, call sequences, etc. Without knowing them, you can think for a long time that it does not work for you. However, most of them can be redefined. In this case, we created a POST method that accepts a url of the form leecero.com/api/Message/{tokenf}/{tokens} with the body Update, serialized as JSON. Or XML. What kind of message and how to deserialize it will determine ASP.NET itself based on the headers. And the line await _queue.AddMessageAsync(message); put our message in the queue for processing.

As you can see, building a work product with Azure has proven relatively simple. I even took the time to wander around the small part of Mail.ru that is accessible to us and pofotkat, as well as sleep an hour.



The fun itself began in the morning when we finished writing bots and began actively preparing for the demonstration. After lengthy testing, we found out that the bots have been thinking for quite a long time - then we hung all the time with stopwatches trying to find a bottleneck.

 2015-07-20T18:48:20 PID[3016] Verbose ChatStateController.NextState - GetCurrentState: 00:00:00.9852497 2015-07-20T18:48:22 PID[3016] Verbose ChatStateController.NextState - DbQuery: 00:00:01.2952876 2015-07-20T18:48:22 PID[3016] Verbose CurrentState: Pretty, input: /start, class: Unknown 2015-07-20T18:48:22 PID[3016] Verbose CurrentState after Pretty, action 2015-07-20T18:48:22 PID[3016] Verbose ChatStateController.NextState - Save state: 00:00:00.3723358 2015-07-20T18:48:22 PID[3016] Verbose Process text message: 00:00:02.7199401 2015-07-20T18:48:22 PID[3016] Verbose Processing 111111:TOKEN/C_ID with message /start 2015-07-20T18:48:22 PID[3016] Verbose Action:   , Term: 2015-07-20T18:48:22 PID[3016] Verbose Send simple message without action: 00:00:00.1108038 2015-07-20T18:48:22 PID[3016] Verbose Sucessfully sent message    from bot to C_ID 

At first we thought that Telegram continues to be under DDOS and therefore everything slows down. Then we thought that the reason for accessing the database (where we store the script) or to Azure Table Storage (where the key-value storage is the current chat state) - however, thanks to caching, all requests after the first were executed in a split second. In the end, we decided to abandon the attempts to speed up the service, as a new problem arose: the web job, which was launched in the continuous mode, fell asleep after some time of inactivity and it took minutes to wake up! Imagine what would happen if he fell asleep during a speech.



In general, I had to customize the service, spamming bots all the time while the performance was going on - so that they would not relax.

Intermedia - NLP, it’s a natural language processing


I am writing above about natural language support. And it really is in Leecero - thanks to the publicly available NLP-as-Service. In Russia, I personally consider Yandex to be the largest specialist in natural languages. For example, you can recall the long-standing project Automatic Text Processing available via the LGPL. Today, as I understand it, the authors work in Yandex. But for a hackathon, connecting to an ASP.NET COM library application is too difficult.



Therefore, my choice fell on the service Yandex SpeechKit Cloud. Many have heard that he is able to recognize the voice, but how many people know that he has a module for the selection of semantic objects from the text? The module is able to identify the date and time from the entered phrase (for example, “tomorrow”), first name, last name and address - which means that Leecero can do it too! In addition, it has a built-in stemmer and a morphological analyzer that highlights parts of a sentence: verbs, nouns, etc. One problem - for such tasks is expensive, $ 5 per 1000 requests. Therefore, a call to SpeechKit Cloud is the second stage of processing a phrase, first it is used by a stemmer from the guys from Stfordford, since it is available via GPLv2.

However, the English language remained not covered! Today SpeechKit Cloud supports Russian, Ukrainian, Turkish. Therefore, for the analysis of English phrases, we connected AlchemyApi from IBM. AlchemyApi is a company that has been doing natural language processing with machine learning since 2009. She lived quietly, and in 2015 was bought by IBM, integrated into Watson and became part of the IBM-IBM Bluemix solution. And what is most beautiful is available along with other Watson services through a public API


AlchemyApi has great capabilities. Of course, 24 hours on the hackathon is not enough to study them all. Here is a small example that we managed to implement - the selection of the order of key entities from the phrase: the actual products that must be ordered.



As you can see, the service parses the offer, extracts keywords from it and gives them a list in the form of xml. This is enough to find products in the database for these keywords and place an order. However, I noticed that AlchemyApi works better on large texts than on short phrases. But, as I understand it, these are all questions of learning - questions that are clearly beyond the scope of this long-running article.

Using AlchemyApi in the program is very simple - they have a beta version of C # api v 0.10, in fact there is the usual REST.
Use trivial
 // Create an AlchemyAPI object. AlchemyAPI.AlchemyAPI alchemyObj = new AlchemyAPI.AlchemyAPI(); // Load an API key alchemyObj.SetAPIKey("KEY"); string order = "I want to order one Margarita Pizza, two small colas and Ham Pizza"; var xml = alchemyObj.TextGetRankedNamedEntities(order); System.Diagnostics.Debug.WriteLine(xml); var xml2 = alchemyObj.TextGetRankedKeywords(order); System.Diagnostics.Debug.WriteLine(xml2); 

The output keywords from the phrase
 <?xml version="1.0" encoding="UTF-8"?> <results> <status>OK</status> <usage>By accessing AlchemyAPI or using information generated by AlchemyAPI, you are agreeing to be bound by the AlchemyAPI Terms of Use: http://www.alchemyapi.com/company/terms.html</usage> <totalTransactions>1</totalTransactions> <language>english</language> <keywords> <keyword> <relevance>0.926847</relevance> <text>Margarita Pizza</text> </keyword> <keyword> <relevance>0.820088</relevance> <text>Ham Pizza</text> </keyword> <keyword> <relevance>0.642818</relevance> <text>small colas</text> </keyword> </keywords> </results> 


Act 2: One day after


Having rejoiced at the victory and having slept off a bit, I looked into the code that we wrote. First, I immediately found the cause of the bots' brakes - it turns out that WebJob works with the queue using the polling method with a minimum interval of 2 seconds. Those. less than the interval can not be put.

The interval is set when creating a job:

  static void Main(string[] args) { // HACK:  CS   JobHostConfiguration configuration = new JobHostConfiguration("cs"); configuration.Queues.MaxPollingInterval = TimeSpan.FromSeconds(2); var host = new JobHost(configuration); host.RunAndBlock(); } 

The second problem (falling asleep WebJob) was solved by installing a daw in the web service settings.



Maybe. I did not check. When I realized this, I threw out everything related to WebJob and Azure Queue and rewrote everything in good old TPL Dataflow. And I immediately added a DI-container and fixed half the services. In general, my personal hackathon lasted one more night.

TPL.Dataflow is a library designed to implement a Data Flow design pattern or processing pipeline.

In a nutshell, the library allows you to build a pipeline consisting of blocks for storing or processing data, linking them according to some condition. Thus, I build a pipeline, where the user’s input is sent to the input, and the output is answered according to a scenario, which can include inside of me, including and calls to third-party web api. Dataflow gives me simple, possibly parallel execution of any blocks, filtering and queue management. And unlike the Azure Queue, everything is in memory and very fast. As I understood, Azure queues are only suitable for converting images.

I started with a very simple conveyor:

  // setup TPL dataflow _messageBuffer = new BufferBlock<TelegramMessage>(); var messageProcessor = new ActionBlock<TelegramMessage>(async message => { try { await _processor.ProcessMessage(message); } catch (Exception ex) { Container.GetInstance<ILogger>().Log(ex.ToString()); } }); _messageBuffer.LinkTo(messageProcessor); 


The message is put there directly from WebHook, just like it was with the Azure queue. It showed itself quite well, but I want to expand it by adding several stages of processing the message, including: language classification, morphological analysis, calling third-party API functions, the answer itself. Now I am designing this scheme, and at the same time I am adding support for media files and English morphology.

Epilogue: So what was the result?


I am sure that many people will find thousands of uses for Telegram bots and bots. Already today, Aviasales made a bot to search for air tickets, Meduza made a bot that is looking for news with the / cat tag, and blogger Ilya Varlamov sends notifications about all his posts directly to Telegram.

Source: https://habr.com/ru/post/262967/


All Articles