AIの業務活用

乱雑なテキストをAIでJSON化しPythonで検証する

AIは整理されていない文章を構造化JSONに変換できますが、結果は別途検証する必要があります。このガイドでは、小さな合成例とPythonの検証処理を使って、必須フィールド、型、許可値、基本的な整合性を確認します。

目次を表示

この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English

対象読者メモ、メール、フォーム、その他の乱雑な文章をAIチャットアシスタントで構造化JSONに変換し、再現可能な検証手順を加えたい人向けです。

準備するもの
  • Python 3.12
  • JSONオブジェクト、文字列、数値、リストの基本的な理解

01AIが抽出したJSONを検証する理由

AIは不規則な文章を構造化されたフィールドに変換するのに便利ですが、見た目が正しいJSONでも内容が間違っていることがあります。必須キーが欠ける、数値が文字列として返る、許可されていない値が入る、元テキストにない情報をAIが推測して追加する、といった問題が起こります。

安全な方法は、抽出と検証を分けることです。まずAIに元テキストを固定された構造へ変換させます。次に、返されたJSONを明示的なルールに照らして確認します。検証だけで抽出されたすべての事実が正しいとは証明できませんが、表計算、データベース、スクリプト、業務フローへ渡す前に多くの構造的な誤りを検出できます。

02小さな合成元データとスキーマを定義する

この例は合成例です。短いサポートメモに、顧客名、チケット番号、優先度、影響を受ける製品、任意の折り返し時間が含まれているとします。

元テキスト: "Ticket 1842. Customer: Mira Lee. Login fails on the desktop app and web portal. Priority is high. Please call after 15:30. Products affected: Desktop, Web."

フィールドルール
ticket_id必須の整数
customer必須の空でない文字列
priority必須: low、medium、highのいずれか
products必須の空でない文字列リスト
callback_timeHH:MM形式の文字列またはnull

このスキーマは意図的に単純にしてあり、Python標準ライブラリだけで確認できます。単にJSONとして解析できるかどうかを確認するより厳しいルールです。

03制約を付けてAIにJSONを依頼する

使用可能なフィールドと、情報が欠けている場合の扱いを正確に指定します。たとえば「次の文章を、ticket_id、customer、priority、products、callback_timeというキーだけを持つ1つのJSONオブジェクトに抽出してください。ticket_idは整数、priorityはlow、medium、highのいずれか、productsは文字列リストにしてください。callback_timeが書かれていない場合はnullを使ってください。欠けている事実を推測しないでください。JSONだけを返してください」と依頼できます。

この合成メモに対する正しい結果は、ticket_idが1842、customerがMira Lee、priorityがhigh、productsがDesktopとWeb、callback_timeが15:30です。

もっともらしいが不正なAI結果では、ticket_idを文字列"1842"として返したり、priorityを"urgent"に変えたり、issue_summaryのような許可されていないフィールドを追加したりすることがあります。見た目が整理されていても、合意したスキーマを満たしていません。

04PythonでJSONを検証する

次のスクリプトはPython標準ライブラリだけを使用します。合成AI応答を読み込み、予期しないキーがないか、必須キーと型が正しいか、priorityが許可された値か、productsリストが正しいか、callback_timeの形式が正しいかを確認します。結果はoutputsに保存し、同じレポートがすでに存在する場合は上書きせず停止します。

python
import json
import re
from pathlib import Path

AI_RESPONSE = '''{
  "ticket_id": "1842",
  "customer": "Mira Lee",
  "priority": "urgent",
  "products": ["Desktop", "Web"],
  "callback_time": "15:30",
  "issue_summary": "Login problem"
}'''

REQUIRED_KEYS = {
    "ticket_id",
    "customer",
    "priority",
    "products",
    "callback_time",
}

ALLOWED_PRIORITIES = {"low", "medium", "high"}
TIME_PATTERN = re.compile(r"^(?:[01]\d|2[0-3]):[0-5]\d$")

errors = []

try:
    data = json.loads(AI_RESPONSE)
except json.JSONDecodeError as exc:
    raise SystemExit(f"Invalid JSON: {exc}")

if not isinstance(data, dict):
    errors.append("Top-level value must be a JSON object.")
else:
    actual_keys = set(data)
    missing = REQUIRED_KEYS - actual_keys
    unexpected = actual_keys - REQUIRED_KEYS

    if missing:
        errors.append("Missing keys: " + ", ".join(sorted(missing)))
    if unexpected:
        errors.append("Unexpected keys: " + ", ".join(sorted(unexpected)))

    if "ticket_id" in data and not isinstance(data["ticket_id"], int):
        errors.append("ticket_id must be an integer.")

    if "customer" in data:
        customer = data["customer"]
        if not isinstance(customer, str) or not customer.strip():
            errors.append("customer must be a non-empty string.")

    if "priority" in data:
        priority = data["priority"]
        if not isinstance(priority, str) or priority not in ALLOWED_PRIORITIES:
            errors.append("priority must be low, medium, or high.")

    if "products" in data:
        products = data["products"]
        if not isinstance(products, list) or not products:
            errors.append("products must be a non-empty list.")
        elif not all(isinstance(item, str) and item.strip() for item in products):
            errors.append("Every product must be a non-empty string.")

    if "callback_time" in data:
        callback = data["callback_time"]
        if callback is not None:
            if not isinstance(callback, str) or not TIME_PATTERN.fullmatch(callback):
                errors.append("callback_time must be HH:MM or null.")

