🤖

iOSアプリに爆速でチャットボットを導入する

2022/02/08に公開

概要

この記事は、iOSアプリ(Swift)に手軽にチャットボットを導入する方法について紹介します。本記事におけるアプリへのチャットボットの導入とは、アプリの主要機能を補助するための目的として、チャットボットを組み込むことを想定します。アプリにチャットボットを組み込む事例はそこまで多くない印象ですが、チャットボットを導入することで様々なことを実現できるので、ぜひご参考にしていただけますと幸いです。

(導入後のイメージ)

※ チャットボットの応答内容は本記事を書くために用意した適当な文言です。

チャットボットをアプリに導入してできること

例えば、チャットボットを導入するとアプリに下記のような機能を組み込むことができます。

カスタマーサポート

アプリのカスタマーサポートをチャットボットに行わせることで、一定数のお問い合わせへの応答を自動化できます。

オンボーディング

アプリの本機能を提供者の意図通りに利用してもらえるよう、チャットボットが自動でオンボーディングを行います。適切なオンボーディングができれば、アプリの価値を感じてもらえる人が増え、継続率の向上等が見込めます。

レコメンド

アプリ内のコンテンツをレコメンドするのに活用ができます。対話形式でユーザに質問をしながら、その回答に応じたコンテンツを表示するといった利用ができます。

ユーザ調査・アンケート

チャットボットを導入することで、対話の中からユーザからアプリへのフィードバックを収集することができます。アンケートフォームを投げるだけでは回答するのに抵抗の多いユーザも多いですが、自然な対話の流れでうまくユーザの声を拾うための問いを織り交ぜることで、抵抗感少なくユーザのフィードバックを集めることができます。

この記事のGOAL

この記事では、上記のような目的で利用するチャットボットをなるべく手軽にiOSアプリに導入する方法を紹介します。GOALには下記の2つを設定します。

  1. iOSアプリにチャットボットを導入して対話形式の機能を組み込める
  2. チャットボットとアプリを連携させる

チャットボットをどれだけリッチに作るかで必要な作業時間は変わりますが、本記事で紹介するようなサンプルのチャットボットであれば、数十分程度でアプリに組み込むことができます。

前提知識

本記事で紹介する方法は、下記の知識があることが前提になります。

  • XCodeによる基本的なiOSアプリの開発方法
  • SwiftによるiOSアプリ開発

準備するもの

1. サンプルアプリ

本記事では、任意のiOSアプリにチャットボットを組み込む例を紹介します。任意のサンプルアプリをご準備ください。チャットボットの組み込みは、WKWebViewを利用します。WKWebViewを追加するViewControllerが1つ必要になります。

2. meboのアカウント

本記事では、チャットボットをmebo(ミーボ)というサービスを利用して作成します。meboは様々なプロダクトに簡単に会話AIを導入できることを目的とした、会話AI構築サービスです。筆者が個人で開発を行なっています。iOSアプリへの組み込みにも対応をしているので、こちらのサービスを利用してこの記事ではチャットボットの組み込みを行なっていきます。まずは下記からmeboにアクセスし、サインアップをしてみてください。

https://mebo.work/

お題

本記事では、上記で説明した「オンボーディング」を目的としたチャットボットを題材とします。具体的なイメージを持ちやすいよう、今回のお題をまとめてみました。

チャットボットを導入するアプリ(架空)の設定

アプリ名: 筋トレモチベーター
利用用途: 
 − 日々の筋トレを記録し後で振り返ることができる
 - どれだけ継続できたかが一眼でわかる
 - どれだけ筋トレによる効果が出たかトラッキングすることができる

上記のような、「筋トレ」の記録アプリを今回は題材にしてみようと思います。

チャットボット導入の目的

上記のアプリをユーザに快適に使ってもらえるよう、オンボーディングをチャットボットに行なってもらいます。チャットボットの用途を下記のように設定しました。

  1. 必要なユーザの情報をヒアリングし、適切に効果測定ができるようにする
  2. アプリの使い方をレクチャし、ユーザに迷わず操作をしてもらえるようにする
  3. ユーザがアプリを使うモチベーションが高める

