Azure OpenAI ChatGPTを使って社内ナレッジをチャット形式で検索する

企業が自社のデータベースに蓄積した固有情報を検索する場合、そのままChatGPTに質問を投げても回答を得ることはできません。これはモデル自体が固有の情報を知らないため、うまく回答を返すことができないという課題があるためです。
現在これを解決するため、各社でプラグインの開発などが行われている状況です。

本記事では蓄積されたFAQ情報を用いて、チャット形式で情報を引き出すような仕組みを構築します。

概要

ChatGPTの登場で、一般的な質問に対してAIが自然な回答を生成することができるようになりました。
社内ナレッジ検索を行うためには、以下のような課題をクリアする必要がありますが、本記事ではこれら3点の課題を解決する方法を提示します。

  1. ChatGPT (GPT3.5/4)をそのまま使うだけでは社内ナレッジを検索できない
  2. 出力のもとになったデータソースが示されてほしい
  3. ユーザーの権限ごとに出力するデータを制限したい

また2023年5月のMS Buildにおいて、MicrosoftはChatGPTと共通のプラグイン規格を採用することを発表しました。

blogs.bing.com

これによってChatGPTを使用した外部連携の仕組みが、Microsoft製品上で利用できるようになります。
本記事は社内ナレッジの検索というテーマを用い、独自にプラグインの仕組みを実装する方法について、具体的な内容まで踏み込んで解説します。

実装方法

アーキテクチャ

Microsoftが提供しているアーキテクチャを参考にしました。
techcommunity.microsoft.com

今回はJupyter Notebookを使用して、同じアーキテクチャからOpenAI APIとCognitive Searchを使用します。

実装方法の概要

以下の手順でデータを整形して検索可能な形としたうえで、ユーザーの質問に回答します。

前準備
  1. FAQナレッジを用意する
  2. 1番で用意したナレッジのAnswer部分を、GPTを使用して文章要約する
  3. 1番で用意したQuestionの部分と、2番で用意した要約済みAnswerを使って、Cognitive Searchでインデックスを作成する
ナレッジ検索ChatGPT
  1. ユーザーが質問を入力する
  2. ユーザーの質問から、GPTを使用して重要なキーワードをCognitive Searchで質問可能な形として抽出する
  3. Cognitive Searchを使用して、要約済みAnswerの候補を抽出する
  4. 3番で抽出した検索候補を整形して、ChatGPTに回答を生成させる

デモ

使用するデータセット

子育てオープンデータ協議会が公開している、「子育てAIチャットボット」普及のための「FAQデータセット」を使用します。
つまり今回は子育てに関する質問をChatGPTに投げると適切な回答が返ってくるような仕組みを構築します。
linecorp.com

過去の生成結果(ChatGPT、Fine Tuningモデル)

以下の記事では同じデータセットを使用して、単にChatGPTに質問を行った場合と、同データセットでFine Tuningしたモデルに質問を行った結果が示されています。
非常にもっともらしく回答するものの、正しい結果を回答させることは難しいと結論付けています。 blog.jbs.co.jp

処理手順

入力する文章

以下の文章を対象に回答を生成します。

母子手帳を受け取りたいのですが、手続きを教えてください。
前準備

まずはデータセットの中身を成形して、Azure OpenAI ChatGPTのプロンプトで入力可能な形に整形します。
今回必要なのはPrompt(質問文)とCompletion(回答例)のセットのみなので、dataset_.xlsxの中で不要なカラムはあらかじめ取り除いておきます。

Completion(回答例)の長さは一定ではなく、またマスクされたURLなどの不要な文字列が残っています。
要約した文章を後ほどChatGPTのプロンプトに使用したいので、今回は200文字以内として要約するように要求します。

以下、使用したPythonのソースコードです。

chat="次の文章を200字以内で要約して。要約文以外は出力しないで。 -> "+df_convert.iloc[index]['completion']
response = openai.ChatCompletion.create(
    engine="",
    messages=[
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": chat},
    ],
    max_tokens=800,
    temperature=0
)
df_convert.loc[index, 'comp_summary'] = response['choices'][0]['message']['content']

200文字というのはGPT-3.5の出力結果を見つつ適当に決めた数字なので、ケースごとに適切な文字数に調整する必要があります。

Azure Cognitive Searchを使用して、元々の質問文と要約した回答例からインデックスを作成します。
これにより単純なキーワード検索によって、ChatGPTで要約した回答から候補を得ることができるようになります。

ナレッジ検索ChatGPT
質問文の入力

実際のナレッジ検索を実施する際の流れを想定します。
ユーザーは以下の通り、AIに対して自然言語で質問文を入力します。

質問文→キーワード抽出(ChatGPT)

ユーザーの質問文に対して回答を生成しようとしても、ChatGPTのモデルが回答を知らなければ適切な答えを返すことができません。

