6月19日、Microsoft ISEが「Separating Deterministic Extraction from AI Inference in Industrial Summarization」と題した記事を公開した。この記事では、産業用シフトログの要約システムにおいて、決定論的な抽出処理とLLM推論を明確に分離することでスキーマ準拠率を0%から100%に改善した実装事例について詳しく紹介されている。以下に、その内容を紹介する。
「JSONは正しい、でも契約は満たしていない」という失敗から始まった
プロトタイプは動いていた。JSONは毎回パースできた。ステークホルダーも読んで頷いていた。しかし評価パイプラインを回したところ、フォーマット正当率:100%、スキーマ準拠率:0% という結果が出た。
これはMicrosoft ISEが開発した、工場などの運用シフトログを構造化サマリーに変換するシステムでの話だ。出力JSONはダッシュボード、コンプライアンスワークフロー、次のシフトの担当者が読むことを前提に設計されており、特定のセクション(イベント記録、未対応タスク、引き継ぎ事項など)が必須フィールドとして定義されていた。
問題は、LLMが要求されたセクションのサブフィールドを丸ごと省略したり、空の配列を返したりしていたことだ。次のシフトの担当者がサマリーを開いても、何も記録がない。「何もなかった」のではなく、モデルが無音で落としていただけだった。
原因はアーキテクチャにあった。LLMをシリアライザーとして使っていたことだ。
フィールドごとに「AIが必要か」を分類する
チームが採用した手法はHVE(Hypervelocity Engineering)と呼ばれるエンジニアリング哲学で、継続的な評価・人間の判断・自動フィードバックループを品質ゲートではなくアーキテクチャ上の意思決定手段として活用する考え方だ。
具体的にやったことはシンプルだった。出力スキーマの全フィールドをスプレッドシートに並べ、1つずつ問いに答えた:
このフィールドは、入力データに1:1で対応する値が既に存在するか?
この分類作業は数日ではなく数時間で完了し、過半数のフィールドが「決定論的」側に分類された。
- 決定論的フィールド:タイムスタンプ、ログエントリID、オペレーターが記録した重要度、生の説明文。これらは入力にそのまま存在する。LLMに「生成」させることは、ときどきミスをするコピー機を使うようなものだ。
- LLM必須フィールド:あるイベントが特定の種類の障害かどうかの分類、タイムライン全体を通じて問題がまだ継続中かどうかの判断、自由記述から構造化識別子を抽出すること。これらには入力データに唯一の正解が存在しない。
この分類結果をYAMLのフィールド分類レジストリとして形式化し、全フィールドを deterministic(LLM禁止)か llm_required に分類した。これが4パスパイプラインの設計原則となった。
4パスパイプラインの構造
Pass 1:決定論的抽出(LLMなし)
ソースデータから全ての確定フィールドを直接コピーし、タイムラインを時系列に並べ、監査用の生レコードを保持する。output.severity == input.severity をユニットテストで全レコードに対してアサートできる。
Pass 2:有界なモデル判断
モデルには Pass 1 のタイムラインと生の入力配列を渡し、レジストリが llm_required と分類したフィールドの判断だけを求める。回答は log_entry_id(元ログの安定した識別子)でキーイングされ、全てのAI判断が特定の入力レコードにトレース可能になる。
Pass 3:ガード付きマージ(LLMなし)
このパスが最もアーキテクチャ的に重要だ。Pass 2のAI出力を log_entry_id をJoinキーとして決定論的スキャフォールドにマージする。
# ソースレコードをlog_entry_idでインデックス化(O(1)ルックアップ)
source_by_id = {
r["log_entry_id"]: r
for r in log_data.get("conditions", [])
}
# ルーティング済みの各レコードについて、LLMではなくSOURCEから構築
for entry_id in routing_decisions.get(section_name, []):
source = source_by_id[entry_id]
record = {
# 決定論的フィールド:常にソースから
"log_entry_id": source["log_entry_id"],
"timestamp": source.get("event_time"),
"description": source.get("description"),
"severity": source.get("severity"),
# AI必須フィールド:Pass 2からのみ
"classification": ai_fields.get(entry_id, {}).get("classification"),
"identifier": ai_fields.get(entry_id, {}).get("identifier"),
}
section_records.append(record)
LLMが決定論的フィールドに誤った値を返してきた場合、システムはソースの値を使い、LLMの試みた値を違反ログに記録する。マージコードは決定論的フィールドについてLLM出力を参照しない。
Pass 4:エビデンスマッピング
サマリーが完成した後にのみ、モデルが各結論を特定のソースレコードにリンクするトレーサビリティを生成する。
0%から100%への改善過程
モジュール化パイプラインはスキーマ準拠率を0%から47%に引き上げた。15件の合成シフトログで評価を回したところ、全ての失敗ケースはオペレーターがシステム状態を何も記録していないシフトに集中していた。これは3種類の障害モードとして現れた。
修正の原則は一貫していた:「何もなかった」という事実をメタデータで記録し、内容を捏造しない。ソース条件が存在しない場合、何を調査して何も見つからなかったかを記録するエントリを生成する。
| メトリクス | 単一プロンプト | モジュール(初期) | モジュール(現在) |
|---|---|---|---|
| フォーマット正当率 | 100% | 100% | 100% |
| スキーマ準拠率 | 0% | 47% | 100% |
| 決定論的フィールドの一致 | 未検証 | 100%(テスト済) | 100%(テスト済) |
| LLM呼び出し回数 | 1回(大) | 2回(ターゲット型) | 2回(ターゲット型) |
さらに、基盤モデルを切り替えてもスキーマ準拠率が100%を維持することを確認しており、決定論的/AI分離の設計はモデル非依存であることが検証されている。
設計の本質
プロンプトに問題はなかった。問題は、ソフトウェアが担うべき仕事をLLMに依頼していたことだ。
決定論的な事実はソフトウェアが保証し、確率的な判断は明示的かつ限定的にモデルに委譲する。この分離により、必須セクションの存在は構造上保証され、全てのAI判断は特定の入力レコードにトレース可能になり、失敗は暗黙的でなく明示的になる。
詳細はSeparating Deterministic Extraction from AI Inference in Industrial Summarizationを参照していただきたい。




