1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LLMが出すJSONの壊れ方を日本語で返す検証APIを作りました(無料枠あり)

1
Posted at

この記事は AI(Claude)が下書きし、運営者が内容を確認して公開しています。

何が困るのか

LLM に「JSON で出力して」と頼んでも、そのまま json.loads に渡せないことがあります。たとえば次のような壊れ方です。

  • 末尾カンマ: {"name": "田中", "age": 20,}
  • シングルクォートで囲まれている
  • ``` のコードブロックで囲まれている
  • 出力が途中で切れている
  • 構文は正しいが、必須項目がない、型が違う("age": "20" のように数値が文字列になっている)

JSONDecodeError だけでは、LLM に何を直させればよいかが伝わりにくいです。そこで、JSON の構文・型・必須項目などを検証し、日本語のエラー説明と、LLM にそのまま再送できる修正指示テキストを返す API を作りました。

検証は JSON Schema に基づくルールベースで、LLM は使っていません。同じ入力なら結果は毎回同じです。

この API で何が返るか

エンドポイントは POST https://oxbox-json-validator.fly.dev/validate です。以下は公開中の API で実際に取得したレスポンスです。

1. 構文エラー(末尾カンマ)

リクエスト本文:

{"json":"{\"name\": \"田中\", \"age\": 20,}"}

レスポンス:

{"valid":false,"error_count":1,"errors_truncated":false,"errors":[{"code":"json_syntax","path":"$","message":"末尾にカンマ(,)が余分に付いている可能性があります(JSON では不可)。(1行目 26文字目付近)","fix":"} や ] の直前のカンマを取り除いてください。","position":{"line":1,"column":26}}],"retry_prompt":"直前に出力されたJSONに問題がありました。次の点をすべて修正し、JSONだけを出力し直してください。\n1. } や ] の直前のカンマを取り除いてください。","usage":{"used":1,"limit":100,"month":"2026-11"}}

原因と位置(1行目 26文字目付近)、直し方が返ります。

2. スキーマ違反(必須項目なし・型違い)

リクエスト本文:

{"json":"{\"age\": \"20\"}","schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"age":{"type":"integer"}}}}

レスポンス:

{"valid":false,"error_count":2,"errors_truncated":false,"errors":[{"code":"required","path":"$","message":"$ に必須項目 \"name\" がありません。","fix":"$ に \"name\" を追加してください。","position":null},{"code":"type","path":"$.age","message":"$.age の型が違います。整数である必要がありますが、文字列が渡されました。","fix":"$.age を整数の値に直してください。","position":null}],"retry_prompt":"直前に出力されたJSONに問題がありました。次の点をすべて修正し、JSONだけを出力し直してください。\n1. [$] $ に \"name\" を追加してください。\n2. [$.age] $.age を整数の値に直してください。","usage":{"used":2,"limit":100,"month":"2026-11"}}

エラーが複数ある場合も、retry_prompt に番号付きでまとまります。エラーは最大50件まで返り、超えた分は errors_truncated: true になります。

3. 正常

同じスキーマで {"name": "田中", "age": 20} を送った場合です。

{"valid":true,"error_count":0,"errors_truncated":false,"errors":[],"retry_prompt":null,"usage":{"used":3,"limit":100,"month":"2026-11"}}

retry_prompt は不合格のときだけ入り、合格時は null です。

主なエラー種別(code)

json_syntax(構文)、type、required、additional_properties、enum、const、minimum/maximum 系、min_length/max_length、pattern、format、min_items/max_items、unique_items、one_of_*/any_of/not などに対応しています。エラー文は日本語が既定で、lang に "en" を指定すると英語になります。

使い方

1. API キーを申し込む

無料 API キーの申込フォーム から申し込みます。キーは運営者が手動で発行し、メールで送ります。通常2営業日以内です。

2. cURL で試す

BASE_URL=https://oxbox-json-validator.fly.dev

curl -X POST "$BASE_URL/validate" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "json": "{\"name\": \"太郎\", \"age\": \"20\"}",
    "schema": {
      "type": "object",
      "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
      "required": ["name", "age"],
      "additionalProperties": false
    }
  }'

認証は X-API-Key: <キー> のほか、Authorization: Bearer <キー> でも行えます。

リクエストの項目は次のとおりです。

項目 型 説明
json 文字列(必須) 検証したい JSON 文字列(LLM の出力そのまま)
schema オブジェクト(任意) JSON Schema。省略すると構文のみ検証。外部 $ref は非対応
lang "ja" / "en"(任意) エラー文の言語。既定は "ja"

json には、パース済みのオブジェクトではなく文字列のまま渡します。

3. Python で試す

import requests

BASE_URL = "https://oxbox-json-validator.fly.dev"

schema = {"type": "object",
          "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
          "required": ["name", "age"]}

llm_output = '{"name": "太郎", "age": "20"}'   # LLM が返した文字列

res = requests.post(
    f"{BASE_URL}/validate",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"json": llm_output, "schema": schema},
    timeout=10,
)
data = res.json()
if not data["valid"]:
    # LLM にそのまま再送できる修正指示
    print(data["retry_prompt"])

4. エージェントの再試行ループに組み込む

retry_prompt を LLM への次の入力に足すだけで、再試行ループが作れます。以下は上の Python 例を土台にした組み込みの一例です。call_llm はお使いの LLM 呼び出し関数に置き換えてください(ここでは仮の関数です)。

import json
import requests

BASE_URL = "https://oxbox-json-validator.fly.dev"

def validate(llm_output: str, schema: dict) -> dict:
    res = requests.post(
        f"{BASE_URL}/validate",
        headers={"X-API-Key": "YOUR_API_KEY"},
        json={"json": llm_output, "schema": schema},
        timeout=10,
    )
    res.raise_for_status()  # 401 / 413 / 422 / 429 などはここで例外になる
    return res.json()

def generate_json(prompt: str, schema: dict, max_retries: int = 3) -> dict:
    current_prompt = prompt
    for _ in range(max_retries + 1):
        llm_output = call_llm(current_prompt)  # 仮の関数: LLM の出力文字列を返す
        data = validate(llm_output, schema)
        if data["valid"]:
            return json.loads(llm_output)
        # 直前の出力と修正指示を付けて、もう一度依頼する
        current_prompt = (
            prompt
            + "\n\n直前の出力:\n" + llm_output
            + "\n\n" + data["retry_prompt"]
        )
    raise RuntimeError("規定回数内に有効な JSON を得られませんでした")

注意点です。

  • 検証を1回呼ぶごとに、無料枠(月100件)を1件消費します。max_retries は小さめにしておくと安心です。
  • JSON が不正でも HTTP ステータスは 200 で返ります。合否は valid で判定してください。
  • 429 にはレート制限(rate_limited、Retry-After 付き)と月間上限超過(quota_exceeded)があります。ループ内で扱いを分けてください。

制限と注意

  • 提供プラン: 現在は無料プラン(月100件)のみです。有料プランは準備中です。
  • サイズ上限: 1リクエスト 100,000 バイトまでです。超えると 413 になります。
  • レート制限: 1キーあたり毎分60回です。超えると 429(rate_limited、Retry-After 付き)になります。
  • 正規表現(pattern)の制限: スキーマの pattern / patternProperties は、1つ256文字・1スキーマ20個までです。超えると 422 PATTERN_LIMIT_EXCEEDED になります。また、検証には2秒の時間制限があり、超えると中断されて 422 VALIDATION_TIMEOUT になります。pattern を単純にし、入れ子の繰り返しは避けてください。
  • スキーマの制限: 入れ子が深すぎるスキーマや不正なスキーマは 422 になり、利用回数は消費しません。外部 $ref は非対応です。
  • 提供していない機能: バッチ処理、Webhook、JSON の自動修復は提供していません。
  • ペイロードは保存しません: 送られた JSON とスキーマは保存せず、ログに残るのは利用件数だけです。ただし、個人情報・機密情報・認証情報を含む JSON は送らないでください。
  • 初回リクエストが遅いことがあります: Fly.io(東京リージョン)でホスティングしており、アクセスがないと停止し、最初のリクエストで起動します。そのため初回は数秒かかることがあります。クライアントのタイムアウトには余裕を持たせてください。

まとめ

LLM が出す JSON の「壊れ方」を、原因・位置・直し方つきの日本語で返し、retry_prompt を LLM に戻せるようにする API です。構文エラーとスキーマ違反を1回の呼び出しで検証でき、エージェントの再試行ループに組み込めます。無料枠は月100件です。気になった方は、キーを申し込んで試してみてください。

リンク・運営者情報

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?