以下の内容はhttps://tech.smarthr.jp/entry/2025/03/05/145917より取得しました。


AI/機械学習によるカスタマーサポートの回答予測 ── 試行錯誤の歴史

こんにちは!SmartHRのカスタマーサポートで技術導入を推進している kano です。

私の所属している技術推進ユニットでは、カスタマーサポートの効率化や品質向上を目指して、様々な取り組みをしています。
カスタマーサポートは、AI/機械学習との親和性がとても高く用途が多いので、私たちも積極的に取り組んでいます。今回は、取り組みの一部をご紹介したいと思います。

目次

問い合わせに素早く回答したい!

カスタマーサポートの本質は、お客様の疑問を素早く、的確に解決することだと考えています。
しかしながら、様々な要因によって、それが年々難しくなってきています。

カスタマーサポートでは、以下の嬉しい課題を抱えています。

  • プロダクトや機能の数がどんどん増える!
  • お客様の数がどんどん増える!
  • お問い合わせの件数がどんどん増える!

また、扱っている領域の性質上、細かいコンテキストを加味すると、特定の回答が問い合わせの大多数を占めるわけではなく、回答パターンが多岐にわたるようです。

上記のような状態で、いままでは熟練したサポート担当者が、様々なお問い合わせに対して適切な回答を行っていました。
しかし、1人1人の負荷が高まることは避けられず、育成にかかる期間も長期化する傾向にあります。

そこで、私たちはAI/機械学習を活用して、お問い合わせに対する回答を予測する取り組みを行っています。
ざっくり要約すると、Chrome拡張機能を開発し、過去のお問い合わせのテキストを元に、回答のためのテキストを画面上でサジェストするというものです。

SmartHRではチャットサポートにSaaSのツールを用いています。
サポート担当者が画面上でチャットと並べて表示させられること、また、現在開いているチャット画面からお問い合わせ内容を取得できることの2点から、Chrome拡張を選択しました。
Chrome拡張の機能やサジェストの内容は、試行錯誤を繰り返しながら徐々に改善を進めています。

以下は、現在までの試行錯誤(失敗)の過程と結果の共有です。
今回の記事を書いていて「なんでこんなことを……」と思う点もありますが、ご笑覧いただけると嬉しいです。

Chrome拡張のスクリーンショット

生成AIによる回答の生成

まずは、何かと話題の生成AIを用いて、カスタマーサポートの回答を生成することに挑戦しました。

やったこと

まずはやってみようということで、Google が提供している Vertex AI Agent Builder の Search 部分を活用し、過去の質問文から類似の質問文を検索し、その時の回答結果から、生成AIを用いて回答文を生成する機能を作成しました。

結果

バックテストでは成功したものもありましたが、質問文をクエリしても適切な回答が検索されないケースが多く、結果として回答が生成されないことが頻繁にありました。
根本的には検索部分の理解が不十分だったと考えていますが、本格的な運用は難しいと判断しました。
トライアルで運用した際に生成された件数も絶対数が少なく、確実な回答が出せた場面はありませんでした。

教訓

単語の羅列ではなく、文章で文章を探そうとしていたことが合わなかったのかな?と考えています。
もしかしたら、単純なベクトル類似度だけで検索させる方が、精度は高いかもしれません。

今回試みたのは生成AIの生成時に有益な情報を追加する、「RAG」と呼ばれる手法にあたります。
試してみてはっきりしたのは「RAGの精度(= 回答の精度)を決めるのは検索」でした。
そのため、検索について根本から理解をする必要があると感じました。
とはいえ、裏を返せば高精度の検索エンジンを作成する話になるので、そのハードルは高く、悩ましく、もう少し中長期的に取り組む課題であると認識しました。

これから

生成AIを用いて回答をするには、いくつかのステップを踏む必要がありそうだと考えています。

いきなり生成AIを用いるのではなく、まずは既存のサポートの資産であるマクロ(=回答のための定型文)を用いて、パフォーマンスを上げる取り組みをする方が良いと考えています。
その方がコスト面でも、ハルシネーションなどのリスクも回避できると考えています。この取り組みについては次項でご紹介します。

長期的には、検索機構も含めてプロトタイプを作成し、徐々に精度を上げられる環境を整えていきたいと考えています。

マクロのサジェスト - 特定のプロダクトでの回答予測

