kickflow Tech Blog

株式会社kickflowのプロダクト開発本部によるブログ

AI申請前レビューのE2Eテスト自動化 〜Difyを活用した動的テキスト検証〜

AIのテスト、毎回結果が違って困ってませんか?

こんにちは、kickflow QAチームのNです。

kickflowではAutifyを使ってE2Eテストの自動化を行っています。
最近AI申請前レビュー機能がリリースされたのですが、AIが出す指摘文が毎回微妙に違うため、指摘内容が妥当かの検証をどう自動化するか試行錯誤していました。

今回は、AutifyDifyを使ってこの問題を解決したので、この取り組みを紹介します。

1. AI申請前レビュー機能の概要

kickflowには、申請を送る前にAIがチェックしてくれる機能があります。
誤字脱字や、フォーマットのミスを見つけてくれる優れものです。

AI申請前レビュー機能で出来ること

  • 申請内容をリアルタイムで分析
  • 誤字・脱字の検出
  • フォーマット不一致の指摘
  • 不整合な内容を教えてくれる(例:賞与「無」なのに支給月を選んでる、など)

2. E2Eテストで直面した課題

毎回違う指摘文が出てくる

AIが出す指摘文、毎回ちょっとずつ違うんです。

例えば

  • 1回目
    • 誤字があります(「お願いしまう」)。
    • 修正案:「お願いしまう」を「お願いします」に修正してください。
  • 2回目
    • 誤字があります(「お願いしまう」)。
    • 正しい表記(例:「お願いします」)に修正してください。
  • 3回目
    • 文中に誤字があります(「お願いしまう」)。
    • 修正してください(例:「お願いしまう」→「お願いします」)。

言ってることは同じなのに、表現が違う…これが、テスト自動化する際に直面した課題でした。

何が困るか

  • 完全に一致してないとテストが失敗する
  • ほぼ毎回失敗するので、テストの意味がなくなる
  • 「誤字を検出できてるか?」をテストしたいのに、「文章が一致してるか?」のテストになってしまう

従来の検証方法とその限界

E2Eテストでよくやる方法を試してみましたが、どれも安定して稼働することが叶いませんでした。

  1. 完全一致で比較:文字が1つでも違うとNG、使えない
  2. 一部だけ一致を確認:甘すぎて、おかしな指摘でもOKになってしまう
  3. 正規表現:複雑で、メンテナンスが大変

3. 解決策:Difyによる文章の「意味」を照合する

どう解決したか

やったこと

  • 期待してる指摘文(Expected)と、実際に出た指摘文(Actual)をDifyに送る
  • Difyが「意味が同じかどうか」を判断
  • 表現が違っても、意味が同じならOK(意味的な同等性)と判定

Difyアプリの中身

作ったワークフローはシンプルです!

  1. 入力項目

    • expected_value: 期待してる指摘内容
    • actual_value: 実際に表示された指摘内容
  2. やること:

    • AIが2つの文章を読み比べる
    • 意味が同じかどうか判断
  3. 出てくるもの:

    • test_result: PassedFailed
    • reason: Failedのときは理由も教えてくれる

実際のプロンプトは以下です

■依頼内容
ユーザーが指定した「期待値」と「実際の値」を比べ、同様の内容になっているかどうかをチェックしてください。
同様の内容である場合は「Passed」を、全く異なる内容である場合は「Failed」を返してください。

