生成AIを技術調査やドキュメント作成に使うとき、最も扱いに困るのがハルシネーションです。もっともらしい説明の中に、存在しないAPIや誤ったバージョン情報が混ざると、レビューに気づかれないまま実装へ進んでしまいます。
私が実際に試して効果を感じたのは、プロンプトだけで「正しい回答」を引き出そうとする方法ではありません。回答を構造化し、根拠と不確実性を出力させ、最後に機械的・人手で検証する流れを作ることです。
この記事では、ハルシネーションを完全になくすのではなく、「見つけやすくする」「断定を減らす」「検証可能な形で回答させる」ための実践的なプロンプトと検証フローを紹介します。
ハルシネーションを前提にプロンプトを設計する
最初に重要なのは、「間違えないで」と書くだけでは不十分だと理解することです。モデルは情報が不足していても、質問に回答しようとします。そのため、分からない場合の振る舞いを明示します。
私が調査用途で使っている基本形は次のようなものです。
あなたは技術調査アシスタントです。
以下のルールに従って回答してください。
- 確認できない情報は推測せず、「確認できない」と書く
- 事実、推測、提案を分けて記載する
- API名、関数名、バージョン番号、日付は特に慎重に扱う
- 根拠が入力文にない場合は、根拠がないことを明示する
- 質問の前提が誤っている可能性があれば指摘する
- 回答の最後に、追加で確認すべき項目を列挙する
「正確に答えてください」よりも、「確認できない場合の処理」を指定する方が、回答の危険な断定を減らせます。
ただし、このプロンプトだけで正しさを保証できるわけではありません。モデルが「確認した」と出力しても、実際に外部情報を確認したとは限らないためです。根拠の有無と、根拠の正しさは別々に検証します。
回答を検証しやすい形式に固定する
自由記述の回答は、どこが事実でどこが推測なのか判別しにくくなります。そこで、私は回答形式をJSONに近い構造へ固定します。
次のJSON形式だけで回答してください。
Markdownやコードフェンスは不要です。
{
"answer": "質問への簡潔な回答",
"facts": [
{
"claim": "検証可能な主張",
"evidence": "入力文または参照資料にある根拠",
"confidence": "high|medium|low"
}
],
"assumptions": [
"回答で置いた仮定"
],
"unknowns": [
"確認できなかった点"
],
"verification_steps": [
"利用者が実行できる確認手順"
]
}
この形式にすると、たとえば以下の問題を見つけやすくなります。
-
evidenceが空なのにconfidenceがhigh - 具体的な主張があるのに
verification_stepsがない -
factsではなくassumptionsに入れるべき内容が混ざっている - 参照資料に書かれていないバージョン番号を断定している
実際の運用では、厳密なJSONを毎回返させるのが難しいモデルもあります。その場合は、まず「見出しを固定する」だけでも効果があります。形式を完全に守れないこと自体も、検出対象として扱います。
検出用プロンプトで回答を再点検する
回答を一度生成したら、別のプロンプトで批判的に点検します。ここでは、元の回答をそのまま信頼せず、主張単位に分解させます。
以下の回答を監査してください。
監査対象:
<ここに生成された回答>
次の表形式で出力してください。
| 主張 | 種類 | 根拠の有無 | リスク | 確認方法 |
|---|---|---|---|---|
種類は「事実」「推測」「提案」のいずれかにしてください。
根拠がない具体名、URL、日付、数値、API仕様には高いリスクを付けてください。
確認方法は、公式ドキュメント確認、実行テスト、ソースコード確認など具体的に書いてください。
この監査用プロンプトで大切なのは、単に「間違いを探して」と頼まないことです。ハルシネーションを見落とした場合、監査側のモデルも同じ誤りを共有する可能性があります。そのため、主張、根拠、確認方法の3点を別々に出させます。
また、回答生成と監査で役割を分けるのも有効でした。生成側には「解決策を提案する」役割、監査側には「疑う」役割を与えます。1回のプロンプトで両方を実行するより、問題箇所が見えやすくなります。
コマンドとコードで事実を確認する
文章のレビューだけでは、APIやコマンドの誤りを見逃します。実行できる主張は、できるだけ実行して確認します。
たとえば、PythonパッケージのAPIについて回答を得た場合は、次のように最小コードを作ります。
python -m venv .venv
.venv\Scripts\activate
python -m pip install -U pip
python -m pip show requests
python -c "import requests; print(requests.__version__)"
関数や引数の存在を確認するだけなら、次の方法も使えます。
import inspect
import requests
print(hasattr(requests, "get"))
print(inspect.signature(requests.get))
CLIの説明であれば、ヘルプと終了コードを確認します。
my-command --help
my-command --version
$LASTEXITCODE
確認できない主張は、記事や仕様書にそのまま採用しません。私の場合は、レビュー用に次のようなチェックリストを作っています。
[ ] 公式ドキュメントに該当する仕様がある
[ ] 使用中のバージョンで実行できた
[ ] サンプルコードがエラーなく動いた
[ ] URLが存在し、内容が主張を裏付けている
[ ] 推測や前提が明示されている
[ ] 未確認事項が残っていない
特にバージョン情報は注意が必要です。モデルが古い知識をもとに回答していることもあるため、「いつ時点の情報か」を必ず確認します。
小さな自動チェックを組み込む
毎回すべてを人手で確認するのは難しいため、回答の形式的な問題は自動検出します。以下は、回答JSONの最低限のチェック例です。
import json
import sys
def validate(result: dict) -> list[str]:
errors = []
required = [
"answer",
"facts",
"assumptions",
"unknowns",
"verification_steps",
]
for key in required:
if key not in result:
errors.append(f"必須キーがありません: {key}")
for index, fact in enumerate(result.get("facts", [])):
for key in ("claim", "evidence", "confidence"):
if not fact.get(key):
errors.append(f"facts[{index}] の {key} が空です")
if fact.get("confidence") == "high" and not fact.get("evidence"):
errors.append(f"根拠なしでhigh判定です: facts[{index}]")
if not result.get("unknowns"):
errors.append("unknownsが空です。未確認事項を再確認してください")
return errors
data = json.load(sys.stdin)
errors = validate(data)
if errors:
for error in errors:
print(f"ERROR: {error}")
sys.exit(1)
print("OK")
このチェックは、内容の真偽を判定するものではありません。根拠のない「自信のある回答」や、未確認事項を出力しない回答を弾くためのゲートです。
実運用では、次のような流れにしています。
- 質問と参照資料を準備する
- 構造化プロンプトで回答を生成する
- JSONの形式チェックを行う
- 監査プロンプトで主張を分解する
- 重要な主張を公式資料や実行テストで確認する
- 未確認の主張を削除するか、条件付きの表現に変更する
- 最終レビューを行う
ハルシネーションを検出・抑制するための検証フローを図にすると次の通りです。
まとめ
ハルシネーション対策で重要なのは、プロンプトに「間違えないで」と書くことではありません。回答を事実・推測・提案に分け、根拠と不確実性を明示させ、検証可能な形式で出力させることです。
さらに、生成と監査を分離し、APIやコマンドは実際に実行します。形式的な不備はコードで検出し、重要な主張だけを人手で重点確認すると、作業量とリスクのバランスを取りやすくなります。
生成AIの回答を「完成品」として扱うのではなく、「検証待ちの下書き」として扱う。この前提をプロンプトと開発フローの両方に組み込むことが、現時点で最も再現しやすい対策だと感じています。