そこでChatGPTにCognitive Searchのナレッジを検索させます。
Cognitive Searchで検索するために、まずは質問文を単語ベースに変換します。

以下が使用したPythonのソースコードです。
これによって質問文の中から重要と思われるキーワードを抽出するように要求します。
engineにAzure OpenAIにデプロイしたモデル名、chatにユーザの入力した文章、word_numに抽出するキーワード数を入力します。

def extract_keyword_gpt(engine,chat,word_num=5):
    system_prompt = "文章からキーワード単語"+str(word_num)+"つ以内で抽出して箇条書きすること。出力する単語は名詞化する。箇条書き以外の返信は必要なし。形式は次の通り。・回答A\n・回答B\n・回答C"
    response = openai.ChatCompletion.create(
      engine=engine,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": "次の文章から重要単語を"+str(word_num)+"つ以内で抽出しなさい:"+chat},
        ],
        max_tokens=1000,
        temperature=0
    )
    return response['choices'][0]['message']['content']

今回は以下の4件のキーワードが出力されました。

  • 母子手帳
  • 受け取り
  • 手続き
  • 教えてください

4番目は検索にあまり使わないようなキーワードですが、1~3番目は重要単語をしっかりととらえています。
ユーザーの質問が複雑かつ長くなるケースも想定されるため、もともとの質問文の長さによって上限単語数を変更してもよいかもしれません。

Cognitive Searchに対して、作成したインデックスに対して検索を実施します。
Pythonに対して以下のライブラリを入れておく必要があります。

pip install azure-search-documents==11.3.0

以下、使用したPythonのソースコードの抜粋です。
Cognitive SearchのURL、APIキー、インデックス名を用意する必要があります。
関数に対して検索ワードのリストと検索数を要求すると、上位n件の結果を得ることができます。

from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient

service_url = ""
api_key     = ""
index_name  = "" 

# 検索クライアントを作成する
credential = AzureKeyCredential(api_key)
client = SearchClient(endpoint=service_url, index_name=index_name, credential=credential)

def reqest_cognitive_search(words,view_num):
    search_text = ' '.join(words)
    search_options = {
        'top': view_num,
        'select': 'comp_summary',
    }
    search_results = client.search(search_text, **search_options)
    return search_results

今回のケースでは使用するキーワードは先ほど提示した4件です。
検索結果としては、暫定で上位3件を取得するように設定しました。
結果としては以下の図のようになりました。白い太字で書かれている2件目のデータが、もともとのFAQデータセットの質問に紐づく回答例で、ユーザーの質問に対して答えられるような検索ができていることが分かります。

回答の生成(ChatGPT)

ユーザーの質問に対して適切と思われる回答が用意できたので、この情報を使ってチャット形式での回答を生成します。

以下が使用したPythonのソースコードです。
chatにユーザーの入力した文章、resultsにCognitive Searchで得られた結果をそのまま入力します。

def answer_result_gpt(engine, chat, results):
    system_prompt = "あなたは質問に回答するAIアシスタントです。"
    results_summary = [result['comp_summary'] for result in results]
    result_text = '\n・'.join(results_summary)
    
    response = openai.ChatCompletion.create(
      engine=engine,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": chat+ "\n検索した結果、次の回答が見つかっています。不要な回答も含まれている可能性があります。この中から回答を生成してください。"+result_text},
        ],
        max_tokens=2000,
        temperature=0
    )
    return response['choices'][0]['message']['content']

ポイントはChatGPTのモデル自体に蓄積された知識から回答を求めるのではなく、知識の検索はAzure Cognitive Searchに任せて、それらから取捨選択・情報整理するようなタスクを任せている点です。

プロンプトにはユーザーの質問に付随する形で、システム側で検索結果を追記しています。ただしこの方法では、ユーザーの入力する文言によって不自然な文章になってしまう可能性があるので、この部分はシステムプロンプトに入力した方がよいかもしれません。(Microsoftの提供する資料でもシステムプロンプトに入力するようになっていました)

結果としては以下の通りです。
最も重要である「妊娠届の提出」について一番上で言及することができています。
一方で母子手帳の交付申請書の提出など、検索結果にも出ていなかった手順を追記していました。
ほかにも求めていない結果を出してくるケースがいくつか見られたため、プロンプトの調整によって出力を制限する必要があるかもしれません。

母子手帳を受け取るためには、以下の手続きが必要です。

1. 妊娠届を提出する
妊娠届は、妊娠5か月以内に市区町村役場に提出する必要があります。提出後、母子手帳の交付申請書がもらえます。

2. 母子手帳の交付申請書を提出する
交付申請書に必要事項を記入し、医療機関で受け取った健康診査結果票や、妊娠届を提出します。

