LLM/RAGの回帰テストをCIに組み込む設計ガイド:評価セットから運用レポートまで

LLM/RAGの回帰テストをCIに組み込む設計ガイド:評価セットから運用レポートまで

大規模言語モデル(LLM)やRAG(検索で拾った文書を根拠に回答を作る仕組み)を本番で運用していると、プロンプトを1行変えたり、モデルや索引を更新した直後に、回答の「当たり」が少しずつズレていくことがあります。 ログを追っても原因が一発で特定できず、とりあえずロールバック...そんな手戻りを減らしたいチーム向けの話です。

この記事では、検索(retrieval)と生成(generation)を分けた評価設計を、CI(継続的インテグレーション)上の品質ゲートに落とし込む手順として整理します。合否基準の決め方、同じ入力でも出力が揺れる性質(非決定性)への扱い、例外運用まで含めて、回帰テストを「属人的な確認作業」から「チームで回る型」に寄せるための判断ポイントとテンプレをまとめます。

ゴールと前提: 何をCIで止めたいか

本番運用しているチームだと、先週まで動いていたRAGチャットボットが今朝のデプロイ後に見当違いの回答を返し始める、という手戻りが起きることがあるかもしれません。ログを見ても原因がすぐ分からず、とりあえずロールバックをした経験を持つ方もいるかもしれません。

LLMやRAGを組み込んだシステムでは、従来のテストだけでは変更の影響が見えにくい場面があります。
これは、同じ入力でも出力が揺れることがある(非決定性)が原因かもしれません。
たとえばOpenAI Evalsなどでは、この前提を置いたうえで評価をテストとして扱い、変更前後の差分を見える化する考え方が紹介されています。ここでいうLLM回帰テストとは、変更前後の品質低下をCI上の品質ゲートで検知し、気づかないままのリリースを減らすための仕組みです。

品質ゲートで決めること: ブロックか警告か

CIに回帰テストを組み込むとき、最初に決めておきたいのが「何が落ちたらマージを止めるか」という線引きです。すべてのスコア低下をブロッキングとして止めてしまうと、出力の揺れによるフレーク(偽陽性: 本当は問題ないのにテストが落ちる現象)でパイプラインが止まり続け、チームが疲弊してしまうかもしれません。逆にすべて警告にすると、サイレント劣化を見逃す原因になります。

PASHでは、このような問題に対して、次のように2段に分けると運用が回りやすいと考えています。

  • ブロッキング(マージ不可): 禁止表現の出力、根拠なし回答の増加、検索ヒット率の閾値割れなど、ユーザー影響が大きい失敗
  • 警告(レビュー付きで通過可): 文体の揺れ、冗長度の変化、スコアの軽微な低下など、人が見て判断すべき変化

線引きの正解はチームごとに異なりますが、「なぜブロッキングにしたか」の理由を言語化して合意を残しておくと、あとで判断がブレにくくなります。

CIゲートは最初からブロッキングにすべきか

CIゲートを最初からブロッキングにすべきかはチーム状況によって変わるかもしれません。でも、出力の揺れが大きく評価セットも育成中であれば、まずは警告中心でベースラインを集めるほうが運用は崩れにくいでしょう。

例えば、2週間ほどスコアの揺れ幅を観測してから閾値を決め、ブロッキングに昇格させる流れがスムーズです。一方で、禁止表現や根拠なし回答など、ユーザー影響が大きい失敗は最初からブロッキングに寄せておくと安心です。

スコアだけで判断しない

評価スコア(0〜1のような数値)は便利ですが、数字だけ見ていると「そもそも正しい問いを評価しているか」が抜け落ちがちです。人手の判断と組み合わせて、評価セット自体が実運用の失敗パターンを反映しているかを定期的に確認しておくとよいでしょう。評価は一度作って終わりではなく、ログから評価ケースを採掘して継続的に育てていくプロセスとして扱うほうが運用に乗りやすくなります。

「テストが通っているから安心」ではなく、「主要な失敗パターンを検知できている」と言い切れる状態を目指すのが、品質ゲートの本来の役割だと考えています。

