2026年4月21日
AI
LLM I/Oとデータ連携基盤の境界設計|構造化出力・冪等性・スキーマ駆動・観測可能性
LLMを業務システムに組み込む際に最も詰まりやすいのが、確率的なLLM I/Oと決定的なデータ連携基盤の境界設計です。本稿では構造化出力・冪等性・スキーマ駆動・観測可能性の4原則と、その実装パターンを整理します。

概要
LLMを業務システムに組み込むとき、最も設計が難しくなるのが「LLMの入出力(I/O)」と「データ連携基盤」の境界です。LLMは確率的にテキストを返すコンポーネントであり、データ基盤は決定的にレコードを扱います。この性質の違いを設計で吸収しなければ、パイプラインは早晩破綻します。
本稿では、DigitalBaseが社内AI基盤の構築で得た知見をもとに、LLM I/Oの特性を整理したうえで、データ連携基盤との相性が良い4つの設計原則(構造化出力・冪等性・スキーマ駆動・観測可能性)を、実装パターンとともに解説します。
LLM I/O の特性
LLMは従来のAPIや関数とは性質が異なります。データ基盤と組み合わせる前に、まずこの特性を正確に把握しておく必要があります。
確率的・非決定論的
同じ入力でも出力が毎回わずかに変動します。temperature=0 を指定しても、完全な再現性は保証されません。レコードの id フィールドのように決定的に同じ値が要求される場面で、LLMの出力をそのまま使うことは避けるべきです。
コンテキスト長の制約
入力できるトークン数には上限があります。近年はモデルによって数万〜100万トークン規模まで拡大していますが、データソースから取得した数万件のレコードを一度に投入できるわけではありません。チャンキング・要約・段階的処理を前提として設計する必要があります。
コストとレイテンシ
クラウドAPIであれば従量課金、ローカルLLMであれば推論時間が支配的なコストになります。1レコードずつ呼び出すか、N件をまとめて呼び出すかによって、コストもレイテンシも桁単位で変わります。
出力の自由度
人間が読む用途であれば自由テキストで構いませんが、データ基盤へ書き戻すには 構造化された型 が必要です。JSON Schemaによる制約を課さないと、後段のパース処理が壊れやすくなります。
相性のいい設計パターン
データ連携基盤とLLMを組み合わせる際の4つの基本原則を、順に整理します。
1. 構造化出力を強制する
LLMの出力は必ずJSON Schemaで型を制約します。Ollama / vLLM / OpenAI / Anthropic / Geminiのいずれも、format や response_format といったパラメータで構造化出力を指定できます。
schema = { "type": "object", "properties": { "category": {"type": "string", "enum": ["urgent", "normal", "low"]}, "summary": {"type": "string", "maxLength": 200}, "tags": {"type": "array", "items": {"type": "string"}}, "action_required": {"type": "boolean"}, }, "required": ["category", "summary", "action_required"], } result = await call_llm( model="qwen3:32b", prompt=user_text, format_schema=schema, ) parsed = json.loads(result) # 型が保証された dict
設計上のポイントは以下の3点です。
enumで取り得る値を限定する(カテゴリ分類などはほぼ全ケースで有効)maxLengthでフィールド長を制約する(DBのカラム長と揃える)requiredで必須フィールドを宣言する(None混入を回避)
これだけで後段の処理が大きく安定します。
2. 冪等性を確保する
LLMが確率的である以上、同じレコードに対して複数回呼び出されることを前提に設計します。重複呼び出しによってDBが汚染されないよう、冪等キーを用意します。
def make_idempotency_key(record_id: str, version: str, prompt_hash: str) -> str: """同じレコード × 同じプロンプト → 同じキー。""" return f"{record_id}:{version}:{prompt_hash[:8]}" async def process(record: dict): key = make_idempotency_key( record["id"], "v1", hashlib.sha256(PROMPT.encode()).hexdigest() ) cached = await cache.get(key) if cached is not None: return cached # 既に処理済み result = await call_llm(...) await cache.set(key, result, ttl=86400) return result
プロンプトのバージョンが変わった際に自動でキャッシュが切り替わるよう、prompt_hash をキーに含めるのが定石です。
3. スキーマ駆動でデータ変換を定義する
「どのような入力を、どのようなプロンプトで、どのような出力スキーマに変換するか」を、コードではなく データ(設定) として保持すると、変更が容易になります。
class TransformLLMConfig(BaseModel): name: str model: str system_prompt: str user_prompt_template: str # Jinja2形式で {{ field }} を埋め込む output_schema: dict # JSON Schema mode: str = "map" # "map" | "filter" | "reduce" batch_size: int = 1
この設計であれば、UIから設定を編集するだけでパイプラインを更新でき、コード変更を伴いません。変換ロジックの追加・修正を、エンジニア以外の運用担当者が安全に行える点も利点です。
4. 観測可能性を組み込む
LLM処理は「失敗しているのに気付かない」事象が発生しやすい領域です。最低限、以下の項目をログに残します。
| 項目 | 用途 |
|---|---|
| 入力プロンプト全文 | 後からの再現・デバッグ |
| 出力テキスト全文 | スキーマ違反の調査 |
| トークン数(input/output) | コスト分析 |
| レイテンシ | パフォーマンス監視 |
| モデル名・バージョン | 切替時の比較 |
| バリデーションエラー | スキーマ違反の検出 |
データ連携基盤側の設計
LLM側の特性を受け止めるため、基盤側にも対応する仕掛けが必要です。
入力境界:チャンキングと前処理
LLMへ渡す前に、データを適切な粒度へ分割します。
- テキスト:トークナイザを用いて、モデルのコンテキスト長の60〜70%を上限に分割する
- テーブル:行単位/列単位で分割し、スキーマ情報をプロンプトに含める
- PDF・画像:ページ単位またはセクション単位で分割する
出力境界:バリデーション → 永続化
LLMの出力は pydantic で受け、検証を通過したものだけをDBへ書き込みます。スキーマ違反のレコードはDead Letter Queueへ退避させ、人間がレビューする運用とします。
スループット制御
asyncio.Semaphore で同時実行数を制限するのが基本です。クラウドAPIのレート制限やローカルGPUの処理能力に合わせて並列度を調整します。
sem = asyncio.Semaphore(8) # 同時8並列 async def bounded_call(record: dict) -> dict: async with sem: return await call_llm(record) results = await asyncio.gather(*[bounded_call(r) for r in records])
アーキテクチャ全体像
[ データソース ] ↓ (CSV/SQL/REST/SaaS) [ ソースオペレーター(pydantic検証)] ↓ (dict ストリーム) [ 前処理:チャンキング・正規化 ] ↓ [ transform_llm(構造化出力強制 + 冪等キー)] ↓ (検証済み dict) [ ロードオペレーター(DB/API/ファイル)] ↓ [ 監視ログ:プロンプト・出力・レイテンシ・エラー ]
アンチパターン集
実装の現場で繰り返し観測される失敗パターンと、その回避策を挙げます。
- プロンプトをコードに直書きする → 設定として外部に切り出す
- JSON出力をスキーマなしで扱う → 必ずJSON Schemaで制約する
- 失敗時の生出力を破棄する → パース失敗時こそ生の出力をログに残す
- 大量レコードを単発で処理する → バッチ処理または並列度制御を必ず導入する
- 冪等性を考慮しない → 冪等キーは設計の初期段階から組み込む
- プロンプトのバージョン管理がない →
prompt_hashをデータに残す
まとめ
LLM I/Oとデータ連携基盤を堅牢に接続するための要点は、LLMの確率的・非決定的な性質を、基盤側の決定的な処理で包むことに尽きます。
- 構造化出力:JSON Schemaで型を制約する
- 冪等性:プロンプトハッシュを含むキーで重複処理を防ぐ
- スキーマ駆動:プロンプトと変換ルールを設定として外出しする
- 観測可能性:プロンプト・出力・レイテンシ・失敗をすべて記録する
これらを設計初期から組み込むことで、LLMを安心して業務データパイプラインへ組み込めるようになります。