今までサポートの中で蓄積してきたマクロ(=回答のための定型文)を使用し、回答をサジェストすることに挑戦しました。
まずは、特定領域のプロダクトに絞り、タレントマネジメント領域のプロダクトでトライアルをしました。

やったこと

サジェストは機械学習を使い実装しました。理由は大きく以下の3つです。

  • 過去の取り組みから、一定精度以上の判定ができることがわかっていた
  • サジェストするべきトピック数が多いため、生成AIによる判定は難しい
  • コスト面でも機械学習の方が有利である

機械学習のモデルを作成するためには、まず「教師データ」を用意する必要があります。
教師データは、お客様の質問文と、私達が回答した文のセットが必要になります。
サポートシステム上のデータでは、どこまでが質問文なのか判別する情報は含まれていないため、何らかの方法で質問文を抽出する必要があります。
ここでは、マクロで回答した日時以前のお客様の入力文を質問文とみなし、集計することにしました。

そのうえで問題になるのが、どの文章がマクロで回答した文章か明確にはわからないことです。
そのため、「SmartHRからの回答文」と「マクロの本文」をコサイン類似度で算出し、類似度が高いものをマクロを使用した文書とみなすことにしました。

コサイン類似度を算出するために、文章をベクトル化する必要があります。
ベクトル化には、Azure OpenAI Service の text-embedding-3-large のモデルを使用しました。
こちらのモデルは、過去の実験でも高い識別精度を確認できていたので使用しています。

実際に問い合わせデータをベクトル化し、問い合わせごとに類似度を算出します。
問い合わせの一連のやりとりの中で、マクロの本文と最も類似度が高かった文章を「マクロを使用した文章」とみなし、使用した日時を取得しました。

上記のデータをもとにモデルを作成しています。モデルは LightGBM を使用しました。
採用理由はいくつかありますが、教師データがテーブルデータであること、そしてサジェストのレスポンス速度も重視する必要があることから、軽量ながら高精度を得られたLightGBMを選択しました。

バックテストでは、正解率で7割程度の精度が出ていました。

結果

いざトライアルを開始してみると、特定のプロダクト領域に関する問い合わせのサンプル数がそもそも少なく、サジェストを使う件数があまりない状態となりました。
そのため、たとえこのモデルが成功していたとしても、効率化の効果が限定的であると考え、急ぎ方針を転換しました。

教訓

トライアルとして、あえて規模の狭い範囲を選んだつもりではありましたが、実際の数値感を確かめずに進めると失敗します。
AI/機械学習が得意な領域の性質を考えると、あまりコンテキストが深くなく、「よくある問い合わせ」には効果的だと考えられます。
そのため、一定以上の規模を確保することが、どうしても必要でした。

マクロのサジェスト - プロダクト全体での回答の予測

特定領域のプロダクトに絞ったトライアルの教訓を元に、マクロのサジェストをプロダクト全体に適用する挑戦をしました。

やったこと

予測は引き続き機械学習でやっていきます。

マクロ判定でも同じ問い合わせに対して複数の回答パターンが存在する場合があり、上位5件を表示するようにしました。
文面上ではほぼ同じ質問内容なのですが、テキストに現れづらい文脈によって回答が変わるケースがあり、 一意に特定しづらいため、複数の回答パターンを表示することでサポート担当者が選択しやすくなると考えました。

また、マクロの判定の前段に、「マクロを使用している/いない」の判定を追加しました。
いままでは、特定のマクロを予測し、その中で最も確信度が高いものを表示し、確信度が低い場合は「マクロを使用していない」としていました。
しかし、結果表示の性質が変わったため、専用のモデルを用意しました。

マクロを使用している/いないの判定には、深層学習を使用しました。
深層学習は、特徴量の抽出が難しい場合や、特徴量の組み合わせが複雑な場合に有効です。
精度が高い予測のためにはサンプル数も必要なのですが、シンプルな2値判定であれば、サンプル数も確保できるため採用しました。

マクロを複数サジェストすることで、当然ですが全体の正解率が8割程度まで向上しました。

実装の一部ですが、マクロ判定部分のコードを示します。
以下は、マクロを使用している/していないの判定を行う関数です。

