イノベーション エンジニアブログ


株式会社イノベーションのエンジニアたちの技術系ブログです。ITトレンド・List Finderの開発をベースに、業務外での技術研究などもブログとして発信していってます!


このエントリーをはてなブックマークに追加

LINE + Function App で AI とお話してみた

お久しぶりです、SREの人 syoga です。今年も後5ヶ月ですが、ログ出して行きましょう。

皆様すでにご存知かもしれませんが「電子レンジで魚が焼ける皿」を買いました。これが凄く便利で、皿に魚(生 or 干物)を載せてレンジでチンするだけで、焼き魚が簡単に作れます!

個人的に感動したので、こちらで共有させていただきました!

まるで魔法のようですね…魔法と言えば我々エンジニアは、魔法のようなクラウドサービスを利用する事が多いかと思います、そう!AWSです。

もしもAWSが使えなかったら…

【ここから超展開】

『つまり!何が起こるか分からない現在(イマ)だからこそ、
地球外生命体が攻めてきて全世界のAWSデータセンターを、
破壊する可能性だってあるんだ!!』

ΩΩΩ < な、なんだってー!?

【ここまで 】

色々なクラウドサービスを利用できた方が良いかなと思い、以前の 記事で作成した「画像解析Bot」くんを、Azureに移行するための理由を無理矢理作りました。

AWS < = > Azure

AWS では Lambda + API GatewayS3を利用していましたが、Azure では同等の機能として Function AppStorage があります。

Function App でも Node.js が利用できますので、コードを改良しつつ載せ替えてみます。

■ 改良したい点
・お話できるようにする
・検索ワード指定でぐるなび検索
・コールバック地獄から脱出

それでは早速…

準備します

1.Function App の作成

必要な物は Microsoft アカウントとクレジットカードと、後は勇気だけ。

詳細な手順については以下の Microsoft Docs を参照下さい。
Azure Functions を使用したサーバーレス API の作成

2.Storage の作成

必要な物は(略)。

詳細な手順については以下の Microsoft Docs を参照下さい。
Azure ストレージ アカウントについて

プランで変わってきますが、1GB ¥1.53〜¥5.10で利用できます(最初の50TB/月)。

3.Redis Cache の作成

ぐるなびでのキーワード指定検索や、下記の Repl-AI で利用するユーザ ID を保存するために、Redis でデータを格納したいと思います。

詳細な手順については以下の Microsoft Docs を参照下さい。
Azure Redis Cache の使用方法

れでぃさ〜。

4.会話用に利用するRepl-AIのユーザ登録

Repl-AI という対話式チャットボットの API を利用し、お話できるようにしますので、docomo Developer support アカウントを作成し、プロジェクトを作成しておきます。

会話シナリオを GUI を利用しノンプログラミングで作成できます、今回はデフォルトのシナリオを利用し会話できるようにします。

詳細については以下ページを参照下さい。
Repl-AI ご利用までの流れ

以上で準備完了です、今回もエラーハンドリングはしません。

検索ワード指定でぐるなび検索

苦肉の策で「◯◯が食べたい。」というメッセージの場合は、◯◯(料理名の部分)を LINE ユーザ ID をキーに、Redis Cache に保存し位置情報をせびるようにします。