AI検索で引用されやすい情報の作り方については、AI検索時代の情報設計ガイドでも触れています。

回帰の原因を4分類する: モデル・プロンプト・索引・データ

止めたい品質ラインが決まったら、次は「何が変わって壊れたか」の切り分けです。プロンプトを直すのか、モデルを戻すのか——分類が先にないと議論が空転しがちです。

たとえば回帰の原因を以下の4つに整理しておくと、切り分けがしやすくなります。

  1. モデル変更
  2. プロンプト変更
  3. RAG索引更新
  4. データ更新

RAGの挙動はretriever・コーパス・LLM・プロンプトの4つが絡み合って決まるため、どれが変わったかを記録していないと原因を追いにくくなってしまいます。

モデル変更

モデル変更に該当するのは、APIプロバイダのバージョンアップや、ファインチューニング済みモデルの差し替えなどです。モデル名・バージョン・切り替え日時の3点を残しておくと、切り替え前後で同一の評価セットを流して出力差分を比較しやすくなります。プロバイダ側の更新はチームの意思と無関係に起きることもあり、サイレント劣化の典型原因になりがちです。

プロンプト変更

プロンプト変更としては、システムプロンプトやフューショット例の書き換えなどがあります。Gitコミットハッシュと変更差分を押さえておき、変更の意図もコミットメッセージに残しておくとあとの切り分けが速くなります。プロンプト変更が回帰につながったかどうかを検出することは、LLM統合の評価設計で中心的な関心事とされています。

RAG索引(コーパス)更新

RAG索引更新の例としては、ベクトルストアの再構築やチャンク分割ロジックの変更などがあります。索引更新は検索結果を変え、生成にも波及するため、ビルドID・チャンク設定・埋め込みモデル名をセットで残しておくと安心です。retrieval側かgeneration側かを切り分けるには、索引単体のRecall@Kを別途記録しておくのが有効です。

データ更新

データ更新にあたるのは、FAQ追加やナレッジベースの変更、マスタデータの入れ替えなどです。更新頻度が高い割に評価セットとの整合が崩れやすいため、データソースのバージョンと変更件数を追っておかないと「正解が変わったのにテストが古いまま」という逆転が起きがちです。

ここまで4つの分類を見てきましたが、もう1点気をつけたいのが採点器(evaluator)自体の変更です。LLMに採点させる手法(LLM-as-a-judge)を使う場合、採点器のプロンプトやモデルもバージョン管理の対象になります(詳細は後述の採点器自体の変動に注意を参照してください)。

変更が起きたとき、4分類のどれに該当するかを1行で書く欄をPRテンプレートに足しておくと、初動が速くなります。直近1週間のデプロイログを見て、最も頻繁に動いている分類から優先度をつけていくとよいでしょう。

テスト設計: retrieval と generation を分けて失敗パターンを決める

原因を4分類できても、「回答がおかしい」だけでは次の一手が見えにくいものです。検索の問題なのか、生成の問題なのか——先に切り分けておくと対処の選択肢が絞れます。

なぜ検索と生成を分けて評価するか

RAGパイプラインは大きく2段階で動きます。クエリから関連文書を取得する検索(retrieval)と、その結果をもとに回答を組み立てる生成(generation)です。この2つを一括で採点してしまうと、どちらが劣化したのか見えにくくなります。

たとえば社内ナレッジ検索で「育休の申請期限は?」に誤答が返ったとします。原因は大きく2つ考えられます。古い就業規則が上位に来てしまった検索の問題か、正しい文書を取得したのに日付を誤読した生成の問題か——両者で対処方法はまるで違ってきます。

検索側の失敗パターン

検索ステップで起きやすい失敗は主に3パターンです。

  • 取得漏れ(Recall低下): 正解文書がTop-Kに入らない。索引更新やチャンク分割の変更後に発生しやすい
  • ノイズ混入(Precision低下): 無関係な文書が上位を占め、生成を汚染する。クエリ書き換えロジックの変更で起きやすい
  • 順位逆転: 正解文書は取得できているが順位が下がり、コンテキスト窓から外れる