status = "PASS" if not errors else "FAIL"
lines = [f"Validation: {status}"]
lines.extend(f"- {error}" for error in errors)

output_dir = Path("outputs")
output_dir.mkdir(exist_ok=True)
output_file = output_dir / "json_validation.txt"

if output_file.exists():
    raise SystemExit(f"Stop: {output_file} already exists.")

output_file.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Wrote {output_file}")

05構造検証の結果を正しく解釈する

不正な合成応答は3つの理由で検証に失敗するはずです。ticket_idが整数ではなく文字列、priorityが許可集合にないurgent、issue_summaryが予期しないキーです。productsリストとcallback_timeの形式は構造ルールを満たしています。

確認項目AIの値期待値結果
ticket_id"1842"整数FAIL
customer"Mira Lee"空でない文字列PASS
priority"urgent"low、medium、highFAIL
products["Desktop", "Web"]空でない文字列リストPASS
callback_time"15:30"HH:MMまたはnullPASS
追加キーissue_summaryなしFAIL

スキーマ検証に失敗した場合、通常は値を黙って型変換するより、結果を修正対象として扱う方が安全です。"1842"を自動的に1842へ変換すると、抽出指示に従わなかった事実が隠れることがあります。自動変換を許可するかは後続処理の要件によって決めます。

06抽出値を元テキストと比較する

構造検証は1つの層にすぎません。重要なフィールドは元テキストと直接比較する必要があります。この例では、ticket 1842、Mira Lee、high、Desktop、Web、15:30はすべて元テキストに明示されています。一方、元テキストにはurgentという語はないため、highをurgentに変えることは単なる書式変更ではなく、元の値を変更することになります。

  • 識別子と名前を元テキストの正確な記述と照合します。
  • priorityのような選択式フィールドでは、元テキストの意味とスキーマ上の許可値の両方を確認します。
  • リストの各項目が実際に元テキストに書かれているか確認します。
  • 任意フィールドが元テキストにない場合は推測せずnullを使います。
  • スキーマが追加プロパティを明示的に許可していない場合は、新しいフィールドを拒否します。
  • 正規化と情報の創作を区別します。大文字小文字の統一は許容できても、存在しない期限を追加することは別です。

07よくあるミスと限界

よくあるミスは、json.loadsが応答を読み込めるかだけを確認することです。解析に成功しても、それは文法的に正しいJSONであることしか示しません。必須キー、型、値が正しいことまでは保証しません。もう1つのミスは、任意の追加フィールドを許可することです。そうすると後続コードが、元の仕様に含まれていなかった情報へ依存する可能性があります。

スキーマが大きくなると、手書きの検証コードは保守しにくくなります。その場合は専用のスキーマ方式や検証ライブラリが適していますが、基本的な問いは変わりません。どのフィールドが必須か、どの型を許可するか、どの値を認めるか、不足情報をnullで表現してよいかを決める必要があります。

検証は、元テキスト自体が完全で信頼できることも保証しません。元テキストに誤ったチケット番号がある場合や文章が曖昧な場合、構造的に正しく抽出してもその問題は残ります。検証や業務ルールで問題が起きたときに重要なフィールドを追跡できるよう、元テキストも保持しておくと便利です。

実行・検証の記録

2026-09-21 · hand-checked example · Python 3.12

  • 合成元テキストにticket 1842、customer Mira Lee、priority high、products DesktopとWeb、callback time 15:30が含まれていることを確認しました。
  • 想定スキーマに許可されるキーがticket_id、customer、priority、products、callback_timeの5つだけであることを確認しました。
  • 不正な例で整数1842ではなく文字列"1842"を使っていることを確認しました。
  • urgentが許可されたpriority集合low、medium、highに含まれていないことを確認しました。
  • issue_summaryが定義したスキーマでは予期しないキーであることを確認しました。
  • ["Desktop", "Web"]が空でない文字列リストのルールを満たすことを確認しました。
  • 15:30が指定した24時間制のHH:MMパターンに一致することを確認しました。
  • スクリプトがoutputs/json_validation.txtだけに書き込み、そのファイルがすでに存在する場合は停止する構成であることを確認しました。
検証範囲の限界
  • Pythonコードは実行していません。合成例、検証分岐、想定される失敗項目はコードを読んで確認しました。
  • この手書き検証処理はここで示した小さなスキーマだけを対象とし、一般的なJSON Schema実装ではありません。
  • 構造検証だけでは抽出値が事実として正しいことを証明できないため、重要な値は元テキストと比較する必要があります。
  • この例では、失敗した結果を自動的にAIへ返して修正させたり、任意の入力テキストに対してセキュリティフィルタリングを行ったりしません。

サイト全体の執筆・検証方針

参考資料

説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。