手順

お題を設定したところで、実際の手順を紹介していきます。
meboの詳しい利用方法については必要な部分だけ掻い摘んで説明します。詳細な利用方法を知りたい方は、下記の書籍をご参照ください。
https://zenn.dev/books/f3d9eb62b6d133/edit

1. チャットボットの作成

まずはmeboを用いてチャットボットの作成を行います。meboのアカウントでログインを行いましょう。

ログインをしたら、画面左上の「新規作成して開始する」のボタンをクリックします。

meboでは、チャットボットの単位を「エージェント」と呼びます。エージェント作成に必要な項目が表示されるので入力し、最後に「登録して開始する」をクリックしましょう。

ダッシュボードに作成したエージェントが表示されればエージェントの作成は完了です。この段階で既に「会話をプレビューする」をクリックするとチャット画面で会話ができる状態になっています。

2. 会話のシナリオを用意する

エージェントを作成できたら、チャットボットが会話をする内容(会話コンテンツ)を作成していきます。meboでは、会話コンテンツを作成するためにいくつかの方法があります。meboの「トレーニング一覧」の画面から、用途ごとに適した方法で会話コンテンツを作成できます。

https://mebo-admin.work/admin/trainings

今回のお題は「シナリオ対話」が適しています。「シナリオ対話」は会話のシナリオをあらかじめ定義しておき、シナリオの流れに沿った会話を行う機能です。

トレーニング一覧画面の「シナリオ対話の作成」をクリックしましょう。

シナリオ一覧画面の「新規シナリオの作成」を押すと、シナリオエディタが起動します。シナリオを作成する前に、会話の流れをあらかじめイメージしておきましょう。

会話シナリオ

下記のようなシナリオの会話を作成してみます。あくまで例なので、内容は適宜変更してください。


①Bot: 「初めまして!筋トレモチベーターAIのXXです。これからあなたが理想の体型に近づけるよう、精一杯サポートさせていただきます。」

User: 「よろしくね。」

②Bot: 「あなたのことは何とお呼びすればいいでしょうか?」

User: 「まさお」

③Bot: 「まさおさん、これからよろしくお願いします💪 トレーニングの効果を正しく測るため、これからいくつかの質問をさせていただきます。」

User: 「わかりました。」

④Bot: 「まずは、筋トレの目的を教えてください。」

User: 「ポッコリお腹を解消したい。」

⑤Bot: 「ありがとうございます。まさおさんの身長はどのくらいなのですか?」

~ 以下、質問が続く ~

⑥Bot: 「ありがとうございました。以上で質問は終了です。回答いただいた情報をもとに、まさおさんの行なったトレーニングの効果を計測していきますね。効果はアプリのXXX画面にリアルタイムで表示されるので、定期的に確認してみてくださいね。 (アプリの画面のイメージを表示する)」

User: 「わかりました。」

⑦Bot: 「それでは、これからトレーニング頑張ってください💪 」 (アプリのホーム画面に遷移する)


※ 今回はあくまでサンプルなので、適当な質問をいくつか用意しています。

会話シナリオの作成

mebo上で上記の会話シナリオを作成していきましょう。

シナリオの設定

シナリオを作成し始めると、まずはシナリオの設定が表示されます。シナリオの名前とシナリオが呼び出される際のトリガーを設定します。今回は会話の開始と同時にシナリオが開始して欲しいので、「会話の開始と同時に呼び出す」を選択しましょう。

ノードの追加

シナリオの中でエージェントの発話の単位を「ノード」と呼んでいます。「ノードを追加する」を押して、ノードを作成しましょう。上記で紹介したシナリオの①〜⑥の発話の作成の様子をそれぞれ紹介していきます。

①挨拶

まずはエージェントが行う挨拶です。

