こんにちは、従業員サーベイ機能を開発しているiguchです。
今回は7月にリリースした、SmartHRで初めてAIを利用した機能となるサーベイの回答要約機能について、開発に関するあれこれを紹介します。
サーベイの回答要約機能の紹介
まずサーベイの回答要約機能について簡単に紹介します。
従業員サーベイ機能では自由記述(テキスト)形式で回答可能な質問を作成できます。
数人の回答であれば短時間で全ての回答を読み回答を把握することが可能ですが、数百人、数千人になってくると、全ての意見を読んでを把握するのには多くの時間を要します。
上記の理由から、実際にお客様からも要約が見れるようにならないかといった要望をいただいていました。
今回開発した回答要約機能を使うと、自由記述形式の回答を部署ごとに要約し、さらにポジティブな意見とネガティブな意見に分類された状態で閲覧でき、意見の方向性の把握が短時間で行えるようになります。
詳しくはアップデート情報をご確認ください。
選定したAIサービスとライブラリ
つづいてAIを利用する上での環境についての紹介です。
元々LLMを利用して要約の開発を行うことは既定路線でしたが、今回はMicrosoft社のAzure OpenAI Serviceを利用しました。
Azure OpenAI Serviceを選んだ主な理由は、
- トレーニングデータの暗号鍵管理
- エンドポイントの非公開化
- アクセス制御
などのセキュリティ面をコントロールできるためです。
また従業員サーベイのバックエンドはRubyで開発しているため、ruby-openaiというgemを使用しました。
執筆時点ではAzure OpenAI Serviceにも対応していますが、開発開始時点では対応していなかったため元リポジトリをforkしてAzure OpenAI Serviceでも利用できるようにパッチを当てて使用していました。
要約のロジック
実際に要約をどのようにAIを利用しているかを紹介します。
Azure OpenAI Serviceではモデルごとに入力プロンプトに使えるトークン数に上限があるため、全ての回答を1つのプロンプトとして入力し要約することはできません。
そのためLangChainというライブラリで利用されているrefineというアルゴリズムを採用して要約を行いました。
LangChainは、LLMを使ってサービスを開発する上でよくあるロジックをフレームワークとして提供しているライブラリです。
実装する上での課題として、Rubyでは公式のLangChainのライブラリは提供されていなかったため、今回はrefineだけを独自に実装しました。
ざっくりとした処理の流れとコードは以下のようになっています。
※以下コード中に含まれるプロンプトはあくまで例となります。
1. 全ての自由記述の回答を1つの文字列に結合する
2. 1で作成した文字列をトークンの上限数を超えないように適切な文字数に分割し配列にする
# messagesに1で作成した文字列が格納されている chunks = messages.shuffle.join.scan(/.{1,2500}/m)
3. 2で作成した配列の最初の要素を要約する
user_content = <<-CONTENT 以下の文章を要約してください。 #{chunks[0]} CONTENT response = openai_client.chat(parameters: { model: "gpt-3.5-turbo", messages: [ { role: "system", content: "You are a powerful summarizer." }, { role: "user", content: user_content.to_s }, ], temperature: 0, }) summary = response.dig("choices", 0, "message", "content")
4. 3の要約結果と2で作成した配列の次の要素を一緒にLLMに渡し要約する(2で作成した配列の要素がなくなるまで繰り返す)
chunks = chunks[1..] chunks.each do |chunk| user_content = <<-CONTENT 以下に既存の要約文があります。 #{summary} 上記にの要約に加え以下の文章を要約してください。 #{chunk} CONTENT response = openai_client.chat(parameters: { model: "gpt-3.5-turbo", messages: [ { role: "system", content: "You are a powerful summarizer." }, { role: "user", content: user_content.to_s }, ], temperature: 0, }) summary = response.dig("choices", 0, "message", "content") end
さらに要約をポジティブな要素とネガティブな要素にわける、いわゆる感情分析もLLMで実行しています。
テストについて
最後にテストに関しても触れておきます。
要約機能のテストについては、無数にある入力値のパターンや、要約の精度をどれぐらいまでテストで担保するかなどが課題でした。
無計画にテストをしてしまうと品質が低いものになって使われなくなってしまったり、品質を過剰に担保しようとしすぎてリリースが全然できなかったりといった事態を避けるべく、テストのやり方と基準を策定しました。
テストのやり方
まず要約対象の回答データが必要なため、回答データを以下のようなプロンプトでChatGPTに生成してもらいました。
# 依頼: 以下の制約条件をもとに、従業員に自社の働く環境について答えてもらったアンケートのサンプルデータを20人分作ってください。 出力結果には、データ以外の要素(例:説明や出力コード)を含めてはいけません。 # 制約条件: - サンプルデータのフォーマットは、以下のオブジェクトの配列にしてください。 - {id: 社員ID, department: 部署, answer: アンケート回答} - 社員IDは1番から連番でふってください。 - 部署は営業が5人、開発が5人、マーケティングが5人、総務が5人となるように割り振ってください。 - 雇用形態は、開発と総務に1人ずつ派遣社員を入れ、あとは全員正社員にしてください。 - ネガティブな回答をした人を10人、ポジティブな回答をした人を10人にしてください。 - アンケート回答は1人につき50文字から100文字にしてください。 # 出力結果:
上記のデータに加え、以下のパターンのデータを追加し要約のテストを実施しました。
- プロンプトインジェクションを狙ったテキスト
- 絵文字が混じっている
- 顔文字が混じっている
- 英語が混じっている
テストの結果をどう判断するかの基準
以下は実際の基準を抜粋したものです。
- 優先度A
- 確認事項
- 入力値にポジティブ、ネガティブと取れるデータがあった場合は両方の要約が出力されること
- 基準を満たしていない場合
- プロンプトを見直す
- 確認事項
- 優先度B
- 確認事項
- 想定通りの形式で出力されること
- ハルシネーションが起きていないこと(明らかな嘘は優先度Aとして対応)
- 基準を満たしていない場合
- 程度に応じて判断する
- 確認事項
- 優先度C
- 確認事項
- 特定のパターンで意味がわからない出力がでないこと
- 基準を満たしていない場合
- ヘルプページに記載する
- 確認事項
これらの基準に従いテストを行うことで、完璧ではないけれども後々改善しつつ一旦ユーザーへの価値提供を優先しリリースしましょうなどの決定を行うことができました。
まとめ
今回はサーベイの回答要約機能の開発に関するあれこれを紹介しました。
AIを活用した開発の一通りの流れが理解できてとてもいい経験になりました。
また、要約に関する複雑な知識がなくてもLLMという新しい技術を使って比較的小さめのコストで開発できたのは今後のSmartHRにおけるAI活用の先駆けとしていい事例になったかなと思います。
回答要約機能はまだテスト中の機能なので今後も改善を続けていきます!
We Are Hiring!
SmartHRでは絶賛エンジニアを大募集しています。
カジュアル面談もやっておりますので気軽にお話ししましょう!