評価指標はRecall@K(正解が上位K件に含まれる割合)やPrecision@K(上位K件の正解率)、MRR(正解が上位に来るほど高くなる指標)などが該当します。LLMを呼ばずルールベースで算出でき、コストが低く再現性も高い点がメリットです。

生成側の失敗パターン

生成ステップでは、検索結果が正しくても以下のような失敗が起きることがあります。

  • 幻覚(ハルシネーション): 取得文書にない情報を生成する。groundedness(根拠忠実性)の低下が兆候になる
  • 省略・欠落: 根拠文書中の条件や例外を落とす。answer relevanceの低下として現れる
  • 形式崩れ: JSON出力の破損や禁止表現の混入。ルールベース検証で拾えるケースが多い
  • トーン逸脱: 敬語崩れやガイドライン違反の表現が出る

これらはTruLensではRAG三要素として整理されており、context relevance(検索適合度)、groundedness(根拠忠実性)、answer relevance(回答適合度)の3つの視点から評価できるようになっています。

評価ワークフローへの落とし込み

失敗パターンを評価に反映するには、次の3種類のテストを組み合わせるのが一般的です。

  • 検索単体テスト: 固定クエリのTop-K結果を期待文書IDと照合。LLM不要でコスト最小
  • 生成単体テスト: 正解文書をコンテキストに固定し生成だけ評価。検索の変動を排除できる
  • End-to-Endテスト: パイプライン全体を通す。本番に最も近いが原因切り分けが難しい

この3種類をセットで保持しておくと、E2Eが落ちたときに検索単体・生成単体の結果から原因を絞り込みやすくなります。

採点器自体の変動に注意

LLMによる採点では、採点プロンプトやモデル版を変えるとスコア自体が揺れることがあります。こうなると回帰か採点ブレか区別がつきません。

この問題を避けるには、採点器のプロンプト・モデル・パラメータをバージョン管理し、比較時は固定しておくと安心です。「回答が変わった」のではなく採点基準が動いただけ——という事故は意外とあります。

もう一つ注意すべきは比較順序バイアスです。pairwise評価(AとBを比較して良い方を選ぶ形式)では、提示順序によって判定が揺れることがあります。対策として、順序を入れ替えて2回評価する、pointwise評価(各回答を単独で採点)に切り替える、複数回実行して集計する、などが使われます。

まずチームのパイプラインで、過去の障害が検索と生成のどちらに集中しているかをログから確認してみると、どこから手をつけるか決めやすくなります。

AIエージェントを企業で運用する際の「何を止めるか・どこで承認を入れるか」という判断軸については、企業でAIエージェントを動かすための運用の判断軸でOpenAI Frontierをベースに整理しています。

評価セット最小テンプレ: 日本語ケースの作り方

失敗パターンが決まったら、次はそれを検知するための評価セットが必要です。最初から数百件を揃える必要はなく、まずは20〜50件の質の高いケースから始めると運用に乗せやすくなります。

ここでは日本語の最小評価セットを5つの観点でテンプレ化します。

5観点のケース設計

評価セットに含めるケースは、以下の観点で分類しておくと抜け漏れを防ぎやすくなります。

  • 質問タイプ: 事実質問(1文で答えられるもの)、手順質問(ステップで答えるもの)、比較質問(複数の選択肢を並べるもの)などがあります
  • 禁止系: 答えてはいけない質問への拒否が正しく動くかを確認します。個人情報の問い合わせや社外秘の漏えい誘導などが該当します
  • 難問: 曖昧な質問、文脈不足の質問、索引にない情報への質問を含めます。「わかりません」と返せるかを見ます
  • 期待挙動: 各ケースに「正解」だけでなく「許容範囲」も定義します。完全一致か意味的同等かを明記しておきます
  • 根拠: 回答の根拠となるドキュメントIDやチャンクIDを紐づけます。retrieval の正否判定に使います

実際のケースをYAMLで書くと、たとえば次のような形になります。

case_id: JP-001
query: 年末調整の締切はいつですか
expected_answer: 毎年1月31日までに提出
acceptable_variants:
  - 1/31
  - 1月末
refusal_expected: false
reference_doc_id: doc_hr_0042
tags:
  - factual
  - hr
  - date