ノードには種別を設定します。「質問」「発話」「フリートーク」から種別は選べます。今回は「発話」を選択しましょう。
種別ごとの詳細な違いは下記のご参照ください。

https://zenn.dev/makunugi/books/f3d9eb62b6d133/viewer/5e8549

「タイトル」には、meboのシナリオエディタ上に表示されるタイトルを入力します。「発話」には、エージェントが実際に会話の中で発するセリフを入力しましょう。「ステート名」はこのエージェントの発話に対して行なったユーザの応答を格納する際のラベル名を入力します。このステート名を用いて、後からユーザの応答内容にアクセスができます。クイックリプライには、ユーザの応答の候補を入力します。(クイックリプライの設定は任意です。)

「追加する」を押すと、シナリオエディタ上にノードが生成されます。この要領でノードを増やしていきましょう。

②名前を尋ねる

ユーザの名前を尋ねるノードです。先ほどとは異なり、ノード種別に「質問」を設定しています。ユーザがこのノードの発話に応答すると、「username」というキー名でユーザの応答した名前が保持されます。「回答が正しいかをユーザに確認する」にチェックをつけておくと、ユーザの応答した内容で間違いがないか、再度ユーザに確認するフローが挿入されます。

2つ目のノードが作成できたら、2つのノードを接続しましょう。1つ目のノードの右端にある●をドラッグすると線が引けるので、その線を2つ目のノードの左端にある●まで接続します。

接続すると、ノード間を移動する条件の設定画面が表示されます。今回は特に条件はないので、「何らかの文字列が入力されている」を選択して「追加する」を押しましょう。

2つのノードが接続されました。ここまできたら、一旦右下の「変更を保存」をクリックして作業を保存しましょう。

保存をすると、「プレビュー」というボタンが左上に表示されます。このボタンからいつでもシナリオのテストができます。

必要に応じてテストをしながら、シナリオを構築していきましょう。それでは、引き続きノードを追加していきます。

③質問を予告する

これから質問することをユーザに伝えるノードを追加します。クイックリプライに適当な相槌を入れておきましょう。

ノードを追加したら、同様にノード間を繋ぎましょう。

④~⑤ ユーザについて質問する

筋トレの目的

ユーザの身長

※ その他必要な質問を自由に設定してください。

⑥質問の終了と画像の表示

画像を表示したいときは、画像URLに画像のURLを設定します。

⑦シナリオの終了

例として、シナリオの終了のノードは上記のように設定してみました。シナリオ終了時は、アプリの次の画面に遷移させたいため、拡張データにアプリが次の画面にいくことを判断できるようなJsonを入力しています。また、Json内にシナリオ会話で取得することができたユーザの情報を含めています。このJsonを扱う方法は後述します。

①〜⑦のノードが接続された状態になれば、シナリオの設定は完了です。特に触れませんでしたが、条件の設定方法等次第で様々なシナリオが作れます。(正規表現等を用いて、入力内容を制限する等も可能です。)ここではシナリオの作成方法の詳細には触れませんので、さらに詳細を知りたい方は下記をご参照ください。

https://zenn.dev/makunugi/books/f3d9eb62b6d133/viewer/5e8549

エージェントを公開する

シナリオを保存したら、meboの公開設定画面を開きましょう。

アプリで今回作成したチャットボットを利用する場合は、エージェントを一般公開する必要があります。

「一般公開」を選択して、エージェントを公開しましょう。

一般公開をすると、チャット画面のURLを取得できます。こちらを後ほどアプリのWKWebViewで読み込むので控えておきましょう。

アプリから利用する場合は、APIキーが必要になります。「APIを有効にする」ボタンを押し、APIキーを取得してください。以上でmebo上での操作は終了です。

3. iOSアプリの実装

ここからはXCodeで実装を進めていきます。上記の手順で作成したチャットボットをアプリに組み込んでいきましょう。本記事では必要な実装を掻い摘んで説明していきますので、詳細な仕様を確認したい方は、下記の記事をご参照ください。

