AIコーディングツールやRAG(検索拡張生成、社内文書などを検索して回答に反映させる仕組み)を使ったアシスタントを開発しているエンジニアに向けた内容です。テスト段階では良い回答に見えても、本番投入後に事実と異なる回答(ハルシネーション、もっともらしい嘘)を出すケースへの対処を整理します。手を動かして確認できる部分まで含めていますので、判断材料になれば幸いです。
何が起きるか
RAGベースのアシスタントを開発し、手動で5〜10問ほど質問して「それっぽい回答だから大丈夫」と判断してリリースするケースがあります。
この状態で本番投入すると、存在しない料金ポリシーを自信満々に引用したり、競合他社のドキュメントにある数値を自社のAPI制限として答えたりする事象が起きます。
問題は、こうした不具合が数百人規模のユーザーに露出してから発覚する点です。原因を遡ると「自動化された評価が一切なかった」という結論に行き着くケースが典型的です。テストプロセスが「質問して回答を読んで親指を立てる」だけだった、という状態です。
なぜ起きるか
原因を分解すると、大きく3つの段階に分かれます。
1つ目は、評価基準がMMLUやHellaSwagのような学術ベンチマーク(AIモデルの一般的な知識・推論力を測る標準テスト)に寄っていて、自社のユースケースに合っていない点です。学術ベンチマークは汎用的な「賢さ」を測るもので、「自社の料金体系について正確に答えられるか」は測れません。
2つ目は、評価が人間の目視(vibe check、雰囲気で良し悪しを判断すること)に依存していて、CI/CD(コード変更のたびに自動でテスト・デプロイを行う仕組み)に組み込まれていない点です。プロンプトを1行変えるたびに人間が5問読み直すのは現実的ではなく、劣化に気づくのが遅れます。
3つ目は、評価に使うテストケース(ゴールデンセット)が固定化・非管理状態にある点です。バージョン管理されておらず、エッジケース(境界条件・例外的な入力)が網羅されていないと、本番で初めて遭遇するパターンに弱くなります。
この3つが重なると、「テストでは良く見えるのに本番で壊れる」状態が定着してしまいます。
自分のプロジェクトが該当するか確認する方法
以下の質問に答えてみてください。1つでも「いいえ」があれば、後述の対策の対象になります。
- RAGやLLM機能のリリース判定に、人間の目視以外の自動チェックがあるか
- テストケースがコード管理下(Gitなど)にバージョン管理されているか
- プロンプトやモデルを変更したときに、回帰(既存の正しい回答が壊れること)を検知する仕組みがあるか
- CIパイプライン上で評価が実行され、閾値未達でPRをブロックできるか
- 「回答が検索結果(コンテキスト)と矛盾していないか」を機械的にチェックしているか
リポジトリを確認する場合は、.github/workflows 配下にLLM出力を検証するジョブが存在するか、tests/ や eval/ ディレクトリにゴールデンセット(正解付きテストケース集)があるかを見てください。存在しない場合、評価は人手のみで行われている可能性が高いです。
対策の手順
評価パイプラインは、テストケース・評価対象LLM・判定者(Judge)・メトリクス集計の4層構造で組み立てます。
1. テストケースを構造化する
まず「入力・期待値・タグ」を持つデータ構造でテストケースを定義します。タグには edge-case(境界事例)や long-context(長文脈)などを付け、後で層別分析できるようにします。
@dataclass(frozen=True)
class TestCase:
id: str
input: dict
expected: dict | None = None
tags: list[str] = field(default_factory=list)2. Judge(判定者)を複数用意する
1種類の判定基準だけでは不十分です。オープンソースの評価ライブラリRAGAS(RAG専用の評価フレームワーク)が提供するFaithfulness(回答が検索結果と矛盾していないか)やAnswer Relevance(質問との関連性)に加えて、以下のような判定者を組み合わせます。
| Judge | 目的 | 種類 | 合格閾値 |
|---|---|---|---|
| Faithfulness | 回答が検索結果と矛盾していないか | LLM判定 | 0.8 |
| Instruction Following | プロンプトの制約を全て満たしているか | LLM判定 | 0.9 |
| JSON Schema | 構造化出力が正しい形式か | 決定的判定 | 1.0 |
| Safety | 個人情報・有害表現・ポリシー違反がないか | LLM判定 | 1.0 |
| Domain Expert | 専門領域(医療・法務・金融等)の正確性 | LLM判定(few-shot) | 0.85 |
Faithfulness Judgeの実装イメージは以下の通りです。LLMに0〜1のスコアと合否、理由を構造化データとして返させます(instructorのようなライブラリでPydanticモデルにパースする方法が一般的です)。
class LLMJudge(Judge):
async def evaluate(self, test_case, response):
client = instructor.from_openai(AsyncOpenAI())
class Output(BaseModel):
score: float = Field(ge=0, le=1)
reasoning: str
passed: bool
result = await client.chat.completions.create(
model="gpt-4o-mini",
response_model=Output,
temperature=0.0,
)
return EvaluationResult(...)temperature(生成のランダム性を制御するパラメータ)は0に固定し、判定結果のブレを抑えます。
3. CIに組み込み、PRをブロックする
評価結果をGitHub ActionsなどのCIジョブに載せ、閾値未達のPull Requestをマージ不可にします。速度が重要なので、夜間バッチではなくPR単位で数十秒〜数分で回る規模に抑えます。テストケース数が増えすぎて重くなる場合は、concurrency を指定して並列実行数を制御します(例では最大10並列)。
4. ゴールデンセットを育てる
本番で見つかった不具合は、そのままテストケースとして追加します。「請求サイクルに関する質問で存在しないポリシーを引用した」といった実際の事例をエッジケースタグ付きで登録し、二度と同じ回帰を見逃さない仕組みにします。
まとめ
RAGアシスタントの目視テストは、質問と回答のパターンを網羅できず、本番投入後に事実誤認を見逃す原因になります。
確認すべきは、テストケースがバージョン管理されているか、CI上で自動評価が走っているか、回答とコンテキストの矛盾を機械的にチェックしているかの3点です。
対策としては、Faithfulnessを中心とした複数Judgeによる評価と、閾値未達でPRをブロックするCI連携、そして本番で見つかった不具合をゴールデンセットに追加し続ける運用が実務的な出発点になります。
まずは手元のリポジトリで eval/ ディレクトリやCIワークフローの有無を確認し、評価が人手だけに依存していないかをチェックしてみてください。