Azure Bot

Azure Bot Services で日本語を英語に翻訳する Bot を作ってみた

11月15日(日本だと、11月16日になってたかも)に、Azure Bot Services がプレビューになりましたので、早速ためしてみました。

■Microsoft Azure Announces Industry’s First Cloud Bot-as-a-Service
https://azure.microsoft.com/en-us/blog/microsoft-azure-announces-industry-s-first-cloud-bot-as-a-service/

 

Azure でのドキュメントは、以下になります。
https://azure.microsoft.com/en-us/services/bot-service/

 

さすがに、プレビューしたばかりの機能ですのでドキュメントは英語って感じですが、
もともと、Bot Framework のドキュメントも英語だったかなーって思ってみたりもします。

ってことで、英語がつらい自分なので、まずは、Bot 先生に英語を教えてもらおうかなってことで、今回のBotを作ってみました。
Bot Services 以外には、Azure Cognitive Service の Translator Speech API を利用します。

Azure Cognitive Service の Translator Speech API については、以下にブログっているのでよかったら見てみてください。
Azure Cognitive Service を使って、日本語を英語に翻訳

まずは、成果物。
日本語を入力すると、英語に翻訳したのと、英語に翻訳したのをもう一度日本語に翻訳しなおした結果を応答します。

 

どんな感じで作ったか、ざっくりとした手順とソースを載せます。

 

Azure Bot Services の作成

Azure ポータルから、サクサク作成できます。
操作は、こんな感じです。
ちなみに、この辺のドキュメントはこれかな。
https://docs.botframework.com/en-us/azure-bots/build/first-bot/

 

日本にもBot Services は来ているみたいです。

 

Create Microsoft App ID and password をクリックします。

 

アプリIDとジェネレートしたパスワードを入力します。
パスワードのコピーは忘れずに~

取得した、Microsoft App ID とパスワードを入力します。

 

テンプレートは、今回はBasic を選択しました。LUIS用のテンプレートも用意されているっぽいです。

 

エミュレーターが右にあるので、すぐに動作確認ができます。
初期の状態では、Bot にチャットした言葉をそのままオウム返しするアプリです。

 

ソース

ソースはこんな感じ。EcoDialog.csx しかいじってないです。
入力された文字を ditect して、日本語だったら英語に翻訳、英語だったら日本語に翻訳とかしてもよかったかもと思いつつ、そんなことはしてないです。
翻訳周りは、ソースの使いまわし感が半端ないなw

#r "System.Web"

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

// For more information about this template visit http://aka.ms/azurebots-csharp-basic
[Serializable]
public class EchoDialog : IDialog<object>
{
    protected int count = 1;
    private const string SubscriptionKey = "{キー}";

    public Task StartAsync(IDialogContext context)
    {
        try
        {
            context.Wait(MessageReceivedAsync);
        }
        catch (OperationCanceledException error)
        {
            return Task.FromCanceled(error.CancellationToken);
        }
        catch (Exception error)
        {
            return Task.FromException(error);
        }

        return Task.CompletedTask;
    }

    public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
    {
        var message = await argument;
        if (message.Text == "reset")
        {
            PromptDialog.Confirm(
                context,
                AfterResetAsync,
                "Are you sure you want to reset the count?",
                "Didn't get that!",
                promptStyle: PromptStyle.Auto);
        }
        else
        {
           
            var authTokenSource = new AzureTextTransfer(SubscriptionKey);
            var token = string.Empty;
 
            try
            {
                token = await authTokenSource.GetAccessTokenAsync();
            }
            catch (HttpRequestException)
            {
                switch (authTokenSource.RequestStatusCode)
                {
                    case HttpStatusCode.Unauthorized:
                        Console.WriteLine("Request to token service is not authorized (401). Check that the Azure subscription key is valid.");
                        break;
                    case HttpStatusCode.Forbidden:
                        Console.WriteLine("Request to token service is not authorized (403). For accounts in the free-tier, check that the account quota is not exceeded.");
                        break;
                }
                throw;
            }
            
            var msgEnglish = authTokenSource.TextTransfer(token, message.Text,"ja","en");
            var msgRejapanese = authTokenSource.TextTransfer(token, msgEnglish,"en","ja");

            await context.PostAsync(msgEnglish);
            await context.PostAsync(msgRejapanese);
            
            context.Wait(MessageReceivedAsync);
        }
    }

    public async Task AfterResetAsync(IDialogContext context, IAwaitable<bool> argument)
    {
        var confirm = await argument;
        if (confirm)
        {
            this.count = 1;
            await context.PostAsync("Reset count.");
        }
        else
        {
            await context.PostAsync("Did not reset count.");
        }
        context.Wait(MessageReceivedAsync);
    }
}


    public class AzureTextTransfer
    {
        /// URL of the token service
        private static readonly Uri ServiceUrl = new Uri("https://api.cognitive.microsoft.com/sts/v1.0/issueToken");
        /// Name of header used to pass the subscription key to the token service
        private const string OcpApimSubscriptionKeyHeader = "Ocp-Apim-Subscription-Key";
        /// After obtaining a valid token, this class will cache it for this duration.
        /// Use a duration of 5 minutes, which is less than the actual token lifetime of 10 minutes.
        private static readonly TimeSpan TokenCacheDuration = new TimeSpan(0, 5, 0);

        /// Cache the value of the last valid token obtained from the token service.
        private string storedTokenValue = string.Empty;
        /// When the last valid token was obtained.
        private DateTime storedTokenTime = DateTime.MinValue;

        /// Gets the subscription key.
        public string SubscriptionKey { get; private set; }

        /// Gets the HTTP status code for the most recent request to the token service.
        public HttpStatusCode RequestStatusCode { get; private set; }

        ///

<summary>
        /// Creates a client to obtain an access token.
        /// </summary>


        /// <param name="key">Subscription key to use to get an authentication token.</param>
        public AzureTextTransfer(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("key", "A subscription key is required");
            }

            this.SubscriptionKey = key;
            this.RequestStatusCode = HttpStatusCode.InternalServerError;
        }

        ///