https://zenn.dev/makunugi/books/f3d9eb62b6d133/viewer/1dd4be

チャット画面を表示するViewControllerの用意

チャット画面を表示するViewControllerを作成しましょう。ここではChatViewControllerと呼ぶこととします。ChatViewControllerを作成したらWKWebViewを宣言し、userContentControllerメソッドを追加してください。下記のような実装になります。

class ChatViewController: UIViewController, WKScriptMessageHandler {
    private var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let config = WKWebViewConfiguration()
        let userContentController: WKUserContentController = WKUserContentController()
        userContentController.add(self, name: "meboCallBack")
        config.userContentController = userContentController
        webView = WKWebView(frame: .zero, configuration: config)
        webView.load(URLRequest(url: URL(string: "チャットページURL")!))
        view = webView
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
	    // TODO: Impl
    }
}

WKWebViewのセットとuserContentControllerの追加を行なっています。userContentControllerを利用するのは、meboのチャット画面から送られてくる情報をハンドリングするためです。meboCallBackという名前を必ず指定しましょう。

<チャットページURL>には、meboの公開設定画面に表示されているチャット画面のURLに&platform=webviewというクエリパラメータを付与の上入力します。

<チャットページのURL>&platform=webview

チャット画面から送られてくるイベントを処理する

例えば下記のタイミングで、チャット画面(WebView)からイベントがJson形式で送信されます。

  • エージェントの読み込みが完了した (agentLoaded)
  • エージェントから応答が返された (agentResponded)
  • エラーが発生した (error)

これらをハンドリングするため、Jsonに対応するStructを定義しましょう。

struct ChatEvent: Codable {
    let event: String
    let bestResponse: BestResponse?
    let extensions: Extensions?
}

struct BestResponse: Codable {
    let utterance: String
    let options: [String]
}

struct Extensions: Codable {
    // 任意の構造体
}

struct ChatError: Codable {
    let error: ChatErrorResponse
}

struct ChatErrorResponse: Codable {
    let code: Int
    let message: String
}

Extensionsはエージェントに指定した拡張データが格納されるオブジェクトです。本記事では、シナリオの最後に、下記のようなJsonを送るよう設定をしました。

{
    "appAction":"goNext",
    "username":"#{username}",
    "purpose":"#{purpose}",
    "height":"#{height}"
}

そのため、Extensionsは下記のように定義してあげます。

struct Extensions: Codable {
    let appAction: String?
    let username: String?
    let purpose: String?
    let height: String?
}

Structが定義できたら、チャット画面からイベントが送信されてきた時のハンドリングを、userContentControllerメソッド内に記述していきます。

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let json = (message.body as? String)?.data(using: .utf8) else {
            return
        }
        let decoder = JSONDecoder()
        guard let chatEvent: ChatEvent = try? decoder.decode(ChatEvent.self, from: json) else {
            if let error: ChatError = try? decoder.decode(ChatError.self, from: json) {
                print(error.error.message)
                return
            }
            fatalError("不明なエラー")
        }
        
        switch chatEvent.event {
        case "agentLoaded":
		// TODO: Impl
        case "agentResponded":
		// TODO: Impl
        default:
            break
        }
    }

このように実装することで、switch構文内でイベントに応じたアプリの処理を実装することができます。

チャット画面にAPIキーとユーザ識別子を渡す

チャット画面で会話をするためには、先ほど取得したAPIキーと会話をするユーザの識別子を渡す必要があります。ユーザの識別子は、チャットボットと会話をするユーザを一意に特定するためのものです。ログイン機能を備えているようなアプリの場合は、ユーザーID等を利用しましょう。今回はサンプルのため、ユニークな文字列を生成してユーザIDとしています。

APIキーとユーザ識別子の宣言をします。

private let apiKey = "<mebo上で取得したAPIキー>"
private let uid = UUID().uuidString