日本語固有の落とし穴: 表記揺れ

日本語の評価で厄介なのが「表記の揺れ」です。全角半角(「1」と「1」)、送り仮名(「行う」と「行なう」)、カタカナ長音(「サーバー」と「サーバ」)など、内容は同じでも文字列としては異なるため見かけ上の差分が生まれがちです。

評価セット作成時に、以下のような正規化ポリシーを決めておくと良いでしょう。

  • 比較前に全角→半角、長音の統一などの正規化を入れるか
  • 同義表記(「問い合わせ」と「問合せ」)を許容するか
  • 数値フォーマット(「1,000円」と「千円」)の扱い

このポリシーが未定のまま回帰テストを走らせると厄介です。回答の中身は同じなのに「差分あり」と報告され、チームが混乱する原因になってしまう可能性があります。正規化ルールは評価セットのREADMEに1ページで書き残しておくと、きっと後から参加したメンバーにも伝わりやすいはずです。

三層の採点設計

評価セットのケースをどう採点するかは、三層で設計するのがおすすめです。

第1層: ルールベース: 正規表現・完全一致・キーワード包含で判定できるケースをここで捌きます。禁止ワードの出力チェック、必須キーワードの包含、フォーマット検証などが該当します。ほぼ追加コストなしで再現性が高いのがメリットです。まずここで拾えるものを増やしていくと、後段が楽になります。

第2層: LLMによる採点: 意味的な正否判定が必要なケースに使います。「回答が質問の意図に沿っているか」「根拠と矛盾していないか」といった判定が対象です。採点器のバージョン管理については前述の採点器自体の変動に注意を参照してください。

第3層: 人手スポット: 新ケースの追加時、第2層の判定が割れたとき、四半期ごとの校正に使う層です。全件を人手で見る運用は中長期的には継続するのは困難です。サンプリングと基準の再校正に限定すると良いでしょう。

第1層のカバー範囲を広げるほど、コストと再現性の両面で有利になります。

セキュリティと個人情報: 評価セットに本番データを入れない

評価セットを素早く作りたいとき、本番のチャットログからそのまま質問と回答を抜き出したくなることがあります。しかし実データをそのまま使うと、ユーザー名や社員番号などの個人情報(PII)がテストログやCIの出力に残りかねません。CIログは開発チーム全員がアクセスでき、外部のCI基盤に長期保存されることもあります。

対策としては次の3つが挙げられます。

  • 評価セット作成時に固有名詞・日付・金額をダミーに置換しておく
  • CIログの保持期間と閲覧権限を確認し、個人情報混入時の削除手順を決めておく
  • 評価セットのレビューフローに個人情報チェック項目を1行加えておく

過度に注意する必要はないかもしれませんが、「開発資産だから個人情報が入っていても問題ない」という思い込みは危険です。

まずはチームの本番ログから、よく失敗する質問を5件抜き出し、上の5観点でタグ付けすることから始めてみてはいかがでしょうか。

CIゲート化: 頻度分割・コスト上限・非決定性・キャッシュと記録

評価セットを整えたら、CIゲートとして回さないと変更を見逃しやすくなります。実行頻度・コスト上限・非決定性対策・記録の4点を先に決めておくとスムーズです。

CI実行頻度の分割

全テストをPRごとに回すと、時間もコストも膨らみ形骸化しがちです。ここでは2層に分けるのが良いと考えています。

  • PRトリガー(軽量): コア品質に直結する10〜20ケースだけ回し、失敗ならビルドを落とす
  • 夜間・週次(重量): 全評価セット+セキュリティスキャンをcron等で定期実行し、結果をJSON/HTMLで保存する

PRゲートは「壊れたら止める」役割で、定期実行は「サイレント劣化を拾う」役割です。この分割を先に決めておかないと、実行時間が伸びるたびにテストがスキップされがちです。

コスト上限の決め方

LLM呼び出しを含むCIは、1回ごとにAPI費用が発生します。月額上限の見積もりは、たとえば次のような式で概算できます。

ケース数 × 平均トークン数 × 単価 × 実行回数

