
本番ログからLLM回帰テスト用データセットを作る最小構成|収集からCI接続まで
LLM(大規模言語モデル)やエージェントを本番運用していると、「昨日は動いていたのに、今日はズレる」場面に出くわすことがあります。プロンプトを1行変えた・モデル版が上がった・索引を更新した、といった変更の直後に回答の当たりがズレる...ログを追っても、モデル起因なのかプロンプト起因なのか一発では切り分けられず、結局ロールバックに頼った経験があるチームは少なくないのではないでしょうか。
手動で失敗例を集めてテストケース化する運用は、トラフィックが増えた時点で続かなくなる傾向があります。失敗をテスト資産に変え るなら、手で集め始める前に「本番ログから失敗候補を自動抽出し、評価データセットへ変換してCIゲートに接続する」レールを先に敷くほうが、チームの負荷を下げやすいと言えます。
この記事では、以下の6工程からなる最小構成を、PII(個人識別情報)マスキングやアクセス制御の設計観点も含めて整理します。
- 収集 ― trace_idとバージョン情報の付与
- 抽出 ― 決定的シグナルのフィルタ
- 正規化 ― 再実行できる評価レコードへの変換
- ラベリング ― 最小の人手で合否を回す設計
- データセット化 ― 重複排除・版管理
- CI接続 ― PRゲートと夜間バッチの二段構え
結論:本番ログを回帰テスト資産に変える最小構成
最小構成の定義(6つの成果物)
ここで扱うのは「本番ログを回帰テストに変えるには、最低何を作ればよいか?」です。
次の6つの成果物が揃えば、本番ログから評価データセットを経てCIゲートまでの導線が回る状態になるはずです。
- 収集基盤 ― 本番ログにtrace_idとバージョン情報を付与し、再現に必要な入出力を構造化して残す仕組み
- 抽出ルール ― エラー・タイムアウト・スキーマ違反など、決定的な失敗候補を自動で拾うフィルタ群
- 正規化パイプライン ― ログ断片を「再実行できる1件 (評価レコード)」に変換する処理
- ラベリング運用 ― 合否判断を最小の人手で回すためのレビューフローと合意基準
- 評価データセット ― 回帰テスト用にフォーマット・分割・重複排除されたケース集合
- CIゲート接続 ― PRマージや定期実行で評価を走らせ、基準未達をブロックまたは警告する仕組み
評価(eval)は軽量なE2Eテストの枠組みとして、OpenAIのエージェント評価ガイドでも説明されています。
- prompt(入力)
- captured run(実行記録)
- checks(検証)
- score(採点)
要は「実行を記録して、ルールで採点する」という流れを、上の6つの成果物で最小限に組み立てるイメージです。
最初から全工程を完成させる必要はありません。まず収集と抽出だけ通し、ラベリングとCI接続は段階的に育てる形で進めると無理がありません。高度な自動抽出、重み付け、レビューUI、データ品質監視といった拡張は、最小構成が回ってから検討すれば十分です。特定のベンダーやツールに依存しない一般的な設計観点を中心に、以降のセクションで各工程を掘り下げます。
最小を支える前提(用語・責任・境界)
ここで扱うのは「なぜ実装より先に、用語と責任と境界を固めるのか?」です。
本番ログを評価資産に変える作業は複数 のロール(PdM・テックリード・運用・情シス)にまたがります。用語がずれたまま進めると、「失敗」の定義一つで手戻りが発生しがちです。
最低限この3つを先に合意しておくと、後工程がスムーズになります。
- 用語定義 ― 「失敗ケース」「評価レコード」「回帰テスト」「品質ゲート」など、記事内でも使う用語の意味をチーム内で1行ずつ揃えます
- 責任分界 ― 誰が抽出ルールを決め、誰がラベリングし、誰がゲート基準を承認するか。最小構成では「決める人」と「回す人」の2ロールで始められます
- データ境界 ― 何を残し何を捨てるか。とくにPIIマスキングや機密データの扱いは、ログ設計の時点で方針がないと後から修正コストが跳ね上がります
「目的定義→データセット収集→メトリクス定義→評価実行→継続評価」という流れはOpenAIの評価ガイドでも示されており、最初の「目的定義」を飛ばすと後工程が空回りしやすい構造になっています。
ここで決めた前提はあくまで初期合意です。運用が回り始めれば定義は更新されていくものなので、最初の完璧さよりも「合意が存在する状態」を作ることが優先になります。
作っておくと安心なチェックリスト
ここでは「結局どこから手をつければよいのか?」を整理 します。
目的としては、最低限の導線が動いている状態をゴールに、手順の順序と到達点を決めることです。
以下の順序で進めると手戻りが少ないはずです。
- 用語・責任・境界の合意 ― 用語定義・責任分界・データ境界の3点を先に固める
- 収集の最小設定 ― trace_id付与とバージョン情報の記録を既存ログ基盤に追加(ログ設計の時点で、後から評価入力を再構成できる粒度を確保しておくのが重要です。具体的にはprompt、取得コンテキスト、ツールI/O、モデル設定など)
- 抽出ルールの初版 ― 例外・タイムアウト・スキーマ違反など決定的シグナルだけでフィルタを組む
- 正規化とラベリングの素振り ― 抽出された失敗候補を5〜10件だけ手で評価レコードに変換し、合否の判断基準を仮決め
- CIへの仮接続 ― 評価スクリプトをCIパイプラインに組み込み、まずは警告のみで回す(ブロッキングへの昇格は運用データが溜まってから)
開発段階からログを十分に取得し、ログから評価ケースを採掘できる状態を早期に作ることは、OpenAIの評価ベストプラクティスでも推奨されています。 手動で失敗例を都度集める運用より先に、この導線を敷いておくほうが、チームの負荷を下げやすい傾向があります。
到達点の目安は「本番で起きた決定的な失敗が、翌日のCIで警告として表示される」状態です。精度や網羅性の改善はこの最小ループが回ってから段階的に進めます。
なぜ手動の失敗ケース収集は続かないのか
でも、そもそもなぜ「人が気づいたら報告する」運用では失敗ケース抽出が早い段階で止まるのか、その構造的な理由を整理します。
出力が揺れる・壊れても気づきにくい
LLMを組み込んだ機能が厄介なのは、非決定的(同じ入力でも出力が毎回揺れうる)な点です。OpenAIのChat Completions APIドキュメントでも、デフォルトでは出力が非決定的であると明記されています。
再現テストの前提が「同じ入力→同じ出力」に依存していると、LLMベースの機能では成り立ちません。
もう一つ押さえておきたいのがサイレント回帰です。モデル提供側の更新やインフラ変更によって、こちらが何も変えていないのにアプリの挙動が変わる現象を指します。2026年のarXiv研究でも、LLMプロバイダのモデル更新がアプリ挙動を予告なく変え、ユーザー苦情で初めて検知される事例が報告されています。
非決定性とサイレント回帰は「たまに起きる例外」ではなく、LLMを使う以上は常態と考えたほうが自然です。この前提を共有しないまま手動収集を始めると、「前は動いていたのに」という報告が増えるだけで原因の切り分けが進まない状況に陥りがちです。
失敗の定義は「次のアクションが決まる粒度」で
失敗ケース抽出で最初にぶつかるのは、「何をもって失敗とするか」という線引きです。失敗分類(taxonomy)は次のアクションが決まる粒度から始めるのが実用的です。
LLMの出力は「正解/不正解」の二値で割り切れない場面が多くあります。たとえば、回答内容は正しいがフォーマットが崩れている、情報は網羅しているが冗長すぎてユーザーが離脱した、といったケースです。「不正解」とだけ記録しても、プロンプトを直すのか、後処理を変えるのか、モデルを切り替えるのかが決まりません。
まずは「このラベルが付いたら、チームの誰が・何を・どう直すか」が1つ決まる粒度を目指すとやりやすくなります。粒度が細かすぎるとラベリングコストが跳ね上がり、粗すぎると対処が曖昧なまま放置されがちです。わずかなプロンプト変更でも性能が大きく変動し得る以上、「プロンプト起因」「モデル起因」「外部データ起因」くらいの軸は初期段階で分けておくと、対処の振り先が明確になります。
失敗定義は一度決めたら終わりではなく、運用しながら粒度を見直す前提で始めるくらいがちょうど良いでしょう。最初から完璧な分類体系を目指すと、定義の議論だけで数週間が過ぎることもあります。
失敗ケースの手動収集が破綻する3要因(再現・優先度・属人化)
手動で失敗を集める運用がなぜ数週間で止まるのか。再現待ち・優先度迷子・属人化の3つが連鎖して運用が停止するパターンが多いです。
再現待ち: 非決定性がある以上、「さっき起きた失敗をもう一度出してください」と言われても再現できないことがあります。ログはあるけど原因追えないという声が現場で出るのは、入力だけ残っていても実行時のモデル版やコンテキストが記録されていないからです。再現できないチケットは優先度が下がり、そのまま塩漬けになりがちです。
優先度迷子: 手動報告が増えると、「どの失敗から対処するか」の判断基準がないまま滞留しがちです。影響範囲もユーザー数も把握できていない状態では、声の大きい報告者のチケットが先に対処され、実際の影響度とは乖離した優先順位になることがあります。
属人化: 失敗かどうかの判断が特定メンバーの暗黙知に依存し始めると、その人が不在のときに判断が止まります。トレースがないと議論が進まないのと同じ構造で、判断基準が共有されていなければ、報告しても「これは失敗なのか仕様なのか」で議論が空転します。
この3要因は独立で はなく連鎖することもあります。再現できないから優先度が決まらない、優先度が決まらないから誰も手を出さない、手を出す人が固定化して属人化する——この循環に入ると、手動収集は自然消滅します。
例外:手動でやって良い場面とやめる判断
「失敗ケースの手動収集はすべて悪」というわけではありません。本番トラフィックが少ない初期段階やPoC段階では、手動で十分なケースもあります。
週に数件しかリクエストがない機能であれば、失敗を1件ずつ目視で優先度判断(トリアージ)しても運用は回るはずです。チームが2〜3人で、全員がプロンプトと出力の文脈を共有できている状態なら、属人化のリスクも限定的です。
ただし、以下のサインが出たら手動運用の限界が近いと言えるでしょう
- 同じ失敗パターンが2回以上報告されているのにテストケース化されていない
- 報告チケットが1週間以上未処理で溜まっている
- 「これは失敗か仕様か」の判断で毎回同じ人に聞きに行っている
こうした兆候が複数重なったタイミングが、自動抽出のレールを敷き始める判断ポイントです。
手動→自動の切り替えは段階的で構いません。最初は決定的な失敗(例外やタイムアウト)だけ自動抽出し、判断が必要なケースは引き続き手動でトリアージする、という混在期間を設けると、移行の負荷を分散できます。
全体像:本番ログから回 帰テストになるまでの流れ
失敗ケースの手動収集が続かない構造を確認したうえで、次は失敗ケースを回帰テスト資産に変える6工程の入出力のつながりを整理します。収集からCIゲートまでの流れを、各工程の役割と受け渡し単位で見ていきます。
収集:trace_idとバージョンをまず通す
では、後工程で失敗を再現・分類するために、最初に何を通しておけばよいのでしょうか。
すべてのリクエストに相関ID(trace_id)と版情報(モデル名・プロンプトハッシュ・ツールバージョン)を付与するのが起点になります。失敗を見つけたあとに「どの版で何が起きたか」をたどれないと、抽出しても原因が追えないためです。
「トレースがないと議論が進まない」という声が現場で出がちですが、裏を返せばtrace_idと版情報さえあれば議論の土台ができます。LangSmithでは本番トレースからデータセット例を生成するパターンが一般的とされており、その前提としてトレーシング設定が必要だと説明されています。
具体的には、まずリクエストの入口でtrace_idを発行し、モデル呼び出し・ツール呼び出し・レスポンスの各ステップに同じIDを伝搬させます。版情報はデプロイ時に環境変数やタグとして埋め込んでおくと、ログ検索だけで絞り込めます。
なお、trace_idにユーザーIDやセッション情報を直接埋め込まない設計が無難です。相関IDはあくまで技術的な紐付けに使い、個人識別情報は別レイヤーで管理する形にしておくと、後からPII対応で困りにくくなります。
抽出:決定的シグナル+層化サンプリング
ここで扱うのは「どのログを失敗候補として拾い、どのログを正常サンプルとして残すか」です。
抽出は2系統に分けて設計するとやりやすいです。1つは決定的シグナル(例外・タイムアウト・スキーマ違反など、判断に迷わない失敗)の全量取り込み。もう1つは、正常応答を含む全トラフィックからの層化サンプリングです。
決定的シグナルだけでは「静かに劣化しているが例外にはならないケース」を取りこぼすためです。正常応答の中にも品質が低下した応答が混ざることがあり、分布を維持したサンプルがないと比較の基準がずれていきます。たとえばDatadogは本番トレースからデータセットを直接生成し、現実のシナリオで変更を検証できる機能を提供しています。こうしたツールの活用を視野に入れつつも、抽出ルール自体はツール非依存で設計しておくと移行が楽です。
サンプリング比率は最初から完璧を目指さなくて大丈夫です。まずは決定的シグナルの全量+正常応答の一部で始め、レビュー負荷を見ながら調整していく形がやりやすいです。比率の初期値はチームのレビュー 可能件数やコスト上限から逆算し、ベースライン計測後にキャリブレーションする流れが自然です(一例として1〜5%程度から始める方法がありますが、環境依存です)。オンライン評価(本番中のリアルタイム監視)は「異常検知」に寄せ、オフライン評価(回帰テスト)は「再現と比較」に寄せる、という役割分担を意識すると設計がぶれにくくなります(LangSmithの評価ドキュメントに詳しく記載されています)。
正規化:評価レコードという最小単位に落とす
「ログ断片をどう加工すれば、再実行できる1件になるか」を整理します。
正規化の目標は「評価レコード」と呼べる最小単位を作ることです。評価レコードとは、入力(プロンプト+取得コンテキスト)・出力(モデル応答)・期待結果または判定基準・版情報(モデル/プロンプト/ツール/索引バージョン)を1件にまとめたものを指します。
ログのままだと形式がバラバラで、CI上で自動実行する際に毎回パースロジックが必要になるためです。形式を揃えておけば、評価スクリプトは1種類で済みます。Vertex AIのGen AI evaluationでもルーブリック(pass/fail)による評価を提供しており、評価データセットを本番ログからサンプリングして作成できることが示されています。
具体的には、抽出済みログから入力・出力・版情報を取り出し、評価レコードのスキーマに変換します。外部API呼び出しの結果やRAGの取得結果がある場合は、それも根拠(context)として同梱します。
ここで気をつけたいのが、ログ設計の時点で「評価入力を再構成できる粒度」を取っていないと、正規化の段階で情報が足りず再実行できないケースが起きる点です。プロンプト全文・取得コンテキスト・ツールの入出力・モデル設定(temperatureやseed)を収集段階で残しておくことが、正規化を成立させる前提条件になります。
CI:PR用と夜間用の二段構えにする
品質ゲートをCIに接続するとき、速度とコストと誤検知のバランスをどう取るかが悩みどころになりがちです。
PR(プルリクエスト)マージ時に回す軽量チェックと、夜間やリリース前に回すフルセット評価の二段構えが基本形です。
評価データセット全件をPRのたびに回すとコストと待ち時間がかさみ、開発者がゲートを迂回し始めるためです。PRゲートでは件数を絞った高速チェック(致命的な回帰の検知)に集中し、フルセット評価は夜間バッチやリリース候補ビルドで回す形にすると、速度を犠牲にせず網羅性も保てます。
PRゲートにはコアケース(過去に致命的な失敗が出たケース)を件数で絞ったサブセットを割り当てます(一例としてレビュー可能件数から逆算して30〜50件程度をたたき台にする方法がありますが、環境依存です)。夜間バッチには全件セットを回し、結果をダッシュボードに集約します。どちらも「ブロッキング(マージ/リリースを止める)」と「警告(通知だけで止めない)」を分けておくのが運用上の基本です。
最初から厳格なブロッキングにすると誤検知でリリースが止まり、ゲート自体が形骸化する傾向があります。段階導入として、まずは警告のみで始め、誤検知率が安定してからブロッキングに切り替えるのが無難です。責任分界としては、ゲート基準の設定はPdM/テックリードが持ち、誤検知のトリアージは運用チームが担う形にすると意思決定が詰まりにくくなります。フレーク対策や対応手順の詳細は「CI/ゲート接続」セクションで扱います。
収集:残す項目と残さない項目(PII/機密)
全体像で工程間のつながりを確認したので、最初の工程である収集の設計に入ります。ログ設計で問われるのは「何を記録するか」ではなく「何を記録しないか」です。PIIマスキングや機密除外の方針が曖昧なまま収集を始めると、後工程の抽出・正規化で使えないログが溜まるか、逆にインシデントの種を自ら仕込むことになります。ここでは、失敗の再現と原因切り分けに必要な最小メタデータと、ログに残してはいけないデータの線引きを整理します。
残すログ項目:後から再現できる最小メタデータ
「ログはあるけど原因追えない」とい う状況を避けるために、再現と原因切り分けに必要な項目を先に決めておきたいところです。
ここでの対象はLLM/エージェントの1リクエスト(1トレース)単位の記録です。以下の粒度が最小ラインになります。
再現に必要
- trace_id / span_id: リクエスト全体と各ステップを一意に紐づける相関ID
- 入力 + 出力: ユーザークエリ、取得コンテキスト、モデル応答
- 版情報: モデル名・バージョン、プロンプトテンプレートのID+ハッシュ
あると強い(原因切り分け・コスト可視化)
- タイムスタンプ: リクエスト開始・終了
- ツール呼び出しの入出力: 外部APIや検索を呼んだ場合の引数と返却値
- トークン消費量 / レイテンシ / seed値: コスト異常検知、非決定性の幅を狭めるため
ログ設計の時点で「評価入力を再構成できる粒度」を取っていないと、失敗を抽出してもテストケースに変換できません。特にプロンプトテンプレートの版情報とツールI/Oは見落とされやすい項目です。
入力・出力にユーザーの個人情報が含まれる場合もあります。この扱いは後述のPIIマスキングで処理するため、ここでは「記録対象として必要」という判断と「そのまま保存してよいか」の判断を分けて考えておくとスムーズです。
残さない項目:ログに入れると危険なデータ
ログに直接書き込むと危険なデータは何か。
ログ基盤は運用チーム・開発チームなど複数のロールがアクセスする共有領域です。以下のデータはログに平文で記録しない設計が原則になります。
- アクセストークン / APIキー / パスワード: 流出時の影響が即座に発生する認証情報
- 暗号鍵 / 署名鍵: 暗号化の意味を失わせるデータ
- 機微な個人情報: 医療記録、金融口座番号、マイナンバーなど、法令やポリシーで保護対象とされるもの
- セッショントークン / Cookie値: 再利用によるなりすましリスクがあるもの
アクセストークンや機微PII、パスワード、暗号鍵などはログに直接記録せず、削除・マスク・サニタイズ・ハッシュ化・暗号化等で扱うことがOWASP Logging Cheat Sheetでも推奨されています。
注意点として、「機微かどうか」の判断は組織のデータ分類ポリシーに依存します。一律のリストで済むものではないため、データ分類の基準を先に決め、ログ設計に反映する順序が自然です。
個人情報(PII)/機密の防波堤:書き込み時マスキング(write-side)と二層保管
「読み出し時にマスクすればいい」という設計と「書き込み時にマスクする」設計では、リスクの性質が変わります。ここではその違いと、二層保管の考え方を整理します。
PII マスキングのタイミングは大きく2つ、read-side(閲覧時)と write-side(書き込み時)に分かれます。永続化前の write-side でマスキングする設計のほうが、結果としてリスクを小さく保ちやすくなります。
read-side マスキングだと、ログ基盤に生データが残るため、アクセス制御の不備やバックアップ経由での漏洩リスクが残りやすい傾向があります。write-side であれば、永続化された時点で機微情報が除去されているため、基盤側の防御が破られた場合でもダメージを限定しやすくなります。
ただし、write-sideマスキングだけでは「マスク前の生データが必要になるケース」(重大インシデントの調査など)に対応できません。そこで二層保管という設計が考えられます。
- sanitized層: マスキング済みのログ。開発・運用チームが日常的にアクセスする領域
- raw層: マスキング前の生ログ。アクセスを厳しく制限し、保持期間を短く設定する領域
raw層は「存在するが、普段は誰も触れない」状態にするのが設計意図です。保持期間は組織のポリシーや規制要件で異なりますが、「必要最小限の期間だけ保持し、期限到来で自動削除する」運用が一般的です。
ただし、二層保管はストレージコストと運用負荷が増える面があります。すべてのログに適用するよりは、機微データを含む可能性があるログ種別に絞って適用するほうが運用しやすい傾向があります。
アクセス制御と監査:誰がどこまで見られるか
ログを保管しても、閲覧 権限が適切でなければ防波堤として機能しません。「誰がどの層のログをどこまで見られるか」を設計項目として最初に決めておくとぶれにくくなります。
ログ基盤へのアクセスは「見る」「書く」「消す」の3操作に分かれます。NIST SP 800-92の推奨に沿えば、ログファイルへのアクセスを制限し、必要な場合でも追記(append-only)権限とし、閲覧権限は可能な限り絞る設計がよいでしょう。
具体的な設計項目として、以下を最初に固定しておくと運用がぶれにくくなります。
- 最小権限の原則: sanitized層は開発・運用チーム、raw層はインシデント対応の限定メンバーのみ
- 閲覧権限の定期見直し: 四半期ごと、または組織変更時に棚卸しする運用ルール
- 監査ログ: 誰がいつどのログにアクセスしたかを別経路で記録する
- 書き込み権限: ログ基盤への書き込みはアプリケーションのサービスアカウントに限定し、人間の直接書き込みは原則禁止
監査ログ自体の保護も忘れやすいポイントです。監査ログを改ざん・削除できる権限が広く開いていると、監査の意味が薄れてしまいます。別の保管先に追記専用で書き出す設計が安心です。
相関ID(trace_id)に混ぜてはいけない情報
相関ID(trace_id / span_id)はリクエストの追跡に不可欠ですが、IDそのものに何を含めるかには制約があります。ここで押さえておきたいのは「相関IDに個人識別情報や機微情報を混ぜない」という点です。
traceparent(W3C Trace Context)はサービス間をまたいで伝播するヘッダです。W3C Trace Context Level 2のプライバシー考慮では、traceparent / tracestate の目的はトレース相関であり、個人識別情報や機微情報を入れてはならないと書かれています。
traceparent はログ基盤だけでなく、中間プロキシ、CDN、サードパーティサービスなど想定外の場所に記録される可能性があります。ユーザーIDやセッションIDをtrace_idにエンコードする設計は、意図しない経路で個人を特定できる情報が拡散するリスクを生みます。
相関IDはランダムまたは擬似ランダムな値(UUIDv4など)を使い、ユーザーとの紐づけはログ基盤内部のインデックスで行う形が一般的です。「trace_idからユーザーを引ける」必要がある場合でも、その対応表はログ基盤内部に閉じ、traceparent自体には含めない設計にしておくと安心です。
tracestate にカスタムキーを追加する場合も同様の原則が適用されます。ベンダー固有のキーに機微情報を含めないよう、設計レビュー時にチェック項目として入れておくと見落としを減らせます。
抽出:失敗候補を自動で拾う最小ルール
収集で残すログ項目とPII/機密の線引 きが決まったら、次は溜まったログから失敗候補を自動で拾う抽出ルールの設計です。起点をどこに置くかで、レビュー負荷と見逃し率が大きく変わります。人手判断が不要な「決定的シグナル」から抽出を立ち上げ、運用が安定してからユーザー由来やコスト異常のルールを足していく順序が無難です。
まずは決定的な失敗から(例外・タイムアウト・スキーマ違反)
判断に迷わない失敗をどう拾うか。ここでは決定的シグナル、つまり条件判定だけで失敗と断定できるイベントを対象にします。具体的には、未処理例外(HTTP 5xx・ランタイムエラー)、タイムアウト超過、出力スキーマ違反の3種が代表的です。
これらはモデルの出力品質に関係なく「システムとして壊れている」状態なので、人がログを読んで判断する工程を省けます。抽出ルールの第一層はこの3種のフィルタだけで十分に立ち上がります。
誤検知がほぼゼロという点が大きいのではないでしょうか。例外が投げられた、レスポンスが返らなかった、JSONスキーマに合わなかった、といったケースは「失敗ではなかった」と覆ることがほとんどありません。Google SREが監視のゴールデンシグナルとしてErrors(明示的エラー)を挙げているのと同じ考え方で、まず確実に拾える層から固めていくのが自然な流れです。
注意点として、スキーマ違反の検知にはスキーマ定義そのものが手元にある状態が出発点です。出力のJSON Schemaやツール呼び出しの型定義がコード上で管理されていない場合は、先にスキーマを明文化する作業から始めることになります。
ユーザー由来シグナル(再試行・低評価・エスカレーション)を拾う
決定的シグナルだけでは、「動いたけれど役に立たなかった」ケースを取りこぼします。ユーザーの行動から失敗を推定できないか、という発想です。UIや運用フローに埋め込まれたユーザー行動、たとえば再試行(リトライ)、低評価(「役に立たなかった」ボタン等)、人間へのエスカレーションは、その取りこぼしを補う第二層になります。
これらのシグナルは「失敗の可能性が高い」だけで確定ではありません。再試行はネットワーク都合かもしれず、低評価は期待値のズレかもしれません。ユーザー由来シグナルは「失敗候補キュー」への投入に使い、最終判断は人またはルールベースのレビューに委ねる設計がやりやすいです。
LangSmithのrun rulesのように、フィードバックスコアが低い場合にトレースをデータセットへ自動追加する仕組みは、この第二層の実装パターンとして参考になります。
まずUIにフィードバック機能(「役に立った/立たなかった」ボタン、再生成ボタン)を入れ、そのイベントをtrace_idと紐づけてログに残します。エスカレーション(チャットボットからオペレーターへの切り替え等) も同様にイベント化します。これらのイベントが発火したトレースを抽出器が候補キューに流す、という形です。
フィードバック機能を実装してもユーザーが押してくれるとは限りません。低評価の収集率が低い環境では、再試行やエスカレーションのほうが信頼できるシグナルになりがちです。どのシグナルの収集率が高いかは環境依存なので、最初は全種類を候補キューに入れておき、レビュー段階でノイズ比率を見て絞る流れが無難です。
コストや消費量の異常も失敗候補にする
正常に見えるのにコストだけ異常な応答は失敗か。ここを整理します。レイテンシの急増、トークン消費量の跳ね上がり、ツール呼び出し回数の異常といった現象は、例外もスキーマ違反も起きていないのに、内部で何かがおかしい状態を示唆します。
SREの現場で使われるゴールデンシグナルにはLatency・Errors・Saturationが含まれています。Errorsは定義次第で「誤った内容」も含められますが、遅延や過剰消費はLatencyやSaturation側で見るほうがわかりやすいです。LLMやエージェントの文脈では、ループ的なツール呼び出しや冗長な推論で応答が膨張するケースがこれに当たります。
統計的な逸脱を検知するルールを加えると、決定的シグナルとユーザー由来シグナルの間を埋められます。具体的には、過去N日間の同 一タスクタイプにおけるp95を超えたトレースを失敗候補キューに入れる、といった形です。
閾値の設定は環境やタスクの種類で大きく変わります。固定値で始めると過検知が増えやすいため、タスクタイプごとのベースラインを一定期間計測してからルール化するほうが運用に乗りやすいです。
過検知と見逃しの扱い(レビューキューと優先度)
抽出ルールの精度をどう運用で受け止めるか。過検知と見逃しはゼロにならない前提で設計し、レビューキューと優先度付けで吸収する形に落とし込むのが無難です。
SREの現場では、障害後に原因と再発防止策を振り返る「ポストモーテム」という慣習があります。ポストモーテムの文化では、ユーザー影響の大きさやデータ損失の有無、復旧にかかった時間などを事前にトリガー基準として定義し、すべてのインシデントを一律に扱わない運用が良いとされています。失敗候補の抽出でも同じ考え方が使えます。
抽出された候補をまずキュー(バックログ)に入れ、以下の優先度で並べます。
- 決定的シグナル(例外・タイムアウト・スキーマ違反): 最優先でレビューに回します
- ユーザー由来シグナル: 影響ユーザー数や発生頻度で重み付けします
- コスト異常: 逸脱度合いでソートし、閾値ぎりぎりのものは後回しにします
「ログはあるけど原因追えない」という状態を避けるには、レビューキューの各項目からtrace_idで元のトレースに即座にジャンプできる導線があるとスムーズです。候補を溜めるだけでレビューされない状態が続くと、抽出ルール自体への信頼が下がり、運用が形骸化します。
見逃し(本来拾うべきだった失敗が候補に入らなかった)への対策は、定期的なランダムサンプリングで補います。本番トレースから無作為にN件を抜き出し、抽出ルールが拾わなかったものに失敗が含まれていないかを確認する作業を週次や隔週で回すと、ルールの穴が見えてきます。
レビューキューの消化率が下がったときに「ルールを緩めて候補を減らす」という手もありますが、根本的な解決にはなりにくいです。まずはレビュー1件あたりの判断コストが高すぎないか(情報不足で判断できない等)を確認し、抽出時に付与するコンテキスト(エラーメッセージ、直前のユーザー入力、影響範囲の概算)を増やすほうが持続しやすいです。
正規化と再現性:評価レコードを再実行可能にする
抽出ルールで失敗候補を拾えるようになっても、そのログ断片が「もう一度同じ条件で走らせられる形」になっていなければ、回帰テストの素材にはなりません。ここでは、抽出した断片を再実行可能な評価レコード1件に変換する正規化の設計を、トレーシングの観点から整理します。