def is_macro_using(vector: np.ndarray) -> bool:
    """
    マクロの使用有無を予測する
    param: vector: np.ndarray: ベクトル化された質問文
    return: bool: False: 未使用, True: 使用
    """
    # モデルの読み込み
    model = load_model("src/model/macro_binary_classification.keras")

    # 予測実行
    predictions = model.predict(vector)

    # 予測結果の閾値判定
    # 出力はシグモイド関数の結果なので、0.5を超えれば1(テンプレート使用)、以下なら0(未使用)とします
    pred_labels = (predictions > 0.5).astype(int)

    if int(pred_labels[0][0]) == 0:
        return False
    else:
        return True

以下は、マクロを使用している場合で、合致率が高い上位5マクロをサジェストする関数です。

def predict_topk_labels(
    vector: list,
    conversation_id: str = "",
    top_k: int = 5,
) -> dict:
    """_summary_
    上位5件のマクロラベルを返す
    Args:
        vector (list): _description_
        conversation_id (str, optional): _description_. Defaults to "".
        top_k (int, optional): _description_. Defaults to 5.
    """

    model = joblib.load("src/model/macro_suggest_model.joblib")
    scaler = joblib.load("src/model/scaler.joblib")
    label_encoder = joblib.load("src/model/label_encoder.joblib")

    with open("src/model/label_to_title.pkl", "rb") as f:
        label_to_title = pickle.load(f)

    with open("src/model/label_to_text.pkl", "rb") as f:
        label_to_text = pickle.load(f)

    # 特徴量のパースとスケーリング
    new_feature_scaled = scaler.transform([vector])
    # 予測確率の取得
    proba = model.predict_proba(new_feature_scaled)
    # 上位k個のテンプレートを取得
    top_results = [
        {
            "label": x,
            "title": label_to_title[x],
            "body": label_to_text[x],
            "highlights": label_to_text[x],
            "tags": "",
            "copy_to_clipboard": label_to_title[x],
        }
        for x in get_top_k_labels(proba[0], top_k, label_encoder)
    ]
    # ラベル名(マクロタイトル)から各値を取得して所定のDictで返す
    result = {
        "query_input": f"suggestion: {conversation_id}",
        "query": None,
        "hit_count": top_k,
        "macros": top_results,
    }
    return result

def get_top_k_labels(proba_row, k, label_encoder):
    """_summary_
    上位k件のラベルを返す
    """
    top_indices = proba_row.argsort()[-k:][::-1]
    return label_encoder.inverse_transform(top_indices)

結果

予測精度が上がり、対象範囲が広がったことで、今度こそと思い臨みましたが結果は芳しくありませんでした。

結論としては、UI/UX面での課題も大きく「使われる」状態には至りませんでした。
いくつか理由があるのですが、もっとも大きな理由はサジェストまでの操作に数ステップを踏む必要があり、手間感があることだと考えています。

また、サポートにあたってはマクロだけでなく、他のナレッジなども参照しており、それらも同時にサジェストできると、より使いやすいというフィードバックもありました。

教訓

アプリが「使われる」にはいくつか超えるべき障壁があることを認識しました。
サジェスト機能などのデータ開発の領域だけではなく、業務分析、UI/UX設計などを総合的に考える必要があると感じました。

これから

効果を発揮するためには「使われる」必要があります。

まずは、UI部分を中心に改善を進めていきます。
すでにURLを切り替えるだけでマクロのサジェストが表示されるようにするなど、いくつか改良を行いました。
サポート担当者にも使ってもらい、使用感を確かめながら改良していきます。

また、マクロを使用した回答の予測を試みましたが、現場が求めているものはマクロを超えて横断的にサジェストができるものでした。
そのため、サジェストできる範囲を拡大していきます。

「事前にこの辺まで含めて把握した上で着手ができていれば……」と、反省すべき点は多いと感じています。

以上、カスタマーサポートでの生成AIによる回答の生成、マクロのサジェスト取り組みをご紹介しました。
引き続き、より質の高いサポートを実現できるように、取り組んで行きたいと思います。

We Are Hiring!

いかがでしたでしょうか。SmartHRでは現在、AIに関連するエンジニアやプロダクトマネージャーを募集しています。
また、カスタマーサポートでもエンジニアの募集をしています。
少しでも興味を持っていただけたら、カジュアル面談でざっくばらんにお話ししましょう!




以上の内容はhttps://tech.smarthr.jp/entry/2025/03/05/145917より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14