この式から、CI環境変数で上限ガードを入れておくと安心です。金額はモデルや頻度で大きく変わるため、まず1週間の実測値を取ってから月次予算を逆算する流れが手戻りが少なくなります。

出力の揺れにどう対処するか

LLMの出力は同じ入力でも揺れることがあります。OpenAIのseedパラメータは「ほぼ同じ出力」を目指す仕組みですが、公式Cookbookでも決定性は保証しないと書かれています。 バックエンド更新でsystem_fingerprint(モデルのバージョンを示す識別子)が変わることもあり、この差分を検知しておくと原因切り分けが速まります。

同一ケースを2〜3回実行して多数決で判定する方法や、閾値に揺れ幅を織り込む方法が実務で使われています。「回答が変わった」という報告が出たとき、本当の回帰かフレークかを分ける仕組みを先に用意しておくと後続タスクが楽になるかもしれません。

キャッシュと記録

キャッシュ(例: promptfoo~/.cache/promptfoo)を活用すれば、同一入力の再実行コストを抑えられます。 ただしキャッシュが効いた状態では揺れ幅の検証になりません。定期実行ではキャッシュ無効化フラグを分けるのが良いでしょう。

キャッシュとは別に、結果のスナップショットをJSONでCI成果物に残しておくと差分比較に使えます。直近5回分をベースラインとして保持しておけば、「いつ壊れたか」の追跡が楽になります。

セキュリティの最小ガード

CIでLLM APIキーを扱う場合、権限は最小限に絞っておくのが重要です。GitHub ActionsならGITHUB_TOKENcontents: readに絞り、必要な権限はジョブ単位で追加する形が安全です。 APIキーはSecretsに格納し、ログ出力のマスク設定も確認しておくとよいでしょう。これはCIが別のサービスになっても同じように考えられます。

なお、LLMによる採点を使う場合は採点器自体のバージョン管理も同様に重要です(詳細は前述の採点器自体の変動に注意を参照してください)。

運用テンプレ: 回帰レポートと落ちた時の切り分け

CIゲートが赤くなったとき、レポートの書式が決まっていないと「で、何が壊れたの?」と聞き返すところから始まりがちです。差分と原因を残すレポート雛形、切り分けチェックリスト、例外運用の型をあらかじめ決めておくと、初動が速くなります。

回帰レポート雛形

レポートに最低限残しておきたい項目として、次の6つが挙げられます。

  1. 差分サマリ(CI失敗直後): どのケースが落ちたか、前回スコアとの差
  2. 再現条件(CI失敗直後): commit SHA、モデル版、評価セットID、seed値
  3. 原因カテゴリ(切り分け後): 検索/生成/採点器/データ/インフラのどれか
  4. 暫定対応(対応決定時): skip指定、閾値の一時緩和、ロールバックなど
  5. 恒久対応(修正PR作成時): プロンプト修正、索引再構築、評価ケース追加など
  6. 更新履歴(各ステップ完了時): 誰が・いつ・何を変えたかの1行ログ

この雛形をIssueテンプレートとしてリポジトリに置いておけば、急なアラート対応でも何から書けばいいか迷わずに済みます。CI結果はJSON等の機械処理用とHTML等の人間用に分けておくと、ゲート判定とレビューを両立しやすくなります。

落ちた時の切り分けチェックリスト

「サイレント劣化」を放置しないために、次の5つの観点で順に確認していくと切り分けがスムーズです。

  1. 検索(retrieval): 期待ドキュメントがtop-k内にあるかを確認します。索引の更新日時とembeddingモデル版も見ておきます
  2. 生成(generation): 検索結果を固定して同じプロンプトを再投入し、出力の揺れ幅を確認します
  3. 採点器: 採点プロンプトやモデル自体が変わっていないかを確認します。評価器の版が動くと回帰か採点ブレか判別できなくなります
  4. データ: 評価セットの最終更新日と件数の増減を確認します。意図しない追加・削除がないかも見ておきます
  5. インフラ: APIタイムアウトやレートリミットを確認します。ログにHTTPステータスやレイテンシが残っているかも見ます

この順番で確認すると、上流から下流へ原因を絞り込みやすくなります。時系列でスコアを追跡し、前回との良化・悪化を比較できる仕組みがあると、さらに切り分けが速くなります。

