Help us understand the problem. What is going on with this article?

【LINE Bot】Heroku+PHP+雑談対話APIで会話Bot作ってみた

More than 3 years have passed since last update.

LINE Botと雑談APIを使って、会話Botを作ってみました。

雑談対話APIとは

docomoさんが提供している自然な会話・雑談を実現するためのAPIです。
ユーザが入力した発言に対して、APIから返事がレスポンスされます。
https://dev.smt.docomo.ne.jp/?p=docs.api.page&api_name=dialogue&p_name=api_usage_scenario

ユーザの何気ない一言にバリエーション豊富な応答を返す、コンピュータと雑談を楽しむことができるAPIです。雑談対話APIはユーザのどんな発話に対しても必ず応答します。

docomo Developer supportへの登録

雑談対話APIを使用するために、開発者登録を行います。

登録完了後、APIの利用申請を行いますが、申請後即使用可能になりました。とても便利:clap:
(ちなみに法人情報の入力を勧められますが、入力しなくても問題なく使えました。APIリクエスト数が個人ユーズの範囲を越える(Botを一般公開する、など)する時は、入力したほうが良いのかも・・・と思っています。)

Heroku環境のセットアップ

先にBotを動かすインフラをセットアップします。今回はHerokuにPHPの環境を作りました。

アプリを作成したら、「Fixie」というアドオンをインストールします。
$ heroku addons:create fixie:tricycle

Static IP addresses for outbound requests

BOT API Trial Accountへの登録

LINE Bot APIの開発者登録をします。先着10,000名とのことですが、4/11時点では問題なく登録可能でした。
https://business.line.me/ja/products/4/introduction

特に審査期間もなく、登録後はすぐにAPIを使用することができます。

BOT APIの設定

Basic information

callback URLは、https://(HerokuアプリのURL):443/callbackを設定します。

スクリーンショット 2016-04-13 21.29.51.png

Server IP Whitelist

先ほどセットアップしたHerokuアプリに戻ります。ダッシュボードからFixieをクリック。

スクリーンショット 2016-04-13 21.45.17.png

Account->AccountDetailを表示します。
スクリーンショット 2016-04-13 21.45.46.png

Outbound IPsの値を、LINE BotのServer IP Whitelistに転記します。
スクリーンショット 2016-04-13 21.55.14.png

環境変数の設定

Herokuアプリに戻って、LINE Botのパラメータを設定します。
スクリーンショット 2016-04-13 22.05.45.png
スクリーンショット 2016-04-13 22.08.46.png

ソースコード

index.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Symfony\Component\HttpFoundation\Request;

$app = new Silex\Application();
$app->post('/callback', function (Request $request) use ($app) {
    $client = new GuzzleHttp\Client();
    $body = json_decode($request->getContent(), true);
    foreach ($body['result'] as $msg) {
        $reply_message = chat($msg['content']['text']);

        $resContent = $msg['content'];
        $resContent['text'] = $reply_message;
        $requestOptions = [
            'body' => json_encode([
                'to' => [$msg['content']['from']],
                'toChannel' => 1383378250, #Fixed value
                'eventType' => '138311608800106203', #Fixed value
                'content' => $resContent,
            ]),
            'headers' => [
                'Content-Type' => 'application/json; charset=UTF-8',
                'X-Line-ChannelID' => getenv('LINE_CHANNEL_ID'),
                'X-Line-ChannelSecret' => getenv('LINE_CHANNEL_SECRET'),
                'X-Line-Trusted-User-With-ACL' => getenv('LINE_CHANNEL_MID'),
            ],
            'proxy' => [
                'https' => getenv('FIXIE_URL'),
            ],
        ];
        try {
            $client->request('post', 'https://trialbot-api.line.me/v1/events', $requestOptions);
        } catch (Exception $e) {
            error_log($e->getMessage());
        }
    }
    return 'OK';
});

$app->run();

