Technical Articles
Part 2 : SAP Cloud Platform FunctionsでLINE BOT開発(画像判定編)
前回のLINE BOT作成記事に引き続き、今回はSAP Leonardo Machine Learning APIと連携し、画像をAIが判定するBOTの作り方について紹介します。
できるようになること
- SAP Leonardo ML APIの使い方がわかるようになる。
- SAP Leonardo ML APIをNode.jsから呼び出せるようになる。
所要時間
- 1時間30分
参考: LINEについて
LINEはアジア圏、特に日本において普及しているSNSツールです。
日本国内におけるユーザ数は約7600万人。デイリーアクティブユーザー(DAU)は85%と、利用者数、利用頻度共に非常に高い数値を誇っています。
参考: LINE Messaging APIとは?
LINEのアカウントを通じてユーザーとの双方向コミュニケーションを実現するAPIです。
- LINEのトーク画面を使った対話型Botアプリケーションの開発が可能となります。
このAPIは、宅配便の再配達受付や、年賀状の作成など、多くの企業LINEアカウントにて既に活用されています。
参考: SAP Cloud Platform Functionsとは?
SAPが先般発表したサーバレスアーキテクチャのプログラム実行環境です。
Node.jsで作成したプログラムを実行することが出来ます。
手順の概要
- SAP API Business HubでLeonardo MLのAPIを試す
- チャネルの作成
LINEアカウントでログインしてBOTアカウント作成や設定をする。 - Functionsのサービス有効化
SAP Cloud Platform CF Trial Europe(Frankfurt)環境にアクセスし、Functionsを利用するための初期設定を行う。 - Node.jsのプログラムを作成する
LINEトーク画面からのメッセージを受け取り、返信するプログラムを作成します。 - スマートフォン実機でのテスト
1.SAP API Business HubでLeonardo MLのAPIを試す
SAP API Business Hubで、Leonardo Machine LearningのAPIを簡単に試すことが可能です。
Leonardo MLでは、
- 事前にトレーニング済みのAIを使う
- ユーザが再学習させたAIを使う
ことが出来ます。今回は事前にトレーニング済みの画像分類APIを呼び出してみることにします。
API Business Hubの画像分類APIのページをブラウザで開き、試したいAPIのTry outボタンをクリックします。
画像ファイルを選択し、実行してみます。
何の画像であるかAIが判定した結果が返ってきます。
API Business HubのCode Snippetボタンを押すと、色々なプログラミング言語からAPIを呼び出すコードスニペットを参照することが出来ます。
この後の手順で、Node.jsからLeonardo MLのAPIをコールするときにAPI Keyを利用します。スニペットの画面を見て、API Keyを控えておきましょう。
2.チャネルの作成
LINEの公式ページの通りにチャネルの作成を行います。
設定方法、設定箇所は前回の記事をご参照ください。
今回私は、『画像認識くん』という名前のチャネル(BOTアカウント)を作成しました
チャネルの基本設定を行います。設定の変更箇所を朱書きしております。
3.Functionsのサービス有効化
2018年10月24日現在、FunctionsはSAP CP CF版TrialのEurope(Frankfurt)リージョンでのみ利用可能です。
また、Betaサービスを有効化する必要があります。
まず、Betaサービスが利用可能な新規サブアカウントを作成します。
次に、trial2サブアカウントのFunctionsサービスを有効化します。
Functionsのインスタンスを作成します。
4.Node.jsのプログラムを作成する
Functionsダッシュボードにアクセスし、プログラムを作成します。
プログラム名は『firstmlbot』にしました。
以下のコードをコピペしてご利用ください。
**********箇所をLINE BOTのチャネル用のアクセストークンに書き換えてください。
YOUR_API_KEY箇所をLeonardo ML APIのAPI Keyに書き換えて下さい。
■index.js
var request = require("request");
var https = require("https");
module.exports = {
handler: function(event, context) {
if (event.data) {
if (event.data.events[0].type == "message") {
if (event.data.events[0].message.type == "image") {
var imageid = event.data.events[0].message.id;
// get image from below link
//https://api.line.me/v2/bot/message/{imageid}/content
var options = {
method: 'GET',
uri: 'https://api.line.me/v2/bot/message/' + imageid + '/content',
encoding: null,
auth: {
bearer: "*********************" // LINE BOT Access Token
}
};
request(options, function(error, response, body) {
// get binary image
var binaryimage = new Buffer(body);
// request to SAP ML Server
var boundary = createBoundary();
let optionstosap = {
host: "sandbox.api.sap.com",
port: 443,
path: "/ml/imageclassification/classification",
method: "POST",
headers: {
"APIKey": "YOUR_API_KEY",
"Content-Type": "multipart/form-data; boundary=" + boundary
}
};
var reqtosap = https.request(optionstosap, function(res) {
var data2 = '';
res.setEncoding("utf8");
res.on("data", (chunk) => {
data2 += chunk;
var resultarr = JSON.parse(data2).predictions[0].results;
var columns = [];
for (var k = 0; k < resultarr.length; k++) {
var column = {};
column.title = resultarr[k].label.substring(0, 35);
column.text = Math.round(resultarr[k].score * 100 * 10) / 10 + "%";
var actions = [];
var action = {};
action.type = "uri";
action.label = "Google翻訳";
action.uri = "https://translate.google.co.jp/?hl=ja#en/ja/" + encodeURIComponent(resultarr[k].label);
actions.push(action);
column.actions = actions;
columns.push(column);
}
var options2 = {
method: 'POST',
uri: 'https://api.line.me/v2/bot/message/reply',
body: {
replyToken: event.data.events[0].replyToken,
messages: [{
type: "template",
altText: "Image classification result.",
template: {
type: "carousel",
columns: columns
}
}]
},
auth: {
bearer: "*********************" // LINE BOT Access Token
},
json: true
};
request(options2, function(err, res, body) {
console.log(JSON.stringify(res));
});
});
reqtosap.on('end', function() {
console.log('data;', data2);
res.end();
});
});
reqtosap.on("error", function(e) {
console.error(e.message);
});
var buffer = unicode2buffer(
'--' + boundary + '\r\n' + 'Content-Disposition: form-data; name="files"; filename="myimage.png"\r\n' +
'Content-Type: image/png\r\n\r\n'
);
var buffer = appendBuffer(buffer,
binaryimage
);
var buffer = appendBuffer(buffer,
unicode2buffer(
'\r\n' + '--' + boundary + '--'
)
);
reqtosap.write(Buffer.from(buffer));
reqtosap.end();
});
}
}
}
console.log('event data ' + JSON.stringify(event.data));
return 'hello world from a function!';
}
}
function createBoundary() {
var multipartChars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var length = 30 + Math.floor(Math.random() * 10);
var boundary = "---------------------------";
for (var i = 0; i < length; i++) {
boundary += multipartChars.charAt(Math.floor(Math.random() * multipartChars.length));
}
return boundary;
}
function unicode2buffer(str) {
var n = str.length,
idx = -1,
byteLength = 512,
bytes = new Uint8Array(byteLength),
i, c, _bytes;
for (i = 0; i < n; ++i) {
c = str.charCodeAt(i);
if (c <= 0x7F) {
bytes[++idx] = c;
} else if (c <= 0x7FF) {
bytes[++idx] = 0xC0 | (c >>> 6);
bytes[++idx] = 0x80 | (c & 0x3F);
} else if (c <= 0xFFFF) {
bytes[++idx] = 0xE0 | (c >>> 12);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
} else {
bytes[++idx] = 0xF0 | (c >>> 18);
bytes[++idx] = 0x80 | ((c >>> 12) & 0x3F);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
}
if (byteLength - idx <= 4) {
_bytes = bytes;
byteLength *= 2;
bytes = new Uint8Array(byteLength);
bytes.set(_bytes);
}
}
idx++;
var result = new Uint8Array(idx);
result.set(bytes.subarray(0, idx), 0);
return result.buffer;
}
function appendBuffer(buf1, buf2) {
var uint8array = new Uint8Array(buf1.byteLength + buf2.byteLength);
uint8array.set(new Uint8Array(buf1), 0);
uint8array.set(new Uint8Array(buf2), buf1.byteLength);
return uint8array.buffer;
}
■Dependencies
{
"dependencies": {
"request": "*"
}
}
(再掲)アクセストークンは、LINE BOTのチャネルの基本設定画面で確認できます。
(再掲)API Keyは、SAP API Business HubのCode Snippetで確認できます。
ソースコードを編集したら、ダッシュボード右上のSave and Deployボタンを押して保存します。
次に、トリガ設定(何を契機として作成したプログラムを動作させるかの設定)を行います。今回は、HTTPトリガを利用します。
参考: Functionsで利用できるトリガ
トリガ | 説明 |
HTTP | URLを発行し、そのURLに対してHTTPリクエストが行われたときに発火します。 |
Timer | 一定時間おきに処理を実行できます。 |
Event | SAP Enterprise Messagingと組み合わせ、外部から送られてきたイベントを契機としてアプリケーションを動作させることができます。 |
FunctionsのトリガURLをコピーし、LINE Developersのチャネル設定画面のWebhook URLにペーストします。
これで開発は完了です。テストを行います。
5.スマートフォン実機でのテスト
LINE Developersのチャネル設定画面のQRコードをスマートフォンで読み取り、友達追加を行います。
BOTに画像を送ると、Leonardo ML 画像分類APIが何の画像なのかを判定してくれます。
以上で画像の判定をするLINE BOTの作成が出来ました。
下記のQRコードをお手持ちのスマートフォンで読み込むことで、今回作成したLINE BOTを簡単に体験することができます。
今回のLINE BOTは、グループチャットに招待して使うことも可能です。
是非LINEのグループチャットに招待して、知り合いの方と一緒にワイワイ遊んでみて下さい。
Leonardo MLの画像分類機能をビジネスで利用する場合は、トレーニング済AIではなく、再学習させたAIを利用すべきでしょう。
Leonardo MLの再学習については、いずれ記事にしたいと思います。
Blogとコードの共有ありがとうございます!すぐに自分のアカウントで再現でき、インパクトのあるデモが作れます!