switch(type) {
    // テキストメッセージ受信処理
    case('text'):
        // テキストメッセージ取得
        let text = lineMess.message.text;

        // メッセージが「◯◯が食べたい。」の場合
        if(text.match(/が食べたい。/)) {
            // メッセージから料理名を取得
            let food = text.slice(0, text.indexOf('が食'));
            if(food) {
                client.set('guru' + userId, food);
                pushTexeMessage('位置情報を送ってくれないかな?', userId, context, function(result) {});
            } else {
                // 料理名が取得できなかった場合
                pushTexeMessage('それは食べ物ではないんじゃないかな?', userId, context, function(result) {});
            }

「◯◯が食べたい。」 以外の場合は、Repl-AI とお話しますが、後ほどそちらに触れます。

位置情報を受信した際に、Redis Cache に登録された料理名を取得し、次回の食べたいメッセージに備えて料理名を削除した後、ぐるなびの API に取得した料理名を投げ、検索結果を返信します。

// 位置情報受信処理
case('location'):
    async.waterfall([
        // 「◯◯が食べたい。」のメッセージを送っているか
        function(callback) {
            client.get('guru' + userId, function(err, result) {
                callback(null, result);
            });
        },
        function(food, callback) {
            // 料理が存在しない場合はパンケーキを設定
            if(!food) {
                food = 'パンケーキ';
            }
            // 料理名で店舗を検索
            getGuruRest(latitude, longitude, food, context, function(result) {
                callback(null, result, food);
            });
        },
        function(restList, food, callback) {
            // 店舗検索結果に応じてメッセージを送信
            if(typeof restList.rest != 'undefined' || restList.rest.length > 0) {
                pushTexeMessage('近くに' + food + 'が食べられるお店があるかな。', userId, context, function(result) {
                    callback(null, restList);
                });
            } else {
                pushTexeMessage('近くに' + food + 'が食べられるお店はないかな。', userId, context, function(result) {});
            }
        },
        function(restList) {
            let rest;
            let sendRest;

            // 料理名を削除
            client.del('guru' + userId);

            // 店名、住所、URLを LINE にプッシュ
            restList.rest.forEach(function(rest) {
                sendRest =  '[店名] : ' + rest.name + '\n';
                sendRest += '[住所] : ' + rest.address + '\n';
                sendRest += '[URL] : '  + rest.url;
                pushTexeMessage(sendRest, userId, context, function() {});
            });
        }
    ]);
break;

食べたいメッセージがない状態で、位置情報が送られた場合は、強制的に検索ワードはパンケーキとなります。

お話できるようにする

Repl-AI 用のユーザIDを払い出し、そのIDを利用する事でユニークにお話できるという仕組みになっています、なのでキーとして LINE ユーザ ID を利用し Repl-AI のユーザ ID を管理します。

その後、払い出されたユーザIDとメッセージを API に投げる事で、お返事メッセージがレスポンスされますので、お返事メッセージを LINE に プッシュします。

async.waterfall([
	// Repl-AI のユーザID登録確認
	function(callback) {
	    client.get(userId, function(err, result) {
	        callback(null, result);
	    });
	},
	// 未登録なら Repl-AI ユーザID登録
	function(replUserId, callback) {
	    let initFlag = false;
	    if(!replUserId) {
	        getReplAiUserid(replUserId, context, function(result) {
	            let initFlag = true;
	            client.set(userId, result);
	            callback(null, result, initFlag);
	        });
	    } else {
            // 登録済ならお話
	        callback(null, replUserId, initFlag);
	    }
	},
	// Repl-AI とお話
	function(replUserId, initFlag, callback) {
	    getReplAiMessage(replUserId, text, initFlag, context, function(result) {
	        callback(null, result);
	    });
	},
	function(respMess) {
    	// お返事をLINEにプッシュ
        pushTexeMessage(respMess, userId, context, function(result) {});
	}
]);

コールバック地獄でも、メンテナンスに困らなければ良いのでは?と思ったのは内緒ですが、async モジュールを利用した事により、ネストが一定より深くならなくなりました!やったね。

使ってみた

・ぐるなび検索を利用してみます。 azure5-1.png

・位置情報を送信し、検索結果が返ってきました azure5-2.png

・お話もしてみましょう
azure.gif
ドヤってますね。

・画像解析も念のため、弊社限定フリー素材 KTN さんにお願いしました
azure5-3.png お!いい感じの年齢判定ですね、翻訳機能はやはりアレですが。

感想

Function App ですが個人的には UI が Lambda よりスマートな感じがしました、Node.js のモジュールインストールは、Function を作成したサーバに対しコンソールで直接操作出来るため、 npm install で普通に出来ます、サーバレスとは…。

webhook + API用の関数作成を実施すれば、API GateWay のような設定は必要なく、Function App の管理画面から利用 URL を取得できます、async を利用し作り直した部分以外(関数等)は、コピペでいけるかと思いきや、console.log が利用できずエラーとなる罠がありました、context.log じゃないとダメ!

Repl-AI の会話シナリオはデフォルトのシナリオを利用しましたが、今後は手を加えて行きたいなと思います、音声解析で会話させるのも良さそうです、Node.js で作成しましたが非同期処理に振り回された感じです、C# でやれば良かったな。

統括すると、サーバレスとか、クラウドサービスとか、使用言語にとらわれずに、なんでもやってみると楽しいもんだな〜と改めて思いました。あれ?作文??