3. 母子手帳を受け取る
交付申請書を提出した後、数日から1週間程度で母子手帳が発行されます。発行された母子手帳は、市区町村役場で受け取ることができます。

レスポンスを作成する

最後にChatGPTの出してきた回答をそのままユーザーに返すことができます。
回答に対して以下のような情報を追記したい場合、この段階でシステム側で処理を行います。

  • 回答の生成に使用されたデータソース示す
    (Azure Cognitive Searchの検索結果から情報元を取得)

その他回答結果

今回示して質疑応答を行った場合の、その他のケースについても示します。
緑・青色の太字が実際の回答例に対するAI回答の比較です。太字になっていない箇所はAIが言及できていない部分になります。

左側の質問では「特定の県以外では受診票の利用ができない」としていますが、回答は「限定的な範囲で利用可能、自治体によって異なる場合がある」としており利用できるという寄りの表現になっています。また回答例の後半部分の助成金については触れられていません。
右側の質問では8種類の必要書類等が示されていますが、AIはこれらを全て適切に回答できていました。また代理で申請する場合についても正しく回答ができています。

他の2件のケースでも同様に回答例と同じく、適切な内容を回答の最上部に生成できていることが確認できました。

全体的な課題として、後半部分に余分な情報が付随しているように見える点があげられます。
このあたりは回答の出力の際に文字数を制限したり、そもそもの文書要約の精度を高めるなどして対応する必要があるでしょう。

文書要約の精度について

Cognitive Searchにインデックスを登録する前段階で、回答例を文書要約した結果を登録しました。
この段階で、例えば重要単語を取り落としてしまうなどといった要因で精度が低くなった場合はうまく検索を行うことができないと考えられます。
さらに要約結果はプロンプトに使用するため、単にキーワードが含まれるかどうかだけではなく、文章自体の精度を高く保つべきと考えます。

今回、以下のような回答例がありました。

窓口で妊娠届をご記入いただき、母子手帳をお渡しします。 
住民票の世帯が別の方が代理で窓口に来られる場合は、委任状が必要になります。 
▼詳しくはこちら (自治体HP内関連ページのURL)

GPT3.5-Turboでこれを要約した結果は以下の通りです。

窓口で妊娠届を提出し、母子手帳を受け取るには、本人以外が代理で来る場合は委任状が必要です。詳細は自治体HPを参照してください。

もともとの回答例の特徴をとらえており、かつ「▼詳しくはこちら」といったような部分についても文章化できています。
一方で日本語としては少しおかしい文章になっているように見えます。
このようなケースがいくつか見られたため、この点についても改善方法を検討する余地があるように思いました。 今回、ChatGPTのモデルはすべてGPT3.5-Turboを使用しましたが、GPT-4 (8kモデル)を使用して結果を生成してみました。

窓口で妊娠届を記入し、母子手帳を受け取る。代理で来る場合は委任状が必要。詳細は自治体HPで確認。

GPT-3.5を使用した際よりもしっかりと要約ができていました。
GPT-4はGPT-3.5に比べて費用がかかりますが、前準備で回答例を要約するのは初回のみであり、新規にデータを追加する以外で継続的に発生するコストはかかりません。この処理においては、精度の高い結果を生成するためにGPT-4の使用を検討する余地があるでしょう。

社内ナレッジ検索にまつわる要求事項

Azure Cognitive Searchの出力を使用しているため、以下の実装が可能となります。

  • 出力のもとになったデータソースを表示できる
  • AzureADの情報を使用して、回答例(要約)の出力結果を制限できる

解説した通り、回答の生成のために途中でCognitive Searchの検索を行っています。
そのためCognitive Searchと紐づくデータに各FAQのデータソース情報も含めておけば、ChatGPTの出力結果に付随する形として、単なるプログラムの処理でそれらを追記することができます。

またユーザーごとに検索可能な結果を振り分ける方法ですが、Azure ADの情報を使って制約をかけることができるようになっています。
詳細は以下の記事をご確認ください。
learn.microsoft.com

おわりに

本記事ではChatGPTとAzure Cognitive Searchを使用して社内ナレッジを検索する方法を示しました。
またCognitive Serviceに読み込ませるデータソースに各FAQのデータソース情報や権限情報などを加えることによって、要求事項を満たすことができることを提示しました。

プロンプトや検索結果数、要約文字数などに工夫すべき点があり、またMS Buildでプレビュー機能が公開されたベクトルサーチなどを使用すれば、より優れた検索結果を得ることができる可能性があります。
今後も出力結果をよりよくするための方法を探っていきます。

執筆担当者プロフィール
西野 佑基

西野 佑基(日本ビジネスシステムズ株式会社)

機械学習系ソリューション開発、業務Webアプリ開発、開発環境自動化などを担当。

担当記事一覧