【ServiceNow】30分で作るクイズ用チャットボット

## はじめに

この記事は [ServiceNow Advent Calendar 2023](https://qiita.com/advent-calendar/2023/servicenow) の６日目の記事です。

最近 ServiceNow の仮想エージェント(Virtual Agent)で遊ぶのにハマっているのですが、四択クイズとか作れないかな〜と思い立ち、試しにやってみることにしました。


## 実現したいこと

以下の要件を満たすチャットボットのモックを **30分以内で** 完成させる。
- ServiceNow のエージェントチャット上に四択問題を表示し、解答者は選択肢を選んで回答できること。
- 解答者は一問ごとに正解、不正解のフィードバックを得られること。
- 解答者の回答が不正解だった場合は、正答および問題の解説を表示すること。


## 用意するもの
- PDIインスタンス（バージョン : Vancouver）
　インスタンスの言語設定は英語ですが、クイズの問題、選択肢、解説文などは便宜上日本語で登録しています。
 
- 問題のサンプル
　今回は [ServiceNow Certified System Administrator 試験仕様書](https://nowlearning.servicenow.com/lxp/en/credentials/certified-system-administrator-mainline-exam-blueprint?id=kb_article_view&sysparm_article=KB0011587) の例題を使わせていただきました。

- 問題の解説文
　各問題を ChatGPT に投げ、生成した解説文を Word ファイルに貼り付けたものを用意しました。


## 作り方

### 1. 必要なプラグインのインストール （〜6:30）

- アプリケーションナビゲータから [All] > [System Definition] > [Plugins] に移動し、下記の３つのプラグインをインストールします。

  - Quiz Designer [com.glide.quiz_designer] ： クイズの登録に使用します。
  - Glide Virtual Agent [com.glide.cs.chatbot] ： Virtual Agent を有効にします。
  - ITSM Virtual Agent Conversations [sn_itsm_va] ： Virtual Agent からナレッジ記事（解説文）を参照するために使用します。

検索窓から各プラグインを検索して "Install" ボタンをクリック→ポップアップ画面が開かれるので 、[Install now] にチェックが入っていることを確認して "Install" ボタンをクリックします。

下のような画面になったら、 "Run in backgroud" ボタンをクリックして次に進めてください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/60b0d1d1-3fe8-7909-36fc-dde1f986cbb2.png" width=640 />

しばらくするとアプリケーションメニューの一覧に [Quizzes] が追加されるので、次の手順に進みます。


### 2. クイズの登録 （〜13:00)

- [All] > [Quizzes] > [Quiz Designer] を選択します。
- 別タブで Quiz Designer が開くので、クイズを登録していきます。
  - Name: ServiceNow Quiz
  - "Drag content, drop it here" にControls タブの Scale を必要な数だけドラッグ＆ドロップ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/99b6f24f-c6e3-3e67-2f13-ce82fd5e6347.png" width=800 />

- 黒い歯車をクリックして Properties ポップアップ画面を開き、問題文、選択肢、正答を設定します。
  - Name: Q1 など
  - Question: （問題文を入力）
  - Choices: （選択肢を入力）
  - Correct answer: （正答となる選択肢の✗をクリック→✔に変更）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/c1a0057d-422c-b49e-c4ba-a0ce38fd484b.png" width=800 />

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/f41ea5c9-5c0c-6a27-dcaa-3f901c87a798.png" width=640 />

- 問題の登録が終わったら、画面右上にカーソルを持っていき "Save and Publish" ボタンをクリックします。
- 現在開いているタブを閉じて、プラットフォームUIの画面に戻ります。

Quizzes のリスト画面に戻ると思いますので、一旦リストをリフレッシュします。
**作成した "ServiceNow Quiz" のレコードが表示されたら、レコードを右クリックしてSys IDをメモしておいてください**（手順の一番最後で使用します）。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/4ea890cd-345c-66d0-2482-771648af2a9e.png" width=800 />


### 3. 解説文の登録 (〜15:00)

不正解だった場合に表示する解説文をナレッジ記事として登録、公開します。
- [All] > [Knowledge] > [Articles] > [Import Articles] に移動します。
- 以下の手順で解説文を書いたWordファイルをアップロードします。
  - Knowledge Base : ナレッジベース "Knowledge" を選択
  - Import a Word File : Word ファイルを一括でドラッグ＆ドロップ or "Browse Files" ボタンからアップロード
- 以下のようにアップロードされたことを確認し、"Import" ボタンをクリックします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/c59cb723-5fd9-24e8-2862-0ea9d437a03c.png" width=800 />


インポートしたナレッジ記事はドラフト状態で保存されていますので、以下の手順で公開(Publish)します。

- [All] > [Knowledge] > [Articles] > [Unpublished] に移動し、インポート済みのナレッジ記事をクリックします。
  - デフォルトで Short Description にインポートしたWordファイル名が、Article body にファイルの内容が反映されていることを確認します。
  - 適宜体裁を整え、"Publish" ボタンをクリックして公開します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/e93843da-369a-989a-ed60-4eccb537b6cb.png" width=800/>

ナレッジ記事の公開が終わったら [All] > [Knowledge] > [Administration] > [Knowledge Bases] に移動し、**今回登録先に指定したナレッジベース "Knowledge" のSys IDをコピーしてメモしておきます**（後ほど Virtual Agent からナレッジ検索をするための設定で使用します）。


### 4. Virtual Agentの作成 (〜25:00)

#### 4-1．NLU を無効にする

デフォルトでは NLU (自然言語理解) の機能がエラーを吐きます。今回 NLU は使用しないので無効にしておきます。

- [All] > [Conversational Interfaces] > [Home] をクリックします。"CI Admin Experience" が別タブで開きます。
- 画面左上の [Settings] > [Virtual Agent] から [Natural Language Understanding (NLU)] のセクションを探します。Configure NLU が Status : on になっていますので、"View Settings" ボタンをクリックします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/cd5baf63-b792-b2b9-0f7c-fddf2255672a.png" width=800 />

- "Activate" トグルボタンを OFF にし、"Save" をクリックします。
- 現在開いているタブを閉じて、プラットフォームUI画面に戻ります。


#### 4-2. トピックブロック "Quiz" の作成

事前に登録したクイズを Virtual Agent から呼び出せるようにします。
Virtual Agent にはサーベイを実行するための "Survey"トピックブロックが OOTB で用意されているため、そちらをコピーしてクイズ用のトピックブロックを作成します。

- [All] > [Conversational Interfaces] > [Virtual Agent] > [Designer] に移動します。
- 検索窓に survey と入力→Surveyトピックブロックが表示されるので、クリックします。
- 右上の「︙」をクリックし、Duplicate → Save します。
  - Name: Quiz
- 新規作成したトピックブロック "Quiz" のプロパティ画面に遷移しますので、画面左上の "Flow" をクリックします。


次に、解答者の回答を格納するスクリプト変数「yourAnswer」を作成します。
- 画面左下 "Variables" の Script 横にある (+) をクリックします。
- 以下の項目を入力し、"Save" ボタンをクリックします。
  - Variable Name: yourAnswer
  - Default value: （空のまま）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/41a15687-36e7-f009-7606-efd58d98bf67.png" width=800 />

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/b5587f07-496a-86d4-28ab-fee6a3684e10.png" width=640 />

同様の手順を繰り返し、問題の正答を格納するスクリプト変数「correctAnswer」を作成します。
- 再び画面左下 "Variables" の Script 横にある (+) をクリックします。
- 以下の項目を入力し、"Save" ボタンをクリックします。
  - Variable Name: correctAnswer
  - Default value: （空のまま）


次に、フロー下部の "Set answers" スクリプトアクション以降に要素を追加していきます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/a8ba9867-b278-2b27-5bf0-67af0906396a.png" width=800 />

１．ユーザの回答および正答をスクリプト変数に代入します。
- Utilities > Script Action をドラッグ＆ドロップします。
  - Node name : Set answers to variables
  - Action expression : （以下を入力）
    ```JavaScript:Set answers to variables
    (function execute() {
        // 問題のSys IDをスクリプト変数から取得
        var questionSysId = vaVars.currentQuestionMetric;
        
        // ユーザの回答をスクリプト変数 yourAnswer にセット
        var surveyAnswers = JSON.parse(vaVars.surveyAnswers);
        vaVars.yourAnswer = surveyAnswers[questionSysId].displayValue;
        
        // 問題のSys IDをキーに正答を取得し、スクリプト変数 correctAnswer にセット
        vaVars.currentAnswer = "";
        var grQuestion = new GlideRecord("asmt_metric");
        if(grQuestion.get(questionSysId))
            vaVars.correctAnswer = grQuestion.correct_answer_choice.getDisplayValue();
    })()    
    ```

２．yourAnswer と correctAnswer が一致するか否かによって処理を分岐させます。
- Utilities > Decision をドラッグ＆ドロップします。
  - Node name : Is your answer correct?

３．正解の場合の分岐を設定します。
- Decision から伸びるパス（矢印）をクリックします。
  - Node name : Yes
  - Branch condition : Script
  - &lt;/&gt; Add Script :（以下を入力）
    ```JavaScript:Yes
    (function execute() {
        return (vaVars.correctAnswer === vaVars.yourAnswer);
    })()
    ```

４．正解である旨を表示します。
- Bot Response > Text をドラッグ＆ドロップします。
  - Node name: Correct
  - Response message: 正解です！


５．不正解の場合の分岐を設定します。
- Decision 下の(+)をクリックしてパスを追加します。
  - Node name : Incorrect
  - Branch condition : Script
  - &lt;/&gt; Add Script :（以下を入力）
    ```JavaScript:No
    (function execute() {
        return (vaVars.correctAnswer !== vaVars.yourAnswer);
    })()
    ```

６．不正解である旨とともに正しい解答を表示します。
- Bot Response > Text をドラッグ＆ドロップします。
  - Node name : Incorrect
  - Response message : 不正解です。正解は「 **（※Data Pill Pickerから "Script Variables > correctAnser"を挿入）** 」でした。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/fffbaa75-7055-0f49-1cdf-9fd033bb12f9.png" width=640 />


７．ナレッジ記事を検索して、問題の解説を表示します。
- Bot Response > Script をドラッグ＆ドロップします。
  - Node name : Display explanation
  - Script output type : Single-part
  - Script response message : （以下を入力）
    ```JavaScript:Display explanation
    (function execute() {
        // Script Include "VAGlobalContextualSearchUtil" を使って検索する
        var contextualSearch = new global.VAGlobalContextualSearchUtil();
        var response = contextualSearch.search(
            "2821675a5b30130070e4492c11f91a89",  // Search Contexts "ITSM VA Knowledge Search"
            vaVars.correctAnswer,
            "dfc19531bf2021003f07e2c1ac0739ab"  // KB "Knowledge"
        );

        var results = response.results;

        if (results.length > 0) {
            // 最もスコアの高いナレッジ1件を表示する
            var knowledgeId = results[0].id;
            knowledgeId = knowledgeId.split(":")[1];
    
            var grKnowledge = new GlideRecord("kb_knowledge");
            if (grKnowledge.get(knowledgeId)) {
                var singleOutMsg = new sn_cs.SinglePartOutMsg();
                singleOutMsg.setHtmlPart(grKnowledge.text);
                return singleOutMsg;
            }
        }
    })()
    ```

ITSM Virtual Agent Conversations プラグインをインストールすると、コンテキスト検索用スクリプトインクルード _VAGlobalContextualSearchUtil()_ が使用可能になるので、そちらを使ってナレッジの検索部分を実装します。

（ちなみに Contextual Search 用のトピックブロックも存在するのですが、やりたいことに対して機能過多に感じたため今回は不採用としました）

search() メソッドの引数は「使用するSearch ContextのSys ID」、「検索ワード」「使用するKnowledge BaseのSys ID」の3つです。
- Search Context は [Contextual Search] > [Search Contexts] から "ITSM VA Knowledge Search" のSys IDをコピーして指定しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/64ba98ce-1412-571e-683a-16f632960050.png" width=800 />

- 検索ワードは問題の正答(スクリプト変数の "correctAnswer") を指定します。
- Knowledge BaseのSys IDは「3. 解説文の登録」でコピーしたものを指定します。

８．"Display explanation" から伸びる矢印を、上部の "Set next question" ブロックに繋ぎます。

以上の設定が終わったら、フローを Save → Publish します。


#### 4-3. トピック "ServiceNow Quiz" の作成

エージェントチャットから呼び出すトピックを新規作成し、その内部で先ほど作成した "Quiz" トピックブロックを呼び出すようにします。

- [All] > [Conversational Interfaces] > [Virtual Agent] > [Designer] に移動します。
- (+) Create をクリックします。
- Utilities > Topic Block を [Start] と [End] の間のパスにドラッグ＆ドロップします。
　- Topic block : 先ほど作成した "Quiz" を選択
　- Node name : Quiz
　- surveyId (String): 「2. クイズの登録」で保存した "ServiceNow Quiz" のSys ID を入力

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/112ef66b-701d-ca68-bce3-04b109da6bf6.png" width=800 />

設定が終わったら Testボタンで問題なく動作することを確認し、Save → Publish します。


### 5. 動作確認 (〜30:00)

ユーザーメニューの [Impersonate user] から ITIL User に切り替え、Service Portal (https://(インスタンスのURL)/sp) にアクセスします。

画面右下の吹き出しアイコンをクリックして、[Show me everything] > [ServiceNow Quiz] を選択します。

問題と選択肢が表示されること、回答を選ぶと正解／不正解が表示されること、また不正解の場合は解説が表示されることが確認できれば完成です！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632778/7b4f6aa5-f825-6416-2a4f-1bb39776f17e.png" width=480 />


## まとめ

- Quiz Designer を使うことにより、直感的に問題、選択肢、正答のセットを定義することができました。
- ナレッジのインポート機能を使うことにより、Word ファイルから複数のナレッジ記事を一括登録することができました。
- OOTB のスクリプトインクルードやトピックブロックを再利用することにより、Virtual Agent の開発コストを削減することができました。
- コンテキスト検索用のスクリプトインクルードを利用することにより、[検索用語の頻度・順序・重みから算出されるスコア](https://docs.servicenow.com/ja-JP/bundle/vancouver-platform-administration/page/administer/search-administration/concept/c_DocumentScoring.html) を元にドキュメントを検索する機能を実装することができました。

ちなみにこの後 MyGPTs でチャットボットを試作してみた結果、あまりの手軽さに膝から崩れ落ちる思いに襲われたのでした。