■期待値
{{#expected_value#}}

■実際の値
{{#actual_value#}}

出力を安定させた構造化出力

AIの応答は基本的にランダムなので、判定結果を決められたフォーマットでの出力をするために、DifyのLLMノードで構造化出力(structured_output)を設定しました。

やったこと

structured_outputtest_resultを必須にする設定を実施

結果

以下の固定JSON形式で必ず出力されるようになる

  { "result": { "test_result": "Passed" } }

or

  { "result": { "test_result": "Failed" } }


この内容は、CTOのkobakeiさんによる社内のAI勉強会で学んだ知識でした。
これにより、毎回確実に同じ形式で判定結果を受け取れるようになったため、E2Eテストが安定して成功するようになりました!

4. 実装ステップ(Autify / JavaScript)

全体の流れ

1. AI申請前レビューを動かす
2. 表示された指摘文を取得(AutifyのJavaScriptステップで)
3. Difyに送る(AutifyのJavaScriptステップで)
4. Difyから判定結果をもらう
5. PassedならテストOK、FailedならNG

ステップ1: 指摘文を取ってくる

AutifyのJavaScriptステップ1:画面から指摘文を取得

  var sel = (typeof selector === 'string' && selector) ? selector : 'YOUR_SELECTOR';
  var idx = parseInt(INDEX, 10);
  if (isNaN(idx)) idx = 0;

  function norm(s) {
    if (!s) return '';
    return String(s).replace(/\u00A0/g, ' ').trim();
  }

  var nodes = document.querySelectorAll(sel);
  if (!nodes || nodes.length <= idx) return '';

  var card = nodes[idx];
  var ct = card.querySelector('.v-card-text');
  var text = ct ? (ct.innerText || ct.textContent || '') : (card.innerText || card.textContent || '');
  return norm(text);
})();

ポイント

  • セレクタで指摘文が表示されてる場所を特定
  • INDEXを引数として設定し、何番目の指摘かを指定
  • 余計な空白とかを削除して返す

ステップ2: Difyで比較

AutifyのJavaScriptステップ2:Difyに送って判定してもらう

  var url = 'YOUR_URL';
  var apiKey = 'YOUR_APIKEY';

  function doFetch() {
    return fetch(url, {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        inputs: {
          expected_value: EXPECTED,
          actual_value: ACTUAL
        },
        response_mode: 'blocking',
        user: USER
      })
    });
  }

  return doFetch()
    .then(function (res) {
      if (!res.ok) {
        return res.text().then(function (text) {
          throw new Error('HTTP ' + res.status + ': ' + text);
        });
      }
      return res.json();
    })
    .then(function (json) {
      var outputs = json && json.data && json.data.outputs;
      var testResult = outputs && outputs.result && outputs.result.test_result;

      console.log('outputs:', outputs);
      console.log('testResult:', testResult);

      if (String(testResult).toLowerCase() === 'passed') {
        return testResult;
      }

      var reason = JSON.stringify(outputs);
      throw new Error('テスト結果が失敗です(Failed)。 詳細: ' + reason);
    });

ポイント

  • EXPECTEDを引数に設定し、期待値を入力
  • ACTUALを引数に設定し、他のステップから取得で画面から指摘文を取得したステップを指定
  • Difyに期待値と実際の値を送る
  • response_mode: 'blocking' で結果が返ってくるまで待つ
  • Passedなら成功、それ以外はエラー

5. 実装の技術的な注意点

画面要素の見つけ方

最初は複雑なセレクタを使ってましたが、最終的にシンプルな .v-card--variant-flat.bg-surface__warning__weak に落ち着きました。

なぜこれがいいか

  • AI指摘エリア専用のクラスだから
  • 他の要素と間違えにくい
  • 後から見ても分かりやすい

6. 効果と今後の展望

良かったこと

  1. テストが安定した

    • 指摘文の表現が変わってもテストが通る
    • 失敗が激減した
  2. メンテが楽になった

    • 複雑な正規表現を書かなくていい
    • 期待値の書き方がシンプル
  3. 応用が効く

    • AIの指摘文が変わってもついていける
    • 新しい指摘パターンが増えても大丈夫

これから試したいこと

  • 他のAI機能のテストにも使ってみる
  • Difyの設定を調整して、もっと精度を上げる

7. まとめ

AIが作る文章のテストは、従来の完全一致検証や部分一致検証では期待している効果が出ません。
しかし、更に別のAIを活用して「意味の同等性」を判定することで、自動テストを安定稼働させることができました。

同じようにAI出力のテストについて、安定化で課題を抱えている方にとって、本記事が新しいアプローチのヒントになれば幸いです。


kickflow では、プロダクトの価値を一緒に高めていく仲間を募集しています。ぜひ採用サイトもご覧ください。

careers.kickflow.co.jp