Talk APIと形態素解析で、賢くないAIチャットbotを作ってみる🐶

この度は会社のQiitaアドベントカレンダーに参加させて頂くことになりました。

本業はディレクターとして働きながら、趣味でWebアプリやツールをQiitaなどなど参考にしながら作成し、自分の作業を効率化するなどしています。
　

増えてきた自作アプリを管理するため、キーワードを含む指示を出すとツールのURLを出してくれたり、自作ツールから結果を出力してくれたりするチャットbotを作っていたのですが、フリーテキストで話かけると「すみません よくわかりません」状態になることがよくあります。

類義語を解析してトリガーの当たり判定を大きくする対応などしたら使いやすくなると思いますが、それよりもトリガーが起動しなかった際に「すみません よくわかりません」では寂しいと思いました。

そこで、今回はチャットボットが簡単な会話に対応する方法を考えてみます。

## やりたいこと
* チャットbotのトリガーが起動しなかった時に、無料で使えて制限が小さい方法で、お茶を濁すための自動返信機能を作りたい
* キャラクター性を表現したい。愛されてほしいので。
* 「すみません よくわかりません」だけ回避できればOK

## 実践意図
* 会話することを主な目的としない（ツールとしての側面が強い）Botが必要となる機会は多く、昨今のGPT3などを活用する必要はないと考えたため、低精度モデル使用時の実際の使用感を確かめたかった
* 精度が高いモデルがない場合、賢くなさそうなキャラクター性を持たせることでユーザーの受容度を上げるアプローチができると思った

## 今回メインで使うAPI

### Talk API
無料で使えるチャットAPI。返信精度は正直低く、質問の答えをはぐらかす返答ばかりするが、ボットへの問いかけがイレギュラーパターンの場合だけ使う想定なのでむしろちょうど良いと思い採用。