上記の値を、エージェントが読み込まれたタイミングで送信します。

        switch chatEvent.event {
        case "agentLoaded":
            webView.evaluateJavaScript("setAppInfo(\"\(apiKey)\",\"\(uid)\");")
webView.evaluateJavaScript("setAppInfo(\"\(apiKey)\",\"\(uid)\");")

上記の実装は、setAppInfoというチャット画面のJavaScriptの実装をアプリの実装で呼び出し、2つの情報を渡しています。ここまでの実装で、アプリのWebViewでチャット画面を利用できるようになりました。

実行するとチャットボットと会話できることが確認できるはずです。

チャットボットの応答を処理する

エージェントが応答を返すたびに、agentRespondedが呼ばれます。まずは、イベントの内容を出力して確認してみましょう。

        case "agentResponded":
            print(chatEvent.bestResponse!.utterance)
	    

エージェントの応答が出力されることが確認できれば成功です。シナリオの会話が終了したら次の画面に遷移をさせたいので、シナリオの最後のノードに仕込んだJsonを扱います。

        case "agentResponded":
            print(chatEvent.extensions?.appAction)
            print(chatEvent.extensions?.username)
            print(chatEvent.extensions?.purpose)
            print(chatEvent.extensions?.height)
	    

試しにExtensionsに流れてくる情報を出力してみます。

拡張データに追加したJsonの内容が送られてきていることが確認できます。この値に応じてアプリ内の適切な処理を実装することで、チャット画面とアプリの機能を連動することが可能です。例として、appActiongoNextが送られてきたら次の画面をを出すように実装してみます。

※ ここでは例として、次の画面に遷移する代わりにアラートダイアログを表示します。

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print(message.body as! String)
        guard let json = (message.body as? String)?.data(using: .utf8) else {
            return
        }
        let decoder = JSONDecoder()
        guard let chatEvent: ChatEvent = try? decoder.decode(ChatEvent.self, from: json) else {
            if let error: ChatError = try? decoder.decode(ChatError.self, from: json) {
                print(error.error.message)
                return
            }
            fatalError("不明なエラー")
        }
        
        switch chatEvent.event {
        case "agentLoaded":
            webView.evaluateJavaScript("setAppInfo(\"\(apiKey)\",\"\(uid)\");")
        case "agentResponded":
            if let extensions = chatEvent.extensions, let appAction = extensions.appAction, appAction == "goNext" {
                DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
                    guard let weakSelf = self else {
                        return
                    }
                    weakSelf.moveToNext(extensions: extensions)
                }
            }
        default:
            break
        }
    }
    
    private func moveToNext(extensions: Extensions) {
        // TODO: 次の画面にExtensionsを渡す
        let alert: UIAlertController = UIAlertController(title: "次の画面に遷移します。",message: "会話が終了しました。", preferredStyle: UIAlertController.Style.alert)
        let confirmAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler:nil)
        alert.addAction(confirmAction)
        present(alert, animated: true, completion: nil)
    }

上記のコードを実行し、最後のエージェントの発話から3秒後にアラートダイアログが表示されれば、チャットボットとアプリの連携は無事できています。

UIを独自実装したい場合

WKWebViewを利用しているので、最小限の実装でチャットボットを導入することができます。UIを作り込めないのがデメリットですが、チャットUIを独自に用意したい場合は、meboのAPIをご利用いただくことで実現できます。

APIの利用方法は下記をご参照ください。
https://zenn.dev/makunugi/books/f3d9eb62b6d133/viewer/f0c36f

まとめ

以上で、iOSアプリに簡単にチャットボットを組み込むことができました。チャットボットの導入に興味がある方は、ぜひお試しいただけますと嬉しいです。チャットボットとアプリの連携の改善やmeboの機能改善等を引き続き行っていこうと考えておりますので、何かフィードバック等ありましたら、下記のTwitterアカウントまでお寄せください。

https://twitter.com/maKunugi

最後までお読みいただきありがとうございました!

Discussion