検索処理部分との連携モジュールについて
前回の記事と異なる部分は以下の通りです。
- 応答の判断部分
- Elasticsearchによる検索処理部分
- Slackへの投稿部分
応答の判断部分
応答は質問かそうでないか判断して返すようにしています。判断基準は「?」(全角)もしくは「?」(半角)を含んでいるかどうか、です。質問の場合はElasticsearchによる検索処理を行います。
if len(self.data) >= 1 and "text" in self.data[0]: input_text = self.data[0]["text"] if "search_bot:" in input_text: if "?" in input_text or "?" in input_text: word = self.__search_word(input_text) self.__slack_call(word) else: word = self.message self.__slack_call(word)
検索処理部分
- 改行や判定のためのトリガーワードなど、検索に不要な単語を削除しています
- 検索のモジュールには1.の処理を行った後の、不要な単語を削除した文章を渡しています
- 検索モジュールから返却された結果があれば必要な部分のみを抽出し、返却されなければ「No match」という単語を返しています
注
環境によってはソースコード中に「¥」記号が表示される場合がありますが、実際にはバックスラッシュです。
replace_input = re.sub("search_bot:|\?|\?", "", input_text.strip()) self.elastic_search.search_data(replace_input) if len(self.elastic_search.search_result) > 0: hyp_batch = self.elastic_search.search_result[0] word = hyp_batch["title"] + "\n" + hyp_batch["abstract"] + "\n" + hyp_batch["url"] else: word = "No match" return word
Slackへの投稿部分
前回の記事と同様の内容なので詳細の説明は除きますが、wordに検索された単語が入っています。
print(self.slack_channel.api_call("chat.postMessage", username=self.user_name, channel=self.chan, text=word, \ icon_url=self.icon_url))
検索モジュールについて
- 初期設定
- 検索処理
- 検索クエリ設定
初期設定では以下のように「--link」で連携したコンテナの名前を指定します。この場合はElasticsearchのポート番号である9200番を指定しています。
self.es = Elasticsearch(['elasticsearch_dialogue'], port=9200,) self.doc = {} self.elastic_index = "_all" self.search_result = []
検索結果を初期化して検索クエリを設定し、そのクエリを使用して検索後、ヒットした結果を配列に追加しています。
self.search_result = [] self.setting_search_query(search_key_word) self.res = self.es.search(index=self.elastic_index, body=self.query) for hit in self.res['hits']['hits']: self.search_result.append(hit["_source"])
その下で検索用のクエリを作成しています。検索スコア設定の際に「boost」で値の重み付けを大きくしています。検索スコアの設定については初回の記事をご覧ください。今回のクエリの場合、「title」の方が検索における重要性を増すように作っています。
self.query = { "query": { "bool": { "should": [ { "match": { "title": { "query": "\"" + search_key_word + "\"", "boost": 10 } } }, { "match": { "abstract": "\"" + search_key_word + "\"" } } ] } } }
では、動作確認を行います。前回の記事で設定した、Slack上でBotが反応するチャネルに対して「捕鯨?」と質問文を入力してみましょう。先ほどのスクリプトでSlackのコンテナに入っているため、以下のコマンドを打つと質問応答に対応するBotが立ち上がります。
python app.py
「捕鯨」と調べたかったのですが結果として「反捕鯨」が出ています。なぜこのような結果になったのかというと、「捕鯨」と登録されたデータの「abstract」に「捕鯨」に関係している要素が入っていないためです。そこで、「title」だけで検索してみます。「get_answer.py」の中にコメントアウトしている部分があるので、そのコメントアウトを外し、先ほどの検索クエリはコメントアウトしてください。
self.query = { "query": { "bool": { "should": [ { "match": { "title": { "query": "\"" + search_key_word + "\"" } } }] } } }
再度、以下のコマンドを実行してください。
python app.py
この場合、「title」のみにフォーカスして「捕鯨」を検索しているため、欲しい結果を取得できます。本文も考慮して検索するほうが検索の質は向上しそうですが、データの品質によってはそうでない場合もあるため、今回のように確認を行いながら検索することが重要になってきます。
このように検索クエリを変えるだけでも多様な検索を行うことができます。さらに、この変更は検索用のコンテナに対する影響がないので、ここでも疎結合な環境による効果を体感できます。
なお、現状の実装では末尾ではなく文字列内に半角の「?」と全角の「?」が含まれた場合は反応するようになっているため、注意が必要です。
最後に
これでUI部分と検索部分を切り離した構成の、Elasticsearchと対話Botによる対話型の検索システムが作成できました。今回の構成の場合、単純な応答からルールを作成した応答への変更などは既存の検索部分を修正せずに行うことができます。また、UI部分もSlackからFacebook、LINEなどの多様なインターフェースに変更可能で、今回のようにDockerを使用した構成の魅力になっています。本記事の内容を応用していただけると幸いです。