:::note warn
商用利用不可なので使い方によっては注意
:::
[Talk API](https://a3rt.recruit.co.jp/)

### goo形態素解析API
文章を送ると品詞分解して返してくれる無料のAPI。形態素分析と言えばMecabが人気ですが、サーバーサイドで動かすのは手間。botに実装したいので今回はこちら。

[goo形態素解析API](https://labs.goo.ne.jp/api/jp/morphological-analysis/)

## Talk APIで問いかけへの返信を取得してみる
```js:Google Apps Script.js
function requestTalkApi(text) {
  //A3RTのTalkAPIにリクエストするテキストを定義する
  //let text = "1+1は？";
  //A3RTのTalkAPIのAPIキーを定義する※この部分を自分のAPIキーに書き換え
  let apiKey = "********************************";
  //Talk APIのリクエストURLを設定する
  let apiURL = "https://api.a3rt.recruit.co.jp/talk/v1/smalltalk";
  //APIのリクエストでPOSTデータするパラメーターを設定する
  let payload = {
    'apikey' : apiKey, 
    'query' : text
    };
  // HTTP POSTで前述で設定したパラメーターをオプションで設定する
  let options = {
  'method': 'post',
  'payload': payload
  };
  //APIにリクエストし、 返却されたデータをテキスト化して変数に格納する
  let res = UrlFetchApp.fetch(apiURL, options).getContentText();
  //JSONデータをparseメソッドでオブジェクトに変換し、結果をログ出力 
  let json = JSON.parse(res);
  let reply = json["results"][0]["reply"]
  let perplexity = json["results"][0]["perplexity"]
  let output = [reply,perplexity]
  console.log(json);

  return(output)
}
```
[こちらの記事を参考](https://auto-worker.com/blog/?p=5408)にしています。

Talk APIに適当なテキストを送ってあげると返信が返ってくるので、適当にいくつか質問して回答をメモしてみます。

| 問いかけ | TalkAPIの返答 |
|:-:|:-:|
| カレーとラーメンどっちが好き？  | あなたはどうですか?  |
| 世界一高い山は？  |  あなたはよくするんですか? |
|  今何食べたい？ |  食べたくないです |

問いかけに「どちら」が入っていると「どちらも好き」「あなたはどう？」といった回答になりやすいです。
また、固有名詞を使用した答えを返さないのも特徴かなと思います。

会話用のAIとしてはあまり実用的ではないと思いました。

（世界一高い山は？には動詞が含まれてないので、動詞を聞くレスポンスが返ってきているのが結構な残念ポイントです。）

## goo形態素解析APIで文章を品詞分解する
```js:Google Apps Script.js
function textAnalysis(sentence) {
  //形態素解析する文章を変数に格納
  //let sentence = "雨が降るでしょう";
  //goo形態素解析APIのリクエストURLとappIdを設定(***部分にIDセット)
  let apiUrl = "https://labs.goo.ne.jp/api/morph";
  let appId = "***************************************************";
  //goo形態素解析APIにパラメータをセットし、HTTP POSTするためのoptionsを設定
  let payload = {
    'app_id' : appId,
    'sentence' : sentence
  };
  let options = {
    'method' : 'post',
    'payload' : payload
  };
  //goo形態素解析APIにHTTP POSTでリクエストし、JSONの結果をパース
  let response = UrlFetchApp.fetch(apiUrl,options).getContentText();
  let json = JSON.parse(response);
  //goo形態素解析APIで処理した結果をログ出力する
  //console.log(json['word_list']);
  let analyzed = json['word_list']
  return(analyzed)
}
```
[こちらの記事を参考](https://auto-worker.com/blog/?p=5408)にしています。

Talk APIから取得した返答レスポンスを形態素解析していきます。

例えば「食べたくないです」の語尾「です」を「ござる」に置換すれば「食べたくないでござる」となってキャラクター性を表現できますが、「海ですら狭い」が「海ござるら狭い」となってしまい意味がわからなくなってしまいます。

そうならないため、語末の「です」であるか判定して置換対象に指定するための判定に使用します。

## 形態素分析データを使って話し方を調整する
```js:Google Apps Script.js
function mainFunction() {
  const sheetID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  const sheet = SpreadsheetApp.openById(sheetID).getSheetByName('ChatBot');
  let lastRow = sheet.getLastRow();
  let lastCol = sheet.getLastColumn();

  let input = sheet.getRange(lastRow,2,1,lastCol-1).getValues().flat();
  let question = input[2];

  //語調変換
  let analyzed = textAnalysis(question).flat(1);
  console.log(analyzed);

  for(let array of analyzed){
    switch (array[1]){
        case "名刺":
        var keitaiso = array[0].replace("私","我輩");
        break;

        case "句点":
        var keitaiso = array[0].replace(".","。");
        break;

        case "動詞接尾辞":
        var keitaiso = array[0].replace("ます","ますワフ").replace("ません","ませぬワフ").replace("ました","ますワフ");
        var gobi = gobi+1;
        break;

        case "形容詞接尾辞":
        var keitaiso = array[0].replace("です","ワフ");
        break;

        case "判定詞":
        var keitaiso = array[0].replace("です","でワフ").replace("でした","だったワフ");
        var gobi = gobi+1;
        break;

        default:
        var keitaiso = array[0];
    }

    answer.push(keitaiso);
  }

  if (answer.slice(-1)[0] == "。"||answer.slice(-1)[0] == "."){
      answer.pop();
    }

    if (answer.join("").indexOf("ワフ") == -1){
      answer.push("ワフ。");
    }

  answer = answer.join("");
  result = [output,,answer];

  sheet.getRange(lastRow,5).setValue(result);
  sheet.getRange(lastRow,6).setValue(answer);
  
  answer = sheet.getRange(lastRow,6).getValue();

  var jsonData = {
    "response" : answer
    }

  var payload = JSON.stringify(jsonData);

  var options = {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : payload
    }

  UrlFetchApp.fetch("https://hooks.slack.com/workflows/*********/*********/******************/******************", options);
  console.log(options)
  }
}
```
形態素解析の結果を参考に、置換条件を設定していきます。
今回は語尾が「ワフ」で一人称が「吾輩」の犬botにしてみますが、ここを書き換えれば好きなようにキャラ性を出せます。

APIの返答のクセに合わせてキャラ付けするのがおすすめです。
犬なのに人の言葉で返事ができてえらいね。

## 使ってみる
Slackワークフロー経由で犬に話かけられるようにしたので、実際に話かけてみます。

ワークフロー経由でSpreadSheetに書き込み、シートに変更が加えられたことをトリガーに書き込まれた内容をAPIに投げ、レスポンスを成形してSlackのwebhookワークフローに投げ返す形にしてみました。
![犬のbot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957099/23b0d7c5-2656-bd46-c73d-cb8602098fff.jpeg)
![犬と会話.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957099/49a5cbe8-e450-fb3a-4bf7-348fac88cbf9.jpeg)

後日談
個人timesチャンネルに犬を招き入れたら、削除できなくて邪魔でした。