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

自由記述の不具合報告を 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 を流し込む先になります。
出典
- Anthropic「Structured outputs」(2026年10月8日確認) https://platform.claude.com/docs/en/build-with-claude/structured-outputs
- OpenAI「Structured Outputs」(2026年10月8日確認) https://developers.openai.com/api/docs/guides/structured-outputs
- OpenAI「openai-python helpers」 https://github.com/openai/openai-python/blob/main/helpers.md
- Pydantic「Models」 https://pydantic.dev/docs/validation/latest/concepts/models/
- Pydantic「Validators」 https://pydantic.dev/docs/validation/latest/concepts/validators/
- PyPI「anthropic」 https://pypi.org/project/anthropic/
- 参考(中国語)CSDN「让 AI 100% 返回合法 JSON:Schema 校验 + 自动重试,生产级可靠性」(2026年6月) https://blog.csdn.net/smile_tianya/article/details/161633795
- 参考(中国語)CSDN「手把手教你用大模型实现结构化信息提取:从 Pydantic 建模到少样本提示实战」(2025年4月) https://blog.csdn.net/The_Thieves/article/details/147293904


