大規模言語モデル(LLM)は、驚くほど滑らかに、もっともらしい嘘(ハルシネーション)をつきます。どれほどプロンプトエンジニアリングを工夫しても、確率モデルである以上、この出力の「ゆらぎ」を完全にゼロにすることは理論上不可能です。
「AIの精度が低いから実務にはまだ早い」と導入を見送るケースは少なくありません。しかし、実運用に耐えうるシステムを構築する上では、「AIは必ず間違える非決定的なコンポーネントである」という前提に立ち、システム全体のエラーハンドリングとアーキテクチャを設計することが求められます。
AIが出力した結果を、人間がいちいち手動でチェックしていては自動化の本末転倒です。本記事では、AIの出力をプログラムの枠組みに閉じ込め、ハルシネーションをシステム的に無効化する防御アーキテクチャの実装手法について、より深い技術的視点から解説します。
1. ハルシネーションを封じ込めるシステム設計の原則
AIの「ゆらぎ」や「ハルシネーション」を人間の目視確認でカバーするのではなく、プログラムによるパースとリトライという堅牢な枠に収める必要があります。
AIの出力をそのまま画面に表示したり、直接データベースに保存したりすることは、絶対に避けるべきアンチパターンです。出力を完全にシステムで制御するためには、以下のフローをバックエンドのパイプラインに組み込みます。
Structured Output(構造化出力)の要求
プロンプト内で「必ず指定したJSONスキーマのみで出力せよ。マークダウンの装飾(“`json など)や挨拶は一切含めるな」と厳格に指定します。
近年のGoogle Gemini APIやOpenAI APIでは、APIのパラメータとして「JSONモード」や「レスポンススキーマ(Response Schema)」を直接指定できる機能が標準化されています。単なる自然言語によるお願いではなく、APIのプロトコルレベルでキーと値の型をシステム側で強制的に固定することが第一歩です。
パースと厳格なバリデーション
AIから返ってきたレスポンスを、単なる文字列として扱うのではなく、プログラム側でパース(解析)します。
TypeScriptなどの静的型付け言語を使用している場合、Zodなどのバリデーションライブラリと組み合わせるのが現在のベストプラクティスです。ここでJSONとしてフォーマットが壊れていたり、必須のキーが欠落している場合、あるいは値のデータ型(数値であるべきところが文字列になっている等)が想定と異なる場合は、即座に「エラー(例外)」としてスローします。
自律的なリトライループ(自己修復機構)
エラーを検知した場合、システムはユーザーにエラー画面を見せるのではなく、バックグラウンドで自律的に動きます。
「JSONの形式が間違っています。指定されたスキーマに従って修正し、再出力してください」というエラーメッセージと共に、元のプロンプトと間違った出力をセットにしてAIに再リクエストを送ります。これを上限3回程度の try-catch ループで処理することで、一時的なハルシネーションやフォーマット崩れをシステムが自己修復します。
2. 実運用を支える周辺技術とインフラ考慮事項
防御アーキテクチャを実際のシステムに組み込む際、コードレベルの工夫だけでなく、インフラやフレームワークの選定も重要な要素となります。
オーケストレーション・フレームワークの活用
自前でリトライループを実装することも可能ですが、プロダクトの規模が大きくなるにつれてコードが肥大化しやすくなります。このような場合、LangChainなどのフレームワークを活用し、フォールバック(代替処理)やリトライのロジックを抽象化・モジュール化することで、可読性の高いワークフローを維持できます。
サーバーレス環境におけるタイムアウト対策
AWS LambdaなどのサーバーレスアーキテクチャでAI連携APIを構築する場合、特有の課題が発生します。LLMの推論(API呼び出し)は通常のデータベースクエリに比べてレイテンシが長く、数秒から十数秒かかることも珍しくありません。
ここに前述の「自律的リトライループ(最大3回など)」を組み込むと、APIゲートウェイやLambdaの実行時間上限(デフォルトの3秒や、API Gatewayの29秒制限など)に抵触し、タイムアウトエラーを引き起こすリスクが高まります。そのため、非同期処理(イベント駆動)に切り替えるか、インフラ側のタイムアウト設定を余裕を持たせてチューニングする設計が不可欠です。
3. 【実践】ハルシネーションを防ぐTypeScript実装イメージ
上記の防御アーキテクチャを、Node.js / TypeScript環境で実装する場合の具体的なワークフローは以下のようになります。
import { z } from "zod";
// 1. 期待するJSONスキーマをZodで定義
// ※このスキーマ定義がハルシネーションに対する「防波堤」となる
const AIResponseSchema = z.object({
summary: z.string(),
confidenceScore: z.number().min(0).max(100),
tags: z.array(z.string())
});
// Zodの型推論を利用してTypeScriptの型を生成
type AIResponse = z.infer<typeof AIResponseSchema>;
async function fetchAIResponseWithRetry(prompt: string, retries = 3): Promise<AIResponse> {
for (let i = 0; i < retries; i++) {
try {
// AIへのリクエスト実行(JSON出力を強制するオプションを付与)
const rawResponse = await callLLMAPI(prompt, { responseFormat: "json" });
// 2. パースとバリデーション
// JSON形式として壊れていればここでエラーとなる
const parsedJson = JSON.parse(rawResponse);
// Zodによる厳格な型チェック。スキーマ違反があれば例外をスロー
const validatedData = AIResponseSchema.parse(parsedJson);
return validatedData; // 成功時はクリーンなデータを返す
} catch (error) {
console.warn(`[Attempt ${i + 1}] AI出力のパースまたはバリデーションに失敗:`, error);
if (i === retries - 1) {
// 最終リトライでも失敗した場合は呼び出し元へエラーを返す
throw new Error("規定回数のリトライを行いましたが、有効なAIレスポンスを取得できませんでした。");
}
// 3. 失敗時はプロンプトにエラー内容を付与してリトライを促す
prompt += `\n\n【システムからの警告】前回の出力で以下のエラーが発生しました。必ず指定のJSONスキーマに従い、修正して再出力してください。\nエラー詳細: ${error}`;
}
}
// TypeScriptのコンパイルを通すためのダミーリターン(到達不能コード)
throw new Error("Unexpected end of retry loop");
}
このように実装することで、AIの非決定的な出力を、システムが安全に扱える「決定的なデータ」へと変換(サニタイズ)することができます。
4. 運用フェーズにおける監視とオブザーバビリティ
実装して終わりではありません。実運用においては「AIがどれくらいの頻度でハルシネーションを起こし、システムが何回リトライして自己修復したか」を監視(モニタリング)する仕組みが必要です。
リトライが成功したからといってエラーログを握りつぶしてしまうと、裏側でAPIの呼び出しコスト(トークン消費量)が想定外に膨れ上がる原因になります。パースエラーやスキーマ違反が発生した際は、その「失敗した生の出力結果」をCloud Logging等に警告(Warn)レベルで記録しておくべきです。
蓄積された失敗ログを分析することで、「プロンプトの指示が曖昧だった」「特定のドメインにおいてAIの知識が不足している」といった根本原因を特定し、プロンプトの改善へと繋げることが可能になります。
まとめ:冷徹なリスク管理がAI実装の鍵
LLMを活用したシステム開発において、ハルシネーションを「AIの性能の限界」として片付けるか、「システム側で処理すべき例外」として扱うかが、プロダクトの安定性を大きく左右します。
AIエージェントによる開発プロセスの自動化など、高度なワークフローを構築する際にも、この「バリデーションと自律的リトライ」のアーキテクチャはすべての基盤となります。
AIという「予測不能なコンポーネント」を、いかにプログラムという「予測可能な檻」に閉じ込めるか。この技術的なリスク管理こそが、モダンなAI開発において最も重要なスキルと言えるでしょう。
