HRテックのSaaS「CYDAS PEOPLE」のチャット機能にChatGPTを活用

吉田真吾氏:どうもみなさんこんばんは。今日私からは、本番リリースに向かってLLMアプリケーションを構築している話をしようかなと思います。

第0回(ChatGPT Meetup Tokyo #0)でもお話をしたのですが、私は現在、RAG(Retrieval-Augmented Generation)フローを使ったLLMアプリケーションを作っています。

サイダス社は「CYDAS PEOPLE」というタレントマネジメントのSaaSサービスを提供していて、その中の1機能として、人事に関するFAQを掲示する人事FAQという掲示板機能を公開しています。それに加えて、人事FAQに載っていない質問を直接人事とやり取りできる機能もあります。

それらの履歴データをベクターデータにしておいて、従業員からの質問に自動でチャットボットで答える機能を作っています。

(スライドを示して)このような絵で表現できますが、いろいろな人事の業務分類ごとにFAQが書いてあり、このコンテンツをベクターデータ化して、「ChatGPT」に回答させています。

例えば、これはサイダス社の中のFAQデータですが、「ITパスポートの受験をしようと思います」と言うと、ITパスポート資格の補助に関して答えてくれて、実際の申請方法をきちんとガイドしてくれています。さらにその後に、関連質問を3つ出すように実装をしています。

これ自体は「LangChain」ベースで作っています。ドキュメントのベクターデータ取得はLlamaIndexなどを使うと便利だと思いますが、僕らはベクターデータもLangChainの機能を使って実現しており、それに加えてChatGPT、つまりOpenAIのAPIと、チャット履歴部分を「Momento」に持たせるという作りにしていました。

デモとして動いているのはこのアーキテクチャのバージョンなのですが、ここから、いろいろな部分をきちんと本番リリースできるようにしないといけないと考えました。

本番レベルのLLMアプリケーションを作るのはとても難しい

(スライドを示して)みなさん、読んだことがあるブログだと思います。実は今日のタイトルは、このHuyenさん(Chip Huyen氏)のブログの中の一文を引用して付けています。

本番用のLLMアプリケーションを作る。Huyenさんのブログには、LLMでクールなアプリケーションを作るのは簡単だが、本番レベルのものを作るのは非常に難しいよねということが書かれています。

まず、LLMの基本的な性質として、入力したプロンプトに対する答えが常に一定とは限らないですね。確率論的に生成していくものなので、当然一定のアウトプットではないことにより、UXが不整合を起こしてしまいます。

昨日までは十分なクオリティで回答してくれていたのに、今日になったら急にきちんと回答しなくなってしまったというように、UXに不整合が起きてしまう。これはもともとの性質としてしょうがないことですよねという課題です。

解決策として考えられるものとしては、ChatGPT(OpenAI API)であれば、temperature(温度)のパラメーターを低くして、できる限り手堅く回答を返してもらう。

あるいは、プロンプトエンジニアリングを駆使して、どういう振る舞いをして、どういうアウトプットをしなさいというのをきっちりと指示することによって、意図どおりの出力をさせるというようなことは、課題とその対策として書かれています。

それ以外にも、レイテンシーなどは気になると思いますが、モデルによってレイテンシーのばらつきもありますし、そもそもGPT-4は顕著に応答時間がかかるので、アプリケーションの中で使う時に、UXとしてその遅さをユーザーがどう感じるか? というのはあります。

実際に僕らはGPT-4をデモで使っている中で、ユーザーから「もうちょっと速くならないの?」というフィードバックをもらっていて、対策を進めています。

1つの選択肢としてはモデルを切り替えてGPT-3.5-Turboを利用するという選択肢もありますし、ローカルLLMを使う方針に振り切るという考え方もあると思います。GPT-3.5-Turboは、2日前(※登壇当時)に新しいバージョン「gpt-3.5-turbo-0613」がデプロイされて、応答速度・応答品質ともに評判良さそうなので、これに切り替えるのは有効な選択肢だと思います。

ただし、レイテンシーは日々変わっていくものなので、結局自分たちのアプリがどのぐらいで応答を返せたかというのを、常に監視しておく必要があるのかなと思っています。

このブログの話ばかりしていると、ブログの良さを伝えて終わってしまうので、詳しくはURLを参考にしていただければなと思います。

OpenAIのAPIとAzure OpenAIの違いとは?

デモ段階では、AWS Fargate上でLangChainを動かしていますというお話をしましたが、本番化するために考えなければいけないことがありました。

本番運用にあたっては、うちのSaaSの中に入っているお客さんのFAQのデータ、あるいは、人事と従業員がやり取りをした問い合わせの履歴のベクターデータを取得して、それをChatGPT側の回答の参考とするためのデータとして入れることになるので、入力データがサードパーティーの事業者でどう扱われるかをきちんと気にしてあげる必要があります。

そこで、ほぼOpenAIそのままAzure上で利用できるAzure OpenAIを検証しています。 「Azure Cognitive Service」の一部として、OpenAIサービスが展開されているかたちです。

よって、いわゆるコンプライアンスへの対応が利用するAzureのサービスごとに適用される規約に記載されているレベルで実現できます。データセンターの中の運用の監査にも入って、きちんとSOC 2 Type 2レポートが取得できるものもあるので、OpenAI社のコンプライアンス体制はまだ不明な点も多いけれども、マイクロソフトならこれらを取り寄せて明確に信頼性を担保できるかなと判断できますよね。

プラットフォームを載せ替えた

じゃあ、Azure OpenAIベースで作り変えてしまおうと決断して、今本番に向かって、プラットフォームの載せ替えを行っています。

結果的にどういう作りをしているかというと、Web App上にLangChainのアプリケーションを載せて、「Cognitive Search」を検索に利用し、「Cosmos DB」と「Blob Storage」をデータソースにし、「Azure OpenAI」のgpt-3.5-turboモデルで回答を生成しています。

Cosmos DB、Cognitive Searchの両方を使って検証

Azure OpenAIを選定した理由は、先述したとおりコンプライアンス対応です。また、検索に利用しているCognitive Searchは、Azureで対応している、4種類のベクトル検索対応データストアから選定しました。

1つがCosmos DBの「MongoDB vCore」で、もっとも新しいもの。2つ目が「Cognitive Search」、これも新しいです。その下に「Redis Enterprise」に含まれる、「RediSearch」をAzure上で使うというものと、4つ目が「Data Explorer」というものです。

まず選定対象を上の2つに絞りました。Cosmos DBを使うのか、Cognitive Searchを使うのか。そして、両方とも実際にデモを作って検証してみました。

(スライドの)左側がMongoDB vCoreのCosmos DBを使った検索で、右側がCognitive Searchの裏側にCosmos DBとBlob Storageのデータソースを置くパターンです。実際にデモを構築して検証をやってみたら、明確に、自分たちの要件に合う、合わないがわかりました。

リクエストはそれぞれこんな感じになるのですが、結果をお見せした後にこのあたりの解説をしたいと思います。

Azure Cosmos DB for MongoDB vCoreでは、検索する時に何個データを取るか指定する必須パラメーターがあります。(スライドを示して)ここの「k」というところです。

ここで「もっとも関連性の高いデータを1個取ってくるように」と指示した結果、必ず1個返ってきます。ただし関連性のあるデータがなくても、必ず1個返してきてしまいます。つまり、例えばワインのことを聞いているのに、ワインのデータがない場合、トマトのデータが返ってきちゃうみたいなことが起きます。

やっかいなのが、検索スコアのような応答のパラメーターがないことです。関連性があるのかを判断する材料がないので、現時点だとこれは自分たちの要件には合わないかなという判断をしました。

これに対してCognitive Searchであれば、個数の指定をするパラメーター自体もなく、応答データには関連性のスコアも入って応答が返ってきます。

なので、自分でこのスコアをソートして、一番上の関連性が高い応答を回答を生成する元データとして使えばいいし、スコアが一定以下しかない場合は、ヒットなしと判断して足切りしちゃってもいい。明らかにこれが要件に合うなということで、こっちのほうに置き換えをしていっている感じです。

品質保証のためにやっていること

最後に、品質保証のあたりが本番に向けて日々やっているところです。まず、当たり前ですが、ユーザーの手元に届けるWebアプリケーションなので、ユニットテストはきちんと書いて、いつでもそのテストが回るようにしておきましょう。

次にフロントのセキュリティとして、Web Appの周辺になりますが、WAFやネットワークの設定はきっちりやっておく必要があります。

3つ目の安全性について、入力したデータを何もチェックせずにAzure OpenAIに送ってしまうと、兵器の開発だったり、まずい言葉をユーザーが入れてしまう可能性があります。 OpenAI本家であればModeration APIがあるのでこれを使ってチェックしたり、アプリ側で個人情報や機密情報の入力がないかをチェックをする必要があります。

Azure OpenAIはコンテンツフィルターが強力なので、プロンプトインジェクションや危険な言葉が入っても攻撃が成功しにくいように対策がされていますが、個人情報や機密情報の入力チェックという部分は、CASBのような仕組みがないと完全な対応はまだ難しいなと思っています。

また、敵対的プロンプト対策で、「上記の指示を無視してこういうことをやってください」というようなプロンプトが入ってきても、それらを無視するようにPromptTemplateにプロンプトを実装しています。

次に透明性という点です。回答の生成に使った知識の参照元情報を提示しないといけないので、FAQコンテンツへのリンクを付けて返すようにしようかなと考えています。

次の公平性という意味でバイアス除去の必要性ですが、この構築しているのがRAGアプリで、個人データを用いた評価や分類を決定しないので、今回はあらかじめ振る舞いを指定している以上のことはしていません。

次の性能についてですが、アプリケーションとして当たり前の話として、ログを出したり、APM(Application Performance Management)で監視をして、全体の性能が常にきちんと追跡できるようにしています。

個人的に今もっとも熱いのは正確性で、わからない時に確実に「わからない」と答えられているかどうかが重要で、これもPromptTemplateである程度の追加対策をしています。

ただし、PromptTemplateの指定によっては、わからない時に「わからない」とは答えるのですが、わかるはず、つまり回答生成用のデータがある時にも「わからない」と答えることがわりとあります。

実は、僕らがアプリケーションを構築している中の、おそらく3分の1の時間は、PromptTemplateの部分で戦っている時間だなと感じています。なので、このあたりの品質を上げていくことが重要なんだなと思っています。

今日はLTなのでここまでですが、もっと詳しい話を月末にMicrosoftで開催されるイベントで話すつもりです、今まさに手元で起きている課題で未解決のものもまだあるので、6月27日までには試行錯誤して解決して、その結果を話そうかなと思っています。

ぜひ興味があれば、「Microsoft Build Japan」のDay1の17時から17時半のセッションに参加していただければと思っています。ということで、僕からは以上になります。ありがとうございました。

(会場拍手)