はじめに
AIエンジニアとしてAIモデルの実装やエッジコンピューティングに携わる中で、私、原田美咲は日々、限られたリソースでいかにAIを「誤作動なく」動かすかという課題に情熱を注いでいます。
最近、システム開発の現場で次のような課題がよく話題に上ります。
「Geminiを使ってメールから注文情報をJSONで抜くシステムを作ったんですが、100回に1回くらい、謎のフォーマットで返ってきてシステムが落ちるんです」
生成AI、特にGeminiやFlashの日本語処理能力は素晴らしいですよね。非構造化データ(自然言語)を構造化データ(JSON)に変換するタスクにおいて、革命的な生産性をもたらしています。しかし、ここで私たちエンジニアが忘れてはならないのは、LLMは「確率論」で動いているという事実です。
99%の精度で正しいJSONが返ってくるとしても、残りの1%でシステム全体を停止させるようなエラーデータが流れてくるとしたら? それは業務システムとして「未完成」と言わざるを得ません。
今回は、プロンプトの調整(お祈り)だけでは防ぎきれないエラーに対し、システム設計レベルでどう「防御」を固めるか。エッジコンピューティングやセンサーデータ解析の現場で培った「制約と信頼性」の視点を応用して、Geminiを業務システムに安全に組み込むための実践的なテクニックをお話しします。
Geminiによる構造化データ抽出の「死角」とは
「JSONモードをオンにすれば大丈夫」
そう思われがちですが、GeminiのJSONモードやFunction Calling(Tool Use)は強力である一方で、万能薬ではありません。
人間には読めてもシステムには通らない「微細なエラー」
Geminiが高い言語能力を持っているがゆえに陥る罠があります。それは、「人間が見れば意味はわかるが、マシンにとっては無効なデータ」を生成してしまうことです。
例えば、ユーザーの住所入力から都道府県を抽出するタスクを考えてみましょう。
- 期待するJSON:
{"prefecture": "東京都"} - Geminiが出しがちなJSON:
{"prefecture": "東京都 (Tokyo)"}
Geminiは気を利かせて英語表記を添えてくれることがありますが、これを受け取るデータベースのカラム定義が厳格な場合、バリデーションエラーになります。また、数値型を期待しているフィールドに、"price": "1,000円"のようにカンマや単位を含めた文字列を入れてくることもあります。
これらは「間違い」というより、LLM特有の「過剰な親切心」や「文脈の過学習」によるものです。しかし、自動化されたデータ分析のパイプラインの中では、この親切心がシステムを止める致命傷になり得ます。
Gemini Pro/Flashにおける日本語処理の特性と限界
特に日本語を扱う場合、Geminiのモデル特性を理解しておく必要があります。日本語はハイコンテクストな言語であり、主語が省略されたり、表記ゆれ(全角・半角)が頻発したりします。
最新のGemini(FlashおよびPro)は、旧世代と比較して推論能力が飛躍的に向上しています。公式情報(2026年1月時点)によると、最新のFlashはかつてのProに匹敵する推論性能を持ちながら、圧倒的な低遅延を実現しています。しかし、それでも「複雑な指示の追従性」において、Proとの使い分けが必要な場面は残ります。
例えば、「ネストが深いJSON構造」や「複数の条件分岐を含む抽出ルール」を与えた場合、高速なFlashでは構造を簡略化したり、稀にフラットなJSONに変えてしまったりするケースが考えられます。最新のProは複雑な問題解決や推論に特化しており、構造化データの整合性を最優先するタスクでは依然として優位性があります。
「プロンプトで『必ずこの形式で』と書いたのに!」と叫びたくなる気持ちはわかりますが、モデルが進化しても、プロンプトはあくまで「緩やかな指示」であり、コンパイラのような「絶対的な命令」ではないのです。
3つの層で見る抽出リスクの特定と評価
敵を知るには、まずリスクを分解しましょう。最新のGemini(FlashやProを含む)では推論能力や構造化データの取り扱いが大幅に強化されていますが、抽出エラーのリスクはゼロではありません。エラーは大きく分けて3つの層で発生し、それぞれの層で対策が異なります。
構文レベル:JSONフォーマットの破損と不正なエスケープ
最も低レイヤーのリスクです。JSONとしてパース(解析)できない文字列が返ってくるケースです。最新のモデルでは生成の安定性が向上していますが、以下のパターンには引き続き注意が必要です。
- 閉じカッコの欠落: 長文生成時にトークン制限などで途切れると発生します。
- 不正なエスケープ処理: 日本語のテキスト内にダブルクォート
"やバックスラッシュ\が含まれている場合、Geminiが適切にエスケープ処理を行わず、JSON構文を破壊することがあります。 - Markdown記号の混入: JSONモードを指定していても、冒頭に ```json` というMarkdownのコードブロック記号を含めて返してくることがあります。これはチャットインターフェース向けに調整されたモデルで特によく見られる挙動であり、除去処理の実装が推奨されます。
これらは JSON.parse() した瞬間に例外が飛ぶため、検知は容易ですが、リトライ処理の実装が必須となります。
スキーマレベル:型不一致と必須フィールドの欠損
JSONとしては有効だが、アプリケーションが期待するデータ構造と異なるケースです。
- 型(Type)の不一致: 数値
100を期待しているのに 文字列"100"や漢数字の"百"が返ってくる。 - 必須フィールドの欠落: 「該当なし」と判断した場合に、
nullを入れるのではなく、フィールドそのものを省略してしまう。 - 配列と単一要素の揺れ: アイテムが1つしかない場合に、配列
["item"]ではなく単一の文字列"item"で返してくる。
これらは静的型付け言語(TypeScript, Go, Javaなど)でバックエンドを開発している場合、実行時エラーや予期せぬバグの原因となります。
意味レベル:ハルシネーションと情報の「捏造」
最も検知が難しく、かつビジネスリスクが高いのがこの層です。最新のGeminiは複雑な推論が可能になっていますが、その高い能力ゆえに文脈を過剰に読み取る「親切な嘘」が発生することがあります。
- 存在しない情報の補完: 「担当者:不明」とすべきところを、文脈から勝手に推測して「担当者:山田」と架空の人物を生成してしまう。
- 事実の歪曲: 「解約したい」という問い合わせを、ポジティブな文脈と誤解して「契約更新の意向あり」とフラグ付けしてしまう。
これは構文的にもスキーマ的にも正しいJSONとして返ってくるため、システム的なバリデーションをすり抜けてデータベースに保存されてしまいます。ここを防ぐには、ロジックによる整合性チェックや、信頼スコアの活用が必要になります。
プロンプト依存からの脱却:堅牢なスキーマ定義技術
「プロンプトエンジニアリングで頑張る」のは、そろそろ卒業しましょう。システム開発においては、より決定論的なアプローチが必要です。Geminiにおいてそれは、「スキーマ駆動」での制御です。
自然言語指示 vs OpenAPIスキーマ定義
プロンプトで「出力はJSON形式で、名前と年齢を含めてください」と書くのは、人間同士の口約束のようなものです。一方、GeminiのAPI(Google CloudのVertex AIやGoogle AI Studio)では、OpenAPI仕様に基づいたスキーマ定義を渡すことで、出力構造を強制する「Controlled Generation(制約付き生成)」が利用可能です。
最新のVertex AIでは、Function Calling(Tool Use)やresponse_schemaパラメータを活用し、あたかも「抽出関数を呼び出すための引数」を生成させるようにGeminiに指示することで、構造化の精度は劇的に向上します。この際、プロンプト本文には「抽出せよ」というタスクのみを記述し、データ構造の定義はすべてスキーマ側に委ねるのが鉄則です。
Pydanticを用いた厳格な型制約の導入
Pythonで実装する場合、Pydantic ライブラリは最強の武器になります。Pydanticで定義したクラスからJSONスキーマを自動生成し、それをGeminiに渡すのです。
from pydantic import BaseModel, Field
from typing import Optional, List
from enum import Enum
class Sentiment(str, Enum):
POSITIVE = "positive"
NEGATIVE = "negative"
NEUTRAL = "neutral"
class CustomerFeedback(BaseModel):
customer_name: Optional[str] = Field(..., description="顧客名。不明な場合はnull")
feedback_summary: str = Field(..., description="フィードバックの要約(50文字以内)")
sentiment: Sentiment = Field(..., description="感情分析結果")
is_urgent: bool = Field(False, description="緊急対応が必要かどうか")
このようにコードで定義することで、以下のメリットがあります。
- Enumによる選択肢の強制:
sentimentフィールドには、定義された3つの値以外は絶対に入りません。Geminiが「やや良い」のような勝手な値を返すのを防げます。 - Descriptionによるコンテキスト注入:
Fieldのdescriptionは、実はプロンプトの一部として機能します。ここに「不明な場合はnull」といった具体的な指示を埋め込むことで、スキーマ定義と指示を一体化できます。
「不明」を許容するためのNullable設計と列挙型(Enum)の活用
AIに「嘘」をつかせないための最大のコツは、「わからなくてもいい」という逃げ道を作ってあげることです。
全てのフィールドを必須(Required)にすると、Geminiは情報が見つからないときに無理やり何かを埋めようとしてハルシネーションを起こします。Optional(Nullable)なフィールドを適切に設定し、「抽出できない場合はnullを返すこと」とスキーマのdescriptionで明示することで、精度の高い「抽出なし」を得ることができます。
防御的実装:多層バリデーションと自己修復フロー
スキーマを定義しても、エラーはゼロにはなりません。最新のGemini(FlashやPro)を含め、LLMは確率的に動作するため、どれほどプロンプトを最適化しても構造化データの生成に失敗する可能性は残ります。そこで重要になるのが、エラーが発生することを前提とした「防御的プログラミング」です。
受け取ったJSONを信用しない「ゼロトラスト」な実装
Geminiから返ってきたJSONを、そのままDBのINSERT文に流し込むのは自殺行為です。必ずアプリケーション側でパースとバリデーションを行う層を挟みましょう。
Pydanticなどのバリデーションライブラリを通すことで、型変換(文字列の"100"を数値の100にする等)や、値の範囲チェックを自動化できます。ここでエラーが出たら、それは「不正なデータ」として弾くべきです。APIの安定性が向上した現在でも、外部入力に対する厳格な検証はシステム設計の鉄則です。
エラー発生時の自動リトライとプロンプト動的修正(Reflexion)
バリデーションエラーが発生した際、単にログを出して終了するのではなく、Gemini自身に修正させるフローを組むと、成功率が格段に上がります。これを「Reflexion(リフレクション)」パターンと呼びます。
特に最新のGemini(Proクラスの推論など)は、エラー内容を理解して自己修正する能力が飛躍的に向上しており、このパターンの有効性が高まっています。
実装フロー例:
- Geminiに抽出をリクエスト。
- 返答をバリデーション。
- エラー発生時: エラー内容(例:「
priceフィールドは数値である必要があります」)をキャプチャ。 - リトライ: 元のプロンプト + Geminiの誤った回答 + エラーメッセージ をセットにして、「以下のエラーが出たので修正してください」と再度Geminiに投げる。
Geminiは自身の間違いを指摘されると、高い確率で正しい形式に修正して返してくれます。これを1〜2回繰り返すだけで、構造化エラーの9割以上は解消できるケースが報告されています。
フォールバック戦略:ルールベース処理とのハイブリッド運用
それでも解決しない(リトライ上限に達した)場合はどうするか。ここでシステムを止めないためのフォールバック戦略が必要です。
- ルールベース抽出: 正規表現などで最低限の情報(メールアドレスや電話番号など)だけ抜いて処理を進める。
- Human-in-the-loop(人間へのエスカレーション): エラーとなったデータを専用のキューに入れ、管理画面で人間が確認・修正できるようにする。
- 未処理フラグ: データに「要確認」フラグを立てて保存し、後続の処理(自動メール送信など)をスキップする。
AIは「ツール」であり「責任者」ではありません。最終的なデータの整合性に責任を持つのは、私たちエンジニアが設計するシステム側のロジックです。
運用フェーズにおける「精度の劣化」と監視
システムが無事にリリースされても、戦いは終わりません。AIモデルは生き物のように振る舞い、環境の変化によって精度が揺らぎます。
「昨日は動いていた」が通用しないLLM特有の運用リスク
通常のソフトウェアと異なり、LLMのAPIはバックエンドでモデルのアップデートが行われることがあります。Googleはバージョン管理機能を提供しており、日付付きの固定バージョンタグ(例: gemini-1.5-pro-001のような形式)を指定することでモデルを固定できますが、それでも長期運用においては提供終了や強制アップデートに伴う微妙な挙動の変化(ドリフト)が発生する可能性があります。
また、入力されるデータ(ユーザーの文章)の傾向が変わることもあります。例えば、若者言葉が増えたり、新しい業界用語が使われるようになったりすると、抽出精度が急に落ちることがあります。
継続的な精度評価のためのゴールデンデータセット構築
こうした変化に気づくために必要なのが、「ゴールデンデータセット」による回帰テストです。
開発中に作成した「入力テキスト」と「正解JSON」のペアを100件程度用意しておきます。そして、CI/CDパイプラインや定期実行ジョブの中で、このデータセットに対してGeminiを実行し、正解率をモニタリングします。
- 完全一致率: JSONが完全に一致するか。
- フィールド別正答率: 特定のフィールド(例:日付)だけ精度が落ちていないか。
もしスコアが急落した場合、以下のような対策を検討します。
- プロンプトの修正: 指示の明確化や制約条件の見直しを行います。
- Few-shotプロンプトの強化: 3〜5件程度の入出力例(Few-shot)をプロンプトに追加することで、モデルに期待するフォーマットや判断基準を明示的に伝えます。最新の技術トレンドでは、思考の過程を含めるCoT(Chain-of-Thought)と組み合わせることで、より複雑なタスクでも安定性が向上することが報告されています。
- モデルバージョンの見直し: 固定していたバージョンが古い場合、最新の安定版へ移行検証を行います。
この「品質の監視」こそが、AIシステムを長く安定して動かす鍵となります。
まとめ
Geminiによる構造化データ抽出は、業務プロセスの自動化において非常に強力な武器です。しかし、その力を安定して引き出すためには、AIの「曖昧さ」をシステムの「厳格さ」で包み込む設計が必要です。
- プロンプトに頼らず、スキーマ定義(Pydantic等)で構造を強制する。
- AIの出力を疑い、多層的なバリデーションを実装する。
- エラーメッセージをAIにフィードバックして自己修復させるループを作る。
- ゴールデンデータセットで精度の変化を常に監視する。
これらは、エッジコンピューティングやセンサーデータ解析など、制約の厳しい環境で私たちが大切にしている「想定外を想定内にする」という考え方そのものです。AIという「確率的なエンジン」を、堅牢なエンジニアリングという「車体」に乗せて初めて、ビジネスの現場で走れるシステムになります。
要件定義やコードレビューの際には、これらの防御的設計の観点をチェックリスト化して確認することをおすすめします。安全で信頼性の高いAI実装を、一緒に広めていきましょう。
コメント