🔰

マイベストで経験した1年とこれから

2024/05/10に公開

株式会社マイベストに24新卒で入社したucchiiiです!
僭越ながら、mybest BlogKaigi 2024 の17日目を担当します。

先日無事マイベストに入社した私ですが、2023年5月より内定者インターンとしてお世話になっていました。今回は、私がその約1年間の内定者インターンにおいて経験したことをお話しできればと思います。

mybest GPTにおけるHallucination対策

マイベストは今年1月に、ChatGPTのGPTsとして「mybest GPT」をリリースしました。mybest GPTは、画像のようにChatGPTにおすすめの商品を聞くと、マイベストのデータベースからおすすめの商品を選んで返してくれる、というものです。

私は機械学習PoCチームの一員として、ChatGPTによるHallucinationへの対策方法を検討しました。

Hallucinatonとは、生成AIが作り出す誤情報のことです。mybest GPT では、APIから送られてきた情報をChatGPTが独自に解釈し、誤った情報を混ぜてユーザーに提供することが懸念されていました。

そこで、実際にChatGPTに対して約150個の質問を投げかけ、得られた回答をマイベストのランキングや記事の情報と比較することで、Hallucinationの有無を調査しました。

作業としては、

  • 想定質問を考える(約150個)
  • 手作業でChatGPTに質問する
  • 回答をスプレッドシートにコピーする
  • 各商品の担当ディレクターに、回答の正誤を確認していただき、正答率を集計
  • プロンプトやロジックを微調整

という、かなり地道なものでした。自動化が進められているこの時代に逆行するような作業でしたが、無事完遂することができました。

結果として、「おすすめの冷蔵庫を教えてください」といった、マイベストの記事に沿っており簡潔に述べられている質問は正しい回答が生成されました。一方で、「一人暮らし向けで6畳の部屋に置いても窮屈に感じない冷蔵庫を教えて」といった複雑なクエリで検索した場合では、想定した記事(一人暮らし向け冷蔵庫)のランキングを取得することができませんでした。

実際に多くの質問をしてみると、質問内容の解析と回答の生成はChatGPTがすることや、質問文や内容はユーザーに一任されることから、私たちの手が及ばない範囲での不確定要素が多く、Hallucinationを完全に無くすことは難しいと感じました。

しかし、受け取ったクエリをマイベストのデータベースに沿うように適切に分解することや、ユーザーに対して質問例を提供することなど、自分たちが取り組めることもあります。注力するには工数がかかるため、mybest GPTがどのくらい利用されるのか、工数をかける価値があるのかをこれから見極めていく必要があると感じました。

と当時考えていましたが、今は想定質問が出てくる仕様になっているのでみなさんぜひmybest GPTを試してみてください!!

Webプロダクト物販チーム

入社して約3ヶ月後の2023年8月〜2024年3月まで、Webプロダクト物販チームに配属されました。

入社当時から、フロントエンドもバックエンドも含めて「調査からリリースまで全ての段階に携わりたい」と希望していたため、このチームでは一つの機能を調査からリリースまで一貫して担当するタスクを任せていただけることが多かったかなと感じています。

特に、多くの技術を幅広く使用したと感じるタスクについてご紹介したいと思います。

商品の詳細情報をスプレッドシートから一括でインポートする

マイベストの記事で扱う商品は、それぞれが「詳細情報」というものを持っています。これは、本体のサイズや重さなどの商品のスペック情報のことです。詳細情報は、各記事において独自に選択し、表示することができます。

以前のオペレーション

以前は、新たに追加された詳細情報をスプレッドシートで保存し、管理画面から手動で入力していました。

実現したこと

スプレッドシートでまとめた状態のものを、一括でインポートできるようにしました。以下が使用するスプレッドシートの例です。

このシートのURLを、管理画面の該当箇所に入力するだけで、詳細情報の入力が完了します。

実現した方法

この機能を開発するにあたり、以下の3つを実装しました。

  • スプレッドシートのURLを入力するモーダル
  • 取得したURLをbackendへ渡すMutation
  • 受け取ったURLを元にデータをDBへ保存する処理

使用した技術は、Ruby on Rails、TypeScrip、React、GraphQLです。

モーダル部分

以下の処理を行うモーダルを作成しました。

  • スプレッドシートのURLを受け取る
  • Mutationを実行する
const handleSubmit = async (e: FormEvent) => {
  e.preventDefault();
  const result = await mutation({
    variables: {
      input: {
        id: categoryId,
        sheetUrl,
      },
    },
  });
}
...
return(
...
  <form className="l-margin-16" onSubmit={handleSubmit}>
    <p>インポート先は読み込み可能な権限にしておいてください</p>
    <input
      className="jk-textfield l-margin-12"
      placeholder="スプレッドシートのURL"
      type="url"
      onChange={(e) => {
        setSheetUrl(e.target.value);
      }}
      required
    />
    ...
  </form>
...
)

Mutation

モーダルで受け取ったURLから、データを保存するためのMutationを作成しました。
Mutation内でスプレッドシートのAPIを叩くことでデータの保存を行っています。(spreadsheet.do_import

module Mutations
  class ImportProductSpecDetailItem < BaseMutation
    field :category, Admin::ObjectTypes::CategoryType, null: true
    field :errors, [Admin::ObjectTypes::ErrorType], null: false

    argument :id, ID, required: true
    argument :sheet_url, String, required: true

    def resolve(id:, sheet_url:)
      category = Category.find(id)

      spreadsheet = Spreadsheets::ProductSpecDetailItem.new(sheet_url, category)
      spreadsheet.do_import

      { category: category, errors: [] }
    rescue => e
      { category: nil, errors: e.to_s.split("\n").map { |message| { message: message } } }
    end
  end
end

スプレッドシートを読み込んでデータを保存する処理

詳細情報に関するスプレッドシートを扱うためのクラスを作成しました。

class ProductSpecDetailItem
  SPEC_DETAIL_SHEET_NAME = 'シート名'.freeze

  def initialize(url, category)
    @spreadsheet_key = GoogleSpreadsheet.extract_spreadsheet_key_from_url(url)
    @category = category
    @service = GoogleSpreadsheet.authorized_service
    @sheet = @service.spreadsheet_by_key(@spreadsheet_key)
  end

  def do_import
    errors = []
    # Googleスプレッドシートを扱うクラスのメソッドを呼び出し、スプレッドシートからデータを取得
    records = GoogleSpreadsheet.do_export(@service, @spreadsheet_key, SPEC_DETAIL_SHEET_NAME)
    errors.concat(errors_in_spec_headers(records.first.keys)) if records.present?
    raise ArgumentError, errors.join("\n") if errors.present?
    # 実際の保存処理は以下のクラス内で行う
    transaction = Transactions::Categories::ProductCategoriesAndProductSpecDetailItems.new(@category)

  end

  def errors_in_spec_headers(record_headers)
    # スプレッドシートのカラム名やデータの不備などを検知するメソッド
  end
end

まとめとこれから

この一年間で、設計〜実装までの様々な工程や、Frontend、Backendを問わない様々な技術に触れることができました。

今までに触れたことのない技術ばかりでしたが、「分からない→自分で10分考える→事態が好転しそうでなければ先輩エンジニアにすぐに聞く」 というフローを確立することで、エラーを起こすことなく期限内に機能をリリースできました。

2024年5月からは商品データベースチームに配属されました。このチームでは、「完全なデータベース作り」を目指して、チームメンバーと協力しながら自身が最大限に成長できるよう貪欲な姿勢で日々の業務に取り組みたいと思います。

Discussion