function chat($send_message) {
    // docomo chatAPI
    $api_key = 'your docomoAPI key';
    $api_url = sprintf('https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY=%s', $api_key);
    $req_body = array('utt' => $text);
    $req_body['context'] = $send_message;

    $headers = array(
        'Content-Type: application/json; charset=UTF-8',
    );
    $options = array(
        'http'=>array(
            'method'  => 'POST',
            'header'  => implode("\r\n", $headers),
            'content' => json_encode($req_body),
            )
        );
    $stream = stream_context_create($options);
    $res = json_decode(file_get_contents($api_url, false, $stream));

    return $res->utt;
}

動作結果

スクリーンショット 2016-04-13 22.21.24.png
_人人人人人人人人人_
> 会話にならない <
 ̄Y^Y^Y^Y^Y^Y^Y^Y ̄

実装修正(2016.05.10追記)

2回目以降の会話にはcontextをリクエスト指定しないといけないとご指摘頂きました。:bow:
雑談会話APIからレスポンスされるcontextをRedisに保存して使用します。
(参考記事: LINE Botとおしゃべりする。http://qiita.com/tkitauji/items/0e1d25bc55c7ea6dcfed

index.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Symfony\Component\HttpFoundation\Request;

$app = new Silex\Application();
$app->post('/callback', function (Request $request) use ($app) {
    $client = new GuzzleHttp\Client();
    $body = json_decode($request->getContent(), true);
    foreach ($body['result'] as $msg) {
        // get from and message
        $from = $msg['content']['from'];
        $message = $msg['content']['text'];
        // get context from Redis
        $redis = new Predis\Client(getenv('REDISTOGO_URL'));
        $context = $redis->get($from);
        // chat API
        $response = chat($message, $context);
        // save context to Redis
        $redis->set($from, $response->context);

        $res_content = $msg['content'];
        $res_content['text'] = $response;
        $requestOptions = [
            'body' => json_encode([
                'to' => [$from],
                'toChannel' => 1383378250, #Fixed value
                'eventType' => '138311608800106203', #Fixed value
                "content" => $res_content,
            ]),
            'headers' => [
                'Content-Type' => 'application/json; charset=UTF-8',
                'X-Line-ChannelID' => getenv('LINE_CHANNEL_ID'),
                'X-Line-ChannelSecret' => getenv('LINE_CHANNEL_SECRET'),
                'X-Line-Trusted-User-With-ACL' => getenv('LINE_CHANNEL_MID'),
            ],
            'proxy' => [
                'https' => getenv('FIXIE_URL'),
            ],
        ];
        try {
            $client->request('post', 'https://trialbot-api.line.me/v1/events', $requestOptions);
        } catch (Exception $e) {
            error_log($e->getMessage());
        }
    }
    return 'OK';
});

$app->run();

function chat($message, $context) {
    $api_key = 'your docomoAPI key';
    $api_url = sprintf('https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY=%s', $api_key);
    $req_body = array(
        'utt' => $message,
        'context' => $context,
    );
    $req_body['context'] = $message;

    $headers = array(
        'Content-Type: application/json; charset=UTF-8',
    );
    $options = array(
        'http'=>array(
            'method'  => 'POST',
            'header'  => implode("\r\n", $headers),
            'content' => json_encode($req_body),
            )
        );
    $stream = stream_context_create($options);
    $res = json_decode(file_get_contents($api_url, false, $stream));

    return $res->utt;
}

スクリーンショット 2016-05-10 14.42.48.png

会話できました!!

スクリーンショット 2016-05-10 14.44.03.png

結構たのしい。

その他改善策

雑談対話APIにdアカウントによるユーザ認証・許可を実施することで、よりユーザの趣味嗜好に基づいた応答が可能となります。

Fixieについて

tricycleプランだと、500req/月まで無料のようです。
たくさん会話したい時は課金しないとですね...:innocent:
スクリーンショット 2016-04-14 15.42.30.png

まとめ

今回は半分ネタで会話Botを作ってみましたが、LINE BotとREST APIの相性は抜群で、アイディア次第でいろんなことができそうだなーと手応えを感じました。

参考URL

先人のみなさま、大変参考になりました。ありがとうございました。。!:pray:

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした