2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rubyで黒澤ルビィを作る(Ruby + LINE Messaging API)

Last updated at Posted at 2021-01-10

黒澤ルビィ、可愛いですよね。

LINE上で喋るルビィちゃんを作りました。Rubyで。

Rubyボット01.png

以下の仕組みで動いています。

  • LINE Messaging APIを用いて、「黒澤ルビィ」としてLINEメッセージの受信、送信を行う。
  • Ruby(Ruby on rails)LINE Messaging APIとやり取りするAPIサーバを作る。
  • メッセージを形態素解析し、その結果をもとにマルコフ連鎖で新しく文章を生成する

LINEアカウントを作る

前準備としてルビィちゃんとして動くアカウント(厳密に言うと、Messaging APIのチャネル)が必要です。それをつくります。

LINE Developersから登録します。
Rubyボット02.png
Create a new channelから新しく作ります。なお、作るときにどういうTypeか聞かれると思います。今回作りたいものはMessaging APIで作ることができるので、該当するものを選択してください。

無事作成できると、アクセストークンを取得できます。
また、アカウントのアイコンや自己紹介欄などを設定できます。1

APIサーバを作る

画面を持つ予定はないので、APIモードでRailsプロジェクトを作ります。
line-bot-apigem2を入れます。先程作成したアカウントのアクセストークンを使ってLINEとやりとりできるようになります。

Controllerを作る

先程作成したアカウントに対してWebhook URLを設定すると、そのアカウントに対して送信したメッセージをトリガーにしてLINEがリクエストを送ってくれます。
具体的なリクエストの中身についてはMessaging APIリファレンスを参照してください。以下の例はテキストメッセージに反応して、「メッセージ」と返すだけの仕組みです。

def client
  @client ||= Line::Bot::Client.new { |config|
    config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
    config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
  }
end

def callback
  body = request.body.read

  signature = request.env['HTTP_X_LINE_SIGNATURE']
  head :bad_request unless client.validate_signature(body, signature)

  events = client.parse_events_from(body)

  events.each do |event|
    next unless event.is_a?(Line::Bot::Event::Message)
    next unless event.type == Line::Bot::Event::MessageType::Text

    client.reply_message(event['replyToken'], message)
  end
  head :ok
end

def message
  # cf. https://developers.line.biz/ja/reference/messaging-api/#text-message
  {
    "type": 'text',
    "text": 'メッセージ'
  }
end

このmessageに相当する部分を、マルコフ連鎖で生成した文章にさせます。

マルコフ連鎖で文章を作る

マルコフ連鎖による文章作成の大枠については解説しているものがたくさんあるので、詳しくは「マルコフ連鎖 文章作成」とかで検索してください。

ざっくり説明すると、あらかじめ形態素解析(言語で意味をもつ最小単位に分解すること)によって言葉のつながりを学習させ、ある単語に対してそれに続く言葉を過去の学習から確率的に選択していくことでそれっぽい文章ができるやつです。

形態素解析する

形態素解析は、Yahoo!が提供している日本語形態素解析を使っています。MeCab(Natto)を使って、オタク用語に特化した辞書を入れるのもアリかなと思います。

LINEメッセージが送られたらそのメッセージをそのまま形態素解析します。結果をパースし、言葉と品詞がセットとなったハッシュの配列にします。3

単語として保存するWordモデルと、つながりを保存するMarkovDicモデルを作り、それぞれ保存します。

Wordモデル

カラム名 説明
pos String 品詞(例:名詞)
surface String 単語(例:庭)

MarkovDicモデル

カラム名 説明
prefix_1 String 単語のつながりをA-B-Cと表現したときのA (例:私)
prefix_2 String 単語のつながりをA-B-Cと表現したときのB (例:は)
suffix String 単語のつながりをA-B-Cと表現したときのC (例:犬)
  # 形態素解析を行い、WordレコードとMarkovDicレコードを作成する。名詞の配列を返す。
  def record(text, user)
    text = remove_url(text)  # URLを取り除いたりしている
    morphological_words = exec(text)

    poses = save_words(morphological_words, user)
    save_markov_dics(morphological_words)

    poses
  end

  # 単語を保存する。
  def save_words(morphological_words)
    words = []

    morphological_words.each do |word|
      w = Word.new(word)
      words << w.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    Word.insert_all(words) if words.present?
  end

  def save_markov_dics(morphological_words)
    markov_dics = []
    morphological_words.each_cons(3) do |p1, p2, suf|
      next if p1['surface'] == "\n"
      next if p2['surface'] == "\n"

      md = MarkovDic.new(prefix_1: p1['surface'], prefix_2: p2['surface'], suffix: suf['surface'])
      md.suffix = 'END_OF_SENTENCE' if md.suffix == "\n" # 文章の終わりを意味するフラグ
      markov_dics << md.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    eos = morphological_words.last(2)
    if eos.size == 2
      p1 = eos[0]
      p2 = eos[1]
      md = MarkovDic.new(prefix_1: p1['surface'], prefix_2: p2['surface'], suffix: 'END_OF_SENTENCE')
      markov_dics << md.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    MarkovDic.insert_all(markov_dics) if markov_dics.present?
  end

文章を作る

LINEに送られたメッセージを保存することができたので、今度は文章を作ります。
始まりの言葉は受け取ったLINEのメッセージからランダムに名詞で始まるMarkovDicを選んでいます。
ざっくり以下のような感じです。

  def create_sentence(text, markov_dic)
    return text if markov_dic.suffix == 'END_OF_SENTENCE'

    next_markov_dic = MarkovDic.where(prefix_1: markov_dic.prefix_2, prefix_2: markov_dic.suffix).sample(1).first
    return text unless next_markov_dic

    text << markov_dic.suffix
    create_sentence(text, next_markov_dic)
  end

  def prefixes
    prefix_1 + prefix_2
  end

Webhook URLを設定する

作成したAPIサーバをデプロイします。(今回はHerokuにデプロイしました。)
最初に作成したアカウントの設定画面でWebhook URLを設定し、メッセージがきたタイミングでLINEがリクエストを送ってくれるようにします。
Rubyボット03.png

LINEグループに追加します。いい感じにレスポンスしてくれます。

Rubyボット05.png

その他

  • グループラインにルビィちゃんを混ぜたところ、「かわいい」「キメラが誕生した」「新しいおもちゃ」と非常に好評でした。
  • クソデカ羅生門を覚えさせた人がいてめちゃくちゃになりました。
  • グループラインにBotアカウントは一つまでしか入れられないようです。黒澤姉妹で話すことはできませんでした。
  • 常に返信がくるとグループラインがやかましくなるので、ランダムで投稿したりしなかったりさせています。(ちなみに直接ルビィちゃんに話しかけると100%返信が来ます。)
  • push_messageを使うとルビィちゃんから話しかけることもできます。
  • 本当はアカウントを紹介したいのですが、記録した言葉の中に含まれる個人情報を考慮すると中々難しいものがあります。
  • 今の所ルビィちゃん要素が0なので、ルビィちゃんっぽくさせたいです。

まとめ

LINE Messaging APIRubyを用いて黒澤ルビィを作りました。

LINE Botは意外と簡単に作ることができるので、ぜひ作ってみてください。

追記:リポジトリはこちらで公開しております。

  1. 開発用のアカウントも合わせて作ったりしています。ちなみに画像は友達が描いたやつです。

  2. line/line-bot-sdk-ruby: LINE Messaging API SDK for Ruby

  3. 以下を参考にしました。Ruby on Rails で Yahoo のテキスト解析 APIを使う-スケ郎のお話

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?