例外運用の型

回帰テストを「常に全パス」前提で回していると、非決定性や緊急リリースで運用が詰まりがちです。フレークへの対処や例外フローを先に決めておくと、いざというときに慌てずに済みます。

  • フレーク判定: 同一commitで3回再実行し、2回以上パスなら「フレーク」としてskipタグを付与します。週次で棚卸しておくと溜まりすぎを防げます
  • 緊急マージ: セキュリティパッチ等でゲート無視が必要なら、承認者2名のApproveでバイパスします。理由は「暫定対応」欄に残しておきます
  • 閾値の段階的引き上げ: 導入初期はwarningのみにしておき、2週間のベースライン収集後にblockingへ昇格させる流れが無理がありません

ログとシークレットの扱い

CIログには評価入力や生成結果がそのまま出ることがあります。個人情報や機密が含まれる場合、ログ経由の漏えいリスクが生じます。シークレットが未マスクで出力された場合は、ログ削除とローテーションが必要になります。 ::add-mask:: でマスク対象は追加できますが、評価入力全体をマスクするのは難しいため、評価セット自体から個人情報を除外しておくほうが確実です。

直近で失敗した回帰テストがあれば、この雛形に当てはめてみるとチーム向けのカスタマイズ箇所が見えてきます。

よくある質問

評価セットは最初から何件必要?
最初から数百件が必須というわけではありません。まずは20〜50件の質の高いケースで回し始めることが良いかもしれません。本番ログから失敗しやすい質問を抜き出し、質問タイプ・禁止系・難問・期待挙動・根拠の5観点でタグ付けしていくところから始めるのがおすすめです。
LLM-as-a-judge(LLMによる自動採点)は信頼できる?
便利ですが万能ではありません。採点モデルやプロンプトを変えると基準自体が揺れます。採点プロンプトのバージョンと使用モデルは固定し、変更ログを残しておくと切り分けがしやすくなります。人手スポットと組み合わせて定期的に校正する運用が良いでしょう。
非決定性でテストが不安定になるのでは?
seed(出力の再現性を高めるためのパラメータ)を指定しても完全な決定性は保証されないため、同一ケースを2〜3回実行して多数決で判定する方法や、閾値に揺れ幅を織り込む方法が実務で使われています。フレーク(偶発的な失敗)判定のルールは先に決めておくと運用が楽になります。
CIの実行コストはどう抑える?
PRゲートはコア品質に直結する10〜20ケースに絞り、全評価セットは夜間・週次の定期実行に回すのが現実的です。キャッシュも活用できますが、揺れ幅の検証時はキャッシュ無効化フラグを分けておくと混乱が減ります。

おわりに

回帰テストをCIに載せても、評価セットが現場の失敗パターンを反映していなければゲートは形だけになりがちで、表記ゆれの正規化ポリシーが曖昧なまま運用を始めると見かけの差分に振り回されることもあります。日本語固有の落とし穴も含め、一度作って終わりではなく継続的に手を入れていく必要がある領域だと感じています。

本番ログから失敗しやすい質問を5件ほど抜き出して評価セット雛形に当てはめるところから始めると、チームに合った形が見えて来るかもしれません。

PASHでは、既存の回帰テスト設計のレビューも行っています。具体的な支援内容についてはサービス紹介ページをご覧ください。

更新履歴(最終更新: 2026年2月12日
    • 関連記事への参照を追加
    • 初回公開

この記事をシェアする

著者プロフィール

大崎 一徳 / エンジニア

中小企業向けに、AI導入・業務自動化・ツール開発を支援しています。 PoC から本番運用まで一貫して伴走し、「現場で使われ続ける仕組み」をつくることを大切にしています。

Udacity Deep Learning Nanodegree 修了
日本ディープラーニング協会(JDLA)主催 第1回ハッカソン GPU Eater賞受賞(チーム ニューラルポケット)

関連ガイド

関連記事

RAGナレッジAI・業務自動化のご相談はお気軽に

現状の課題整理から公開後の運用まで、状況に合わせて丁寧にサポートします。