<summary>
        /// Gets a token for the specified subscription.
        /// </summary>


        /// <returns>The encoded JWT token prefixed with the string "Bearer ".</returns>
        /// <remarks>
        /// This method uses a cache to limit the number of request to the token service.
        /// A fresh token can be re-used during its lifetime of 10 minutes. After a successful
        /// request to the token service, this method caches the access token. Subsequent 
        /// invocations of the method return the cached token for the next 5 minutes. After
        /// 5 minutes, a new token is fetched from the token service and the cache is updated.
        /// </remarks>
        public async Task<string> GetAccessTokenAsync()
        {
            // Re-use the cached token if there is one.
            if ((DateTime.Now - storedTokenTime) < TokenCacheDuration)
            {
                return storedTokenValue;
            }

            using (var client = new HttpClient())
            using (var request = new HttpRequestMessage())
            {
                request.Method = HttpMethod.Post;
                request.RequestUri = ServiceUrl;
                request.Content = new StringContent(string.Empty);
                request.Headers.TryAddWithoutValidation(OcpApimSubscriptionKeyHeader, this.SubscriptionKey);
                var response = await client.SendAsync(request);
                this.RequestStatusCode = response.StatusCode;
                response.EnsureSuccessStatusCode();
                var token = await response.Content.ReadAsStringAsync();
                storedTokenTime = DateTime.Now;
                storedTokenValue = "Bearer " + token;
                return storedTokenValue;
            }
        }

        public String TextTransfer(string AppID,string textToTransfer, string from, string to)
        {
            string translation = String.Empty;
            string uri = "https://api.microsofttranslator.com/v2/http.svc/Translate?appid="+ AppID + "&text="
                            + System.Web.HttpUtility.UrlEncode(textToTransfer) + "&from=" + from + "&to=" + to;

            HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
            WebResponse response = null;

            try
            {
                response = httpWebRequest.GetResponse();
                using (Stream stream = response.GetResponseStream())
                {
                    System.Runtime.Serialization.DataContractSerializer dcs =
                        new System.Runtime.Serialization.DataContractSerializer(Type.GetType("System.String"));
                    translation = (string)dcs.ReadObject(stream);
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                    response = null;
                }
            }
            return (translation);
        }
    }

 

Web Chat の作成

Bot Frameworkの時とあんまり変わらないです。
それにしても、スクリーンショットとり忘れて後からとったのでちょっと微妙

 

Channels のタブのWeb Chat の「Edit」からWeb Chat の設定ができます。Skypeもありますし、他にもいろいろあります。

 

ちなみに、Bot Framework のMy Botsのところから編集することもできます。
開いたあとは、「+Add new site」をクリックすると、「How would you name youe site?」って聞かれるので、なんか文字列を入力しときます。
下のスクリーンショットでは、すでに「bot demo Trancerator」とrとlを間違えてタイプして登録しちゃってますw

 

そうすると、接続用のキーと iframe で埋め込むタグを Get できます。

 

まとめ

自分はまだあまりドキュメントを読んでませんが、Bot Framework を触ってたので割とすんなり使えました。
Bot Framework を使ったことがなくて、Bot Servicesを初めて使う方は、MSの大森さんのブログを見て頂くとちゃんと手順が載っているのでいいかもしれません。
■BOT 作成がより簡単に。Azure Bot Service による BOT アプリ作成&公開 [手順付き]
https://blogs.msdn.microsoft.com/bluesky/2016/11/16/how-to-create-publish-bot-using-azure-bot-service/

■たったの 3 ステップ!文章判別 Bot を作成しよう by Azure Bot Service × LUIS (Language Understanding Intelligent Services)
https://blogs.msdn.microsoft.com/bluesky/2016/11/17/3-steps-tutorial-azure-bot-service-x-luis-ja/

Bot Framework との違いは、Bot Framework は、API Appsにデプロイしましたが、
Bot Services は、Azure Functions 上で動くってことだと思います。

ソースの一番初めで、「#r "System.Web"」って書いてますが、
外部アセンブリ参照は、このように書くみたいです。
■Azure Functions C# developer reference (Azure Functions C# 開発者向けリファレンス)
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-csharp

あと、まだドキュメントをちゃんと読んでないので試せてないのですが、デバッグはこの辺の情報が参考になるみたい。
https://docs.botframework.com/en-us/azure-bot-service/manage/debug/

Azure Functions というと、Serverless ですよね。
今のクラウドは、IaaS をベースに徐々に PaaS にシフトする感じの過渡期で、Serverlessはもうちょい先かなって思ってたのですが、
いきなり Serverless が来た~!!って感じで、結構面白い展開かなって感じました。

自分は、Serverless はまだあんまりついていけてないので、これから勉強していきたいなって思います。

Serverless に興味がある方は、12/11に札幌で勉強会があるみたいですので、こちらに参加するのもいいかもですよ。
https://serverless.connpass.com/event/43745/

 

最後に、Bot Framework も言語の翻訳って自動的にしてくれるのあったはずで、
Bot Services にも同じのがあるような気がするけど、ここまでブログ書いてから思い出したので、
サンプルとしてイマイチだったかな。。。。

-Azure, Bot
-, , ,