構造化出力で不具合報告を JSON にする:スキーマ設計・検証・再試行の実装

#構造化出力#Claude API#Pydantic#Python#不具合報告

画面いっぱいに表示されたソースコード
写真:Pexels

自由記述の不具合報告を JSON にするなら、言語モデルの構造化出力で「形式」を保証し、Pydantic とマスタ照合で「中身」を確かめ、通らないものだけを再試行か人の確認に回す、という 3 段に分けるのが実装しやすい形です。この記事では、Claude API の構造化出力(2026年10月時点で一般提供)と Pydantic v2 を使って、その 3 段を Python で組みます。考え方は 不具合報告と作業日報をデータに変える に書いたので、ここでは実装に絞ります。

全体の構成

不具合報告(自由記述)
   │
   ▼  1. 構造化出力で取り出す(形式はAPIが保証)
DefectReport(Pydantic モデル)
   │
   ▼  2. マスタ照合・業務ルール(Pydantic の検証 + context)
   ├─ OK  → 品質記録へ登録
   └─ NG  → 3. 誤りを伝えて1回だけ再試行 → それでも NG なら人の確認へ

1 段目で保証されるのは、JSON として正しく、スキーマどおりの型であることまでです。設備番号が実在するか、工程と現象の組み合わせがありえるかは、2 段目で自分で確かめます。

準備

pip install anthropic pydantic
export ANTHROPIC_API_KEY=...

2026年10月時点の最新は、anthropic が 1.12.0、pydantic が 2.13.5 です。構造化出力は以前はベータ機能で output_format というパラメータでしたが、一般提供になって API の上では output_config.format に移り、ベータヘッダーも不要になりました。古い記事のコードをそのまま使うと 400 エラーになるので注意してください。

一方で、Python SDK の便利な関数 client.messages.parse() は、Pydantic のモデルを output_format= という名前の引数で受け取ります。SDK がモデルからスキーマを作って output_config.format として送り、返ってきた JSON を検証して parsed_output に入れてくれます。名前は同じでも、API の古いパラメータとは別物です。

1. スキーマを Pydantic で書く

from typing import Literal
from pydantic import BaseModel, Field

Process = Literal["板金", "曲げ", "溶接", "塗装", "組立", "配線", "検査"]
Phenomenon = Literal["クラック", "キズ", "変形", "寸法不良", "異物", "動作不良", "その他"]


class DefectReport(BaseModel):
    occurred_on: str | None = Field(None, description="発生日。本文の表記のまま。書かれていなければ null")
    equipment_code: str | None = Field(None, description="設備番号(例: EQ-1001)。書かれていなければ null")
    process: Process | None = Field(None, description="工程。本文から判断できなければ null")
    phenomenon: Phenomenon = Field(description="不具合の現象")
    location: str | None = Field(None, description="不具合の部位")
    suspected_cause: str | None = Field(
        None, description="推定原因。本文に無ければ null。書き手の推測なら末尾に(推定)"
    )
    action: str | None = Field(None, description="取った処置。書かれていなければ null")

設計で気をつけた点は 3 つです。

  • 集計に使う項目(工程・現象)は Literal で選択肢を固定する。自由な文字列にすると「曲げ」「ベンディング」「曲げ加工」が別々に数えられます。
  • 書かれていないことは null にさせる。description で明示しておくと、モデルが推測で埋める余地が減ります。
  • 「理由」や「考えた過程」を書かせる項目は作らない。Anthropic の文書は、モデルの思考や段階的な推論を求める項目が拒否(reasoning_extraction)の原因になりうるとし、短い説明を求めるよう勧めています。

スキーマの複雑さには上限があります。2026年10月時点の文書では、省略可能な項目は合計 24 個まで、anyOf や ["string", "null"] のような型の組み合わせを使う項目は 16 個までです。上のモデルは省略可能な項目が 6 個なので余裕があります。

2. 取り出す

import anthropic
from pydantic import ValidationError

client = anthropic.Anthropic()

SYSTEM = (
    "あなたは製造現場の不具合報告から項目を取り出す担当です。"
    "本文に書かれていないことは推測で埋めず null にしてください。"
)


class NeedsReview(Exception):
    """人の確認に回す"""


def extract(text: str) -> DefectReport:
    try:
        response = client.messages.parse(
            model="claude-sonnet-5",
            max_tokens=1024,
            system=SYSTEM,
            messages=[{"role": "user", "content": text}],
            output_format=DefectReport,
        )
    except ValidationError as e:
        # 途中で切れた出力などは、SDK の検証で弾かれる
        raise NeedsReview(f"形式の検証に失敗しました({e.error_count()}件)") from e

    if response.stop_reason == "refusal":
        raise NeedsReview("モデルが応答を拒否しました")
    if response.stop_reason == "max_tokens":
        raise NeedsReview("出力が上限で途切れました")
    return response.parsed_output

構造化出力でも、スキーマどおりにならない場合が文書に明記されています。

状況起きること扱い
拒否stop_reason が refusal。HTTP は 200 で、生成した分は課金される人の確認へ
出力の上限stop_reason が max_tokens。出力が不完全になりうるmax_tokens を上げて再試行するか、人の確認へ
選択肢の大文字・小文字enum と const の文字列は大文字・小文字が保証されない。エラーにも特別な stop_reason にもならない比較を大文字・小文字を無視して行う

3 つ目は見落としやすい点です。日本語の選択肢では起きにくいのですが、EQ-1001 のような英字を含むコードを選択肢にすると、eq-1001 で返ってきてもエラーになりません。

3. マスタと照合する

形式が正しくても、設備番号がマスタに無ければ使えません。Pydantic の検証関数に、context でマスタを渡して照合します。

from pydantic import ValidationInfo, field_validator


class CheckedReport(DefectReport):
    @field_validator("equipment_code", mode="after")
    @classmethod
    def must_exist(cls, v: str | None, info: ValidationInfo) -> str | None:
        if v is None:
            return v
        code = v.strip().upper()  # 大文字・小文字の揺れをここで吸収する
        equipment = (info.context or {}).get("equipment", set())
        if code not in equipment:
            raise ValueError(f"設備マスタに無い設備番号です: {v}")
        return code


EQUIPMENT = {"EQ-1001", "EQ-1002", "EQ-1003"}  # 実際は設備マスタから読む

report = extract(text)
checked = CheckedReport.model_validate(report.model_dump(), context={"equipment": EQUIPMENT})

Pydantic の文書のとおり、context はモデルを直接作るときには渡せないので、model_validate() を通します。マスタに無い値は、ここで ValidationError になります。

4. 再試行と人の確認への振り分け

マスタに無い値が返ってきたときは、誤りを伝えて 1 回だけ取り出し直させます。それでも通らなければ人の確認に回します。何度も繰り返すと、費用がかさむうえに、もっともらしい値を作り出させる方向に働くからです。

def process(text: str, equipment: set[str], retries: int = 1) -> CheckedReport:
    prompt = text
    for attempt in range(retries + 1):
        report = extract(prompt)
        try:
            return CheckedReport.model_validate(report.model_dump(), context={"equipment": equipment})
        except ValidationError as e:
            if attempt == retries:
                raise NeedsReview(str(e)) from e
            reason = e.errors()[0]["msg"]
            prompt = (
                f"{text}\n\n"
                f"前回の取り出し結果に誤りがありました({reason})。"
                "本文に設備番号が無い場合は null にしてください。"
            )

NeedsReview になった報告は、元の文章と理由を一覧にして人が見ます。現場で運用するときは、取り出した値・元の文章・使ったモデル名・日時を一緒に保存しておくと、あとで「なぜこの値になったか」を確かめられます。

OpenAI の API で書く場合

同じことは OpenAI の Responses API でもできます。Pydantic モデルを text_format に渡し、結果を output_parsed で受け取ります。

from openai import OpenAI

client = OpenAI()
response = client.responses.parse(
    model="gpt-6-astra",
    input=[
        {"role": "system", "content": SYSTEM},
        {"role": "user", "content": text},
    ],
    text_format=DefectReport,
)
report = response.output_parsed

OpenAI の厳密モードでは、すべての項目を必須にし、省略可能な項目は null との組み合わせで表すこと、すべてのオブジェクトに additionalProperties: false を付けることが求められます。どちらの API でも、使えない JSON Schema の機能を含めるとエラーになります。

はまりどころ

  • 最初のリクエストだけ遅い。Anthropic の構造化出力は、新しいスキーマを最初に使うときに文法を組み立てるため、待ち時間が増えます。組み立てた結果は最後に使ってから 24 時間キャッシュされます。スキーマを頻繁に変えると、そのたびに遅くなります。
  • 数値の範囲や文字数の制約は、Anthropic では API 側でサポートされない。SDK は minimum・maximum・minLength などを項目の説明文に移し、受け取ったあとに手元で検証します。範囲の確認は 3 段目の検証でも重ねて行うのが確実です。
  • Amazon Bedrock で使う場合は対応状況を確認する。2026年10月時点の Anthropic の文書では、構造化出力は Claude API や各クラウドの Claude Platform では一般提供ですが、Amazon Bedrock では旧来の連携(Opus 4.6 以前のモデル)での提供で、新しい「Claude in Amazon Bedrock」では使えないとされています。国内リージョンで処理させたい場合は、使うモデルと連携方式を先に確かめてください。
  • モデル名は変わる。この記事のモデル名は 2026年10月時点の文書に載っているものです。使う時点で各社のモデル一覧を確認してください。

取り出した結果の受け皿には、決まった形の品質記録が要ります。weaveAI FactorySuite の品質の画面は、不良コード別の内訳と処置を製造実績にひもづけて記録するので、ここで取り出した phenomenon や action を流し込む先になります。

出典

#構造化出力#Claude API#Pydantic#Python#不具合報告

執筆weaveAI 編集部(株式会社サオス)

株式会社サオスで weaveAI を開発しているチームです。製造業の業務システムと AI について、一次情報と実際の検証をもとに解説しています。

運営者情報・編集方針 →

weaveAI FactorySuite

御社の工場で、動かしてみませんか。

マスタ・在庫・製造実績・品質・保全をすぐに使え、足りない画面は AI との対話で作れます。いまお使いの帳票(紙・Excel)があれば、どの画面に置き換わるかを一緒に確認します。