なぜLLMは「もっともらしい嘘」をついてしまうのか
LLMを使った開発プロジェクトに携わっていると、モデルが堂々と間違った情報を返してくることに驚かされます。OpenAIの最新の研究報告によれば、ハルシネーションが発生する根本的な原因は、モデルの学習プロセスと評価方法に起因しています。実際、GPT-5でもハルシネーションは完全には解決されていません。
この問題を理解するために、まず身近な例から考えてみます。
ハルシネーションを理解する3つの身近な例え
試験で当てずっぽうをする学生のジレンマ
最も分かりやすい例は、多肢選択式の試験を受ける学生の行動です。答えが分からない問題に直面したとき、学生には2つの選択肢があります。
学生が取りうる行動パターンは以下の通りです。
- 空欄にして確実に0点を取る
- 当てずっぽうで答えて、運が良ければ得点を獲得する
- 「分かりません」と正直に書いて、部分点を期待する
現在のLLMの評価システムは、この試験で「当てずっぽうでも答えた方が得」という採点方式を採用しているようなものです。正解率(Accuracy)のみで評価されるため、モデルは「分かりません」と答えるより、推測でも何か答える方を選ぶようになってしまいます。
知ったかぶりをする新入社員の例
もう一つ分かりやすい例は、会議で知ったかぶりをしてしまう新入社員の状況です。上司から「昨年の売上データはどうだった?」と聞かれたとき、実際のデータを知らない新入社員が「約1億円でした」と適当に答えてしまうケースを想像してください。
LLMも同様に、学習データに存在しない情報を聞かれたとき、パターンから「それっぽい」答えを生成してしまいます。例えば、ある研究者の誕生日を聞かれた場合、実際のデータを持っていなくても「9月10日」のような具体的な日付を答えてしまうのです。
辞書にない言葉を説明しようとする状況
3つ目の例として、存在しない専門用語の意味を聞かれた状況を考えてみます。「クオンタムフラクタライゼーション」という造語の意味を聞かれたとき、多くの人は「量子的な何か」と「フラクタル的な何か」を組み合わせて、もっともらしい説明を作り出そうとします。
LLMも同じように、未知の概念について質問されると、既知のパターンを組み合わせて「それらしい」回答を生成してしまうのです。
技術的な観点から見たハルシネーションの発生メカニズム
事前学習における根本的な制約
LLMの「事前学習」は、大量のテキストデータから次の単語を予測するプロセスです。このプロセスには根本的な制約があります。
モデルが学習する際の特徴として、以下の点が挙げられます。
- スペルや文法のような一貫したパターンは正確に学習できる
- 頻度の低い具体的な事実(人物の誕生日、論文のタイトルなど)は、パターンだけでは予測不可能
- 正解・不正解のラベルがないため、有効な文章と無効な文章の区別が難しい
これは画像認識で例えると、猫の写真に「ペットの誕生日」というランダムなラベルを付けて学習させようとするようなものです。どんなに高性能なアルゴリズムでも、写真から誕生日を予測することは不可能です。
評価指標が生む間違ったインセンティブ
現在の多くのベンチマークでは、モデルの性能を「正確性(Accuracy)」で評価しています。しかし、この評価方法には大きな問題があります。
SimpleQAベンチマークの結果を見てみると、興味深い傾向が明らかになります。
表 SimpleQAにおけるモデルの挙動比較
測定指標 | gpt-5-thinking-mini | OpenAI o4-mini |
---|---|---|
棄権率(回答を控える割合) | 52% | 1% |
正確性(正解率) | 22% | 24% |
エラー率(間違いの割合) | 26% | 75% |
o4-miniの方が正確性では若干上回っていますが、エラー率は75%と非常に高くなっています。これは、不確実な場合でも推測で答えることで、見かけ上の正確性を上げているためです。
July 2025: will no longer be updated for new models or benchmark results. The repo will continue to host reference implementations for HealthBench, BrowseComp, and SimpleQA.
simple-evals
実践的なハルシネーション対策の実装方法
ハルシネーションの原因を理解したところで、実際のシステム開発においてどのような対策を取れるのか、具体的な実装方法を見ていきます。
プロンプトエンジニアリングによる基本対策
不確実性を表現させるプロンプト設計
最も基本的な対策は、プロンプトで明示的に不確実性の表現を求めることです。
const systemPrompt = `
あなたは正確な情報提供を重視するアシスタントです。
以下のルールに従って回答してください:
1. 確実に知っている情報のみを提供する
2. 不確かな場合は「確実な情報がありません」と明記する
3. 推測が必要な場合は「推測ですが」と前置きする
4. 情報源が曖昧な場合は「要確認」タグを付ける
重要:間違った情報を提供するよりも、「分からない」と答える方が価値があります。
`;
async function queryLLM(userQuestion: string): Promise<string> {
const response = await openai.chat.completions.create({
model: "gpt-5",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userQuestion }
],
temperature: 0.3 // 低めの温度設定で確実性を高める
});
return response.choices[0].message.content;
}
段階的検証を組み込んだプロンプト
複雑な質問に対しては、段階的に検証させることでハルシネーションを減らせます。
const verificationPrompt = `
質問に回答する前に、以下の手順で検証してください:
STEP 1: 質問の内容を正確に理解する
- 何について聞かれているか明確にする
- 曖昧な部分があれば明示する
STEP 2: 自己の知識を確認する
- この情報を確実に知っているか評価する
- 知識の確実性を%で表現する(100%確実、70%おそらく正しい、など)
STEP 3: 回答を構成する
- 確実性に応じて適切な表現を選ぶ
- 不確実な部分は明確に示す
最終回答: [ここに回答を記載]
`;
RAG(Retrieval-Augmented Generation)による信頼性向上
サーバーレス環境でのRAGシステム構築
「サーバーレス」アーキテクチャを活用したRAGシステムは、ハルシネーション対策の有力な手段です。AWS Lambda、Amazon OpenSearch Serverless、Amazon Bedrockを組み合わせることで、スケーラブルかつコスト効率的なソリューションを構築できます。
import { LambdaClient } from "@aws-sdk/client-lambda";
import { OpenSearchClient } from "@aws-sdk/client-opensearch";
import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime";
class ServerlessRAGSystem {
private opensearchClient: OpenSearchClient;
private bedrockClient: BedrockRuntimeClient;
constructor() {
this.opensearchClient = new OpenSearchClient({
region: "us-east-1"
});
this.bedrockClient = new BedrockRuntimeClient({
region: "us-east-1"
});
}
async retrieveContext(query: string): Promise<string[]> {
// OpenSearch Serverlessから関連文書を検索
const searchParams = {
index: "knowledge-base",
body: {
query: {
neural: {
text_vector: {
query_text: query,
k: 5
}
}
}
}
};
const results = await this.opensearchClient.search(searchParams);
return results.hits.hits.map(hit => hit._source.content);
}
async generateWithContext(
query: string,
contexts: string[]
): Promise<string> {
const prompt = `
以下のコンテキスト情報のみを使用して質問に答えてください。
コンテキストに情報がない場合は、「提供された情報では回答できません」と答えてください。
コンテキスト:
${contexts.join("\\\\n\\\\n")}
質問: ${query}
回答:`;
const response = await this.bedrockClient.invokeModel({
modelId: "anthropic.claude-3-sonnet",
body: JSON.stringify({
prompt: prompt,
max_tokens_to_sample: 500,
temperature: 0.2
})
});
return JSON.parse(response.body).completion;
}
}
ベクトルデータベースとの連携実装
「サーバーレス」のベクトルデータベースサービスを活用することで、セマンティック検索の精度を高められます。
import { Pinecone } from '@pinecone-database/pinecone';
class VectorSearchRAG {
private pinecone: Pinecone;
private indexName: string = 'knowledge-base';
constructor(apiKey: string) {
this.pinecone = new Pinecone({ apiKey });
}
async upsertDocuments(documents: Document[]): Promise<void> {
const index = this.pinecone.index(this.indexName);
// ドキュメントをチャンク化してエンベディング
for (const doc of documents) {
const chunks = this.splitIntoChunks(doc.content, 512);
const embeddings = await this.generateEmbeddings(chunks);
const vectors = chunks.map((chunk, i) => ({
id: `${doc.id}-${i}`,
values: embeddings[i],
metadata: {
content: chunk,
source: doc.source,
timestamp: new Date().toISOString()
}
}));
await index.namespace('documents').upsert(vectors);
}
}
private splitIntoChunks(text: string, chunkSize: number): string[] {
const words = text.split(' ');
const chunks: string[] = [];
for (let i = 0; i < words.length; i += chunkSize) {
chunks.push(words.slice(i, i + chunkSize).join(' '));
}
return chunks;
}
}
ファクトチェック層の実装
外部APIを活用した事実検証
生成された回答の事実確認を行う層を追加することで、ハルシネーションを検出できます。
interface FactCheckResult {
isVerified: boolean;
confidence: number;
source?: string;
issues?: string[];
}
class FactChecker {
async checkFacts(response: string): Promise<FactCheckResult> {
// 回答から検証可能な主張を抽出
const claims = await this.extractClaims(response);
const verificationResults = await Promise.all(
claims.map(claim => this.verifyClaim(claim))
);
// 全体的な信頼度を計算
const overallConfidence = this.calculateConfidence(verificationResults);
return {
isVerified: overallConfidence > 0.7,
confidence: overallConfidence,
issues: verificationResults
.filter(r => !r.verified)
.map(r => r.issue)
};
}
private async extractClaims(text: string): Promise<string[]> {
// 数値、日付、固有名詞を含む文を主張として抽出
const sentences = text.split(/[.!?]+/);
const claims: string[] = [];
const patterns = [
/\\\\d+/, // 数値
/\\\\d{4}年/, // 年
/[A-Z][a-z]+/ // 固有名詞
];
for (const sentence of sentences) {
if (patterns.some(pattern => pattern.test(sentence))) {
claims.push(sentence.trim());
}
}
return claims;
}
private calculateConfidence(results: any[]): number {
if (results.length === 0) return 1.0;
const verifiedCount = results.filter(r => r.verified).length;
return verifiedCount / results.length;
}
}
実装時のベストプラクティス
モニタリングとフィードバックループの構築
ハルシネーション対策を継続的に改善するために、以下のようなモニタリングシステムを構築します。
class HallucinationMonitor {
private metrics: Map<string, number> = new Map();
async logResponse(
query: string,
response: string,
metadata: ResponseMetadata
): Promise<void> {
const hallucinationScore = await this.detectHallucination(response);
// CloudWatchメトリクスに送信
await this.publishMetrics({
hallucinationScore,
modelConfidence: metadata.confidence,
responseLength: response.length,
timestamp: Date.now()
});
// 閾値を超えた場合はアラート
if (hallucinationScore > 0.3) {
await this.sendAlert({
query,
response,
score: hallucinationScore
});
}
}
private async detectHallucination(response: string): Promise<number> {
// 複数の指標を組み合わせてスコアを計算
const indicators = {
containsAbsoluteStatements: this.checkAbsoluteStatements(response),
hasUnverifiableData: this.checkUnverifiableData(response),
confidenceWithoutSource: this.checkSourcelessConfidence(response)
};
return Object.values(indicators)
.reduce((sum, val) => sum + val, 0) / Object.keys(indicators).length;
}
}
A/Bテストによる改善の検証
異なるプロンプトや設定の効果を検証するために、A/Bテストフレームワークを実装します。
class PromptABTester {
private variants: Map<string, PromptVariant> = new Map();
async runExperiment(
query: string,
userId: string
): Promise<ExperimentResult> {
// ユーザーをバリアントに割り当て
const variant = this.assignVariant(userId);
// バリアントのプロンプトで実行
const response = await this.executeWithVariant(query, variant);
// 結果を記録
await this.logExperimentResult({
userId,
variant: variant.name,
query,
response,
metrics: await this.calculateMetrics(response)
});
return { response, variant: variant.name };
}
private calculateMetrics(response: string): Promise<Metrics> {
return Promise.resolve({
hallucinationScore: 0,
userSatisfaction: 0,
factualAccuracy: 0,
responseTime: 0
});
}
}
将来の展望と継続的な改善アプローチ
評価指標の見直しが必要な理由
OpenAIの研究が指摘するように、現在の評価方法では「正確性」のみに焦点を当てているため、モデルが推測することにインセンティブが働いてしまいます。
理想的な評価指標には以下の要素が必要です。
- 不確実性を適切に表現した場合の加点
- 自信を持った誤答に対する大きなペナルティ
- 情報源の明示に対する評価
サーバーレスアーキテクチャがもたらす柔軟性
「サーバーレス」環境でのハルシネーション対策システムは、以下の利点があります。
コスト面での利点として挙げられるのは次の通りです。
- 使用した分だけの課金で、アイドル時のコストがゼロ
- 自動スケーリングにより、負荷に応じた最適なリソース配分
- マネージドサービスの活用による運用コストの削減
技術面での利点も重要です。
- 新しいモデルや手法の迅速な導入が可能
- マイクロサービス化により、個別コンポーネントの独立した改善
- イベントドリブンアーキテクチャによる疎結合な設計
組織的な取り組みの重要性
ハルシネーション対策は、技術的な実装だけでなく、組織全体での取り組みが必要です。
まず、開発チームでは以下のような体制を整えます。
- 定期的なモデル評価とベンチマーキング
- ハルシネーション事例の収集と分析
- 継続的な改善プロセスの確立
ビジネス側との連携も欠かせません。
- ユーザーへの適切な期待値設定
- ハルシネーションリスクの透明な説明
- フィードバックループの構築
まとめ
LLMのハルシネーションは、モデルの学習方法と評価指標に起因する構造的な問題です。完全に解決することは現時点では困難ですが、適切な対策を実装することで、実用レベルまでリスクを低減できます。
本記事で紹介した対策アプローチは以下の通りです。
- プロンプトエンジニアリングによる不確実性の表現
- RAGシステムによる信頼できる情報源との連携
- ファクトチェック層の実装による検証
- 継続的なモニタリングとフィードバックループ
「サーバーレス」アーキテクチャを活用することで、これらの対策をスケーラブルかつコスト効率的に実装できます。AWS Lambda、Amazon Bedrock、OpenSearch Serverlessなどのマネージドサービスを組み合わせることで、運用負荷を最小限に抑えながら、高度なハルシネーション対策システムを構築できます。
重要なのは、ハルシネーションを「完全に排除する」のではなく、「適切に管理する」という考え方です。不確実性を認識し、それをユーザーに透明に伝えることで、より信頼性の高いAIシステムを実現できます。
今後、評価指標の改善やモデルの進化により、ハルシネーションの問題は徐々に改善されていくことが期待されます。しかし、それまでの間も、本記事で紹介した実践的な対策を組み合わせることで、ビジネスで安心して使えるAIシステムを構築できます。