はじめに:プロンプトをいじった後、AIの全回答を手動で確認していませんか?
LAN内の資産管理や情シス業務向けにLLM(チャットボット)を構築し、現場に公開したとします。
ユーザーから「もっと丁寧に答えて」「要約して」と言われ、システムプロンプトを少し書き換えた経験はありませんか?
その直後、恐ろしい悲劇が起きます。
- 「プロンプトを少し変えたせいで、LAN内のPC台数を嘘八百で答えるようになった」
- 「存在しないIPアドレスを自信満々にでっち上げる(ハルシネーション)」
- 「要リプレース端末を聞いたのに、正常な端末を勝手に故障と判定する」
Webアプリのコードであれば単体テスト(Unit Test)を書くのが当たり前なのに、なぜかLLMのプロンプト変更だけは「人間がチャット画面で数回テストしてヨシ!」でリリースされがちです。
AIの事実整合性を担保するには、「実データベースの正解データと照合し、期待される重要キーワードが回答に含まれているかを自動採点するベンチマークスイート」 をCI/CDで回す必要があります。
本記事では、筆者のプロジェクトで実際に運用している 10項目の自動精度評価フレームワーク の設計と実装を公開します。
1. 標準テストケース10項目(TC01〜TC10)の定義
テストケースは、あらかじめ実際のDBの正解値(グラウンドトゥルース)と一致させて定義します。
| テストID | テスト質問 | 検証内容・正解キーワード | 判定基準 |
|---|---|---|---|
| TC01 | 「スマートフォンは何台ありますか?」 | ['4'] |
端末一覧内のスマホ台数(4台)を正しくカウント |
| TC02 | 「NASのメーカー名は何ですか?」 | ['Synology'] |
ストレージのベンダー名を正確に特定 |
| TC03 | 「要リプレースと判定されている端末のIPは?」 | ['192.168.1.236'] |
EoL端末のIPを正確に列挙 |
| TC04 | 「IP 192.168.1.200 のOSは何ですか?」 | ['Ubuntu'] |
対象ホストのOSバージョンを回答 |
| TC05 | 「複合機のIPアドレスとメーカー名を教えて」 | ['192.168.1.233', 'Epson'] |
機器種別からIPとベンダーを同時特定 |
| TC06 | 「全端末の合計台数は何台ですか?」 | ['20'] |
全台数(20台)をハルシネーションせず回答 |
| TC07 | 「Amazon端末のIPアドレスは何ですか?」 | ['192.168.1.227'] |
ベンダー名からIPを逆引き |
| TC08 | 「192.168.1.9 のデバイス分類は何ですか?」 | ['スマート'] |
スマートTV等の種別を回答 |
| TC09 | 「192.168.1.236 のOS判定はどうなっていますか?」 | ['特定不可'] |
不明な端末を強引に推測せず「特定不可」と正しく保留 |
| TC10 | 「Synology製NASの全IPを教えてください」 | ['192.168.1.12', '192.168.1.13'] |
複数存在する機器の全IPを漏れなく列挙 |
特に TC09(不明なものを知ったかぶりせず『特定不可』と答えられるか) は、ハルシネーション抑制の最重要チェックポイントです。
2. Keyword Match 判定アルゴリズムの実装
複雑なLLM-as-a-Judge(判定用LLM)を使うと、判定側のブレやコストが発生します。
CI/CDで高速・安価に回すためには、大文字・小文字を無視した「必須キーワード完全包含(Deterministic Keyword Match)」 が最も確実です。
💻 コア実装・設定スニペット(抜粋)
# コア実装ロジック(要点抜粋)
import requests
import json
import time
TEST_CASES = [
{"id": "TC01", "q": "スマートフォンは何台ありますか?", "expected": ["4"]},
{"id": "TC02", "q": "NASのメーカー名は何ですか?", "expected": ["Synology"]},
{"id": "TC03", "q": "要リプレースと判定されている端末のIPはどれですか?", "expected": ["192.168.1.236"]},
{"id": "TC04", "q": "IPアドレス 192.168.1.200 のOSは何ですか?", "expected": ["Ubuntu"]},
{"id": "TC05", "q": "プリンター(複合機)のIPアドレスとメーカー名を教えてください。", "expected": ["192.168.1.233", "Epson"]},
{"id": "TC06", "q": "全端末の合計台数は何台ですか?", "expected": ["20"]},
{"id": "TC07", "q": "Amazon端末のIPアドレスは何ですか?", "expected": ["192.168.1.227"]},
{"id": "TC08", "q": "192.168.1.9 のデバイス分類は何ですか?", "expected": ["スマート"]},
{"id": "TC09", "q": "192.168.1.236 のOS判定はどうなっていますか?", "expected": ["特定不可"]},
{"id": "TC10", "q": "Synology製のNASのIPアドレスをすべて教えてください。", "expected": ["192.168.1.12", "192.168.1.13"]},
]
API_ENDPOINT = "http://localhost:8000/api/v1/chat"
def run_accuracy_benchmark():
passed = 0
results = []
print("[*] AI事実整合性ベンチマークテストを開始します...\n")
for tc in TEST_CASES:
payload = {"message": tc["q"], "reset_history": True} # 会話履歴を毎回クリア
start_time = time.time()
try:
res = requests.post(API_ENDPOINT, json=payload, timeout=20)
res.raise_for_status()
reply = res.json().get("reply", "")
except Exception as e:
reply = f"ERROR: {str(e)}"
elapsed = round(time.time() - start_time, 2)
# Keyword Match 判定: 全ての期待キーワードが含まれているか
is_pass = all(kw.lower() in reply.lower() for kw in tc["expected"])
if is_pass:
passed += 1
status_mark = "✅ PASS" if is_pass else "❌ FAIL"
print(f"[{tc['id']}] {status_mark} ({elapsed}s) Q: {tc['q']}")
if not is_pass:
print(f" 期待キーワード: {tc['expected']}")
print(f" 実際のAI返答: {reply[:100]}...\n")
results.append({
"id": tc["id"],
"pass": is_pass,
"elapsed_sec": elapsed,
"expected": tc["expected"],
"reply": reply
})
accuracy_rate = (passed / len(TEST_CASES)) * 100
print(f"\n==========================================")
print(f"総合スコア: {passed}/{len(TEST_CASES)} 合格 ({accuracy_rate:.1f}%)")
print(f"==========================================")
# 結果をJSONとして出力
with open("accuracy_report.json", "w", encoding="utf-8") as f:
json.dump({"accuracy_rate": accuracy_rate, "results": results}, f, indent=2, ensure_ascii=False)
# 合格率100%未満ならCI/CDを落とす
if accuracy_rate < 100.0:
exit(1)
まとめ:プロンプトエンジニアリングを「ソフトウェア工学」にする
AIアプリケーションの開発において、最も危険なのは「感覚的なチューニング」です。
- プロンプトを1行修正したら、自動テストを一括実行する
- 10項目の必須要件が1つでも欠落したらプルリクエストをマージさせない
- ハルシネーション(嘘)をCI環境で機械的にブロックする
このテスト自動化スイートを1つ導入しておくだけで、「プロンプトをいじったら他の機能が壊れた」というデグレを100%防止でき、安心して本番環境のAIを改善し続けることができます!
📚 さらに詳しいアーキテクチャ設計・パラメータ表・トラブルシューティング
最後までお読みいただきありがとうございます!
本記事が少しでも「役に立った」「あとで試したい」と感じていただけましたら、ぜひQiitaの 「いいね(♡)」 と 「ストック」、および アカウントのフォロー をいただけると今後の検証・執筆の大きな励みになります!
また、本記事のさらに踏み込んだ 「深層アーキテクチャ解説」「主要パラメータの技術的根拠」「現場アンチパターン・トラブルシューティング対応表」 は、以下のGitHub技術リファレンスにて公開しています。
👉 【第21弾 詳細技術リファレンス (QIITA_ARTICLE_DETAIL_21.md)】
⭐ 全14層・200パターンの自立型インフラ&オンプレAIリファレンス集(おじさんの武器庫)を順次公開中です。ぜひGitHubリポジトリへの Star (★) と X (@OjisanTechnical) のフォローもお願いいたします!
👉 GitHub リポジトリ: ojisantech/ojisan-arsenal
💡 【検証環境・個人研究(PoC)に関する注記】
本記事は、筆者個人の自宅検証環境(ホームラボ/仮想環境)において、公開情報およびOSS(オープンソースソフトウェア)を用いて独自に構築・検証した個人研究(PoC)の記録です。特定の企業・団体の実環境や業務上の非公開情報とは一切関係ありません。