ライフスタイル

JSON 出力と結果の検証

イベント列と最終結果を区別し、構造化出力を解析して不足項目や不正形式を検出します。

読了目安 20 分 · 操作 25 分

独自の手順図です。製品画面の画像ではありません。
画像:Mokaair (© Mokaair)
この記事の目次
  1. 目標と教材
  2. 手順1:イベントと最終回答を分ける
  3. 手順2:固定の入力と契約を用意する
  4. 手順3:1回実行して段階的に検証するプログラム
  5. 手順4:実行して内容を確認する
  6. 手順5:モデル利用のない3つの失敗実習
  7. 復元・制限・次の学習

目標と教材

この段落の教材・資料:

手順1:イベントと最終回答を分ける

--json は stdout を JSONL、つまり1行ごとに独立した JSON オブジェクトにします。単一の配列でも最終回答だけでもないので、events.jsonl 全体を1回の json.loads に渡しません。--output-schema は最終回答の形を指定し、-o はその回答を final.json に保存します。1回でイベントと構造化回答を残し、stderr に診断を保存できます。拡張子は名前であり、形式はフラグと内容で決まります。

ファイル内容合格条件
status.jsonラッパーが記録した終了状態exited かつ exit_code 0
events.jsonlCLI の行単位イベント各行を解析でき、完了があり失敗がない
final.json最終構造化回答欄・型・版・値を検証
stderr.log進行と診断調査用で回答ではない
schema.json今回要求した形式期待する契約と保存

これらの名前は教材の約束で、Codex が常に自動作成するファイル一覧ではありません。

手順2:固定の入力と契約を用意する

自分の空の exec-json-lab で git init を実行し、下記 tasks.md を UTF-8 で作成します。前編の教材を使うなら完全一致を確認します。異なる版の混用を防ぐため検証器は exec-practice-1 だけを受け付けます。手で総数3、完了1、未完了2を確認します。実データに変える場合は新しい版と検証契約を定義し、何でも通るまで検証を削除しません。

tasks.md · markdown
# Practice tasks
Revision: exec-practice-1

- [x] Read the guide
- [ ] Create a practice file
- [ ] Verify the result

手順3:1回実行して段階的に検証するプログラム

下記全体を同じフォルダーの run_summary.py に保存します。標準ライブラリだけなので pip install は不要です。新しい出力フォルダー、schema、running 状態を保存してから引数配列で Codex を起動し、モデル文字列から shell 命令を組み立てません。120秒は教材の設定で Codex 固定制限ではありません。タイムアウト・起動失敗・非0終了は証拠を残して回答を不採用とし、自動再試行しません。

Windows では単体版の codex.exe を使います。PowerShell で Get-Command codex -All を実行し、インストール済み .exe を確認します。npm の .cmd/.ps1 ラッパーや別名しかない場合、Python は PowerShell と同じ方法でコマンドを探しません。から公式の単体版を導入し、新しいターミナルで再確認します。対処として shell=True やシェル文字列の連結に変更しません。macOS/Linux はターミナルと Python の PATH が同じことを確認します。

run_summary.py(全体) · python
"""Run once, retain evidence, and accept only a validated practice summary."""
import argparse
import json
from pathlib import Path
import subprocess
import sys

SCHEMA = {
    "type": "object",
    "properties": {
        "revision": {"type": "string"},
        "total": {"type": "integer", "minimum": 0},
        "completed": {"type": "integer", "minimum": 0},
        "pending": {"type": "integer", "minimum": 0},
    },
    "required": ["revision", "total", "completed", "pending"],
    "additionalProperties": False,
}
PROMPT = (
    "Read only tasks.md. Return its Revision marker and checkbox counts "
    "as revision, total, completed and pending. Do not edit input files "
    "or use external services."
)


def verify(run):
    status = json.loads((run / "status.json").read_text(encoding="utf-8"))
    if (not isinstance(status, dict)
            or status != {"state": "exited", "exit_code": 0}
            or type(status.get("exit_code")) is not int):
        raise ValueError("Process did not exit successfully")
    events = []
    for number, line in enumerate((run / "events.jsonl").read_text(encoding="utf-8-sig").splitlines(), 1):
        if not line.strip():
            continue
        event = json.loads(line)
        if not isinstance(event, dict) or not isinstance(event.get("type"), str):
            raise ValueError(f"Invalid event on line {number}")
        events.append(event["type"])
    if (events.count("turn.completed") != 1 or events.count("turn.started") != 1
            or events.index("turn.started") > events.index("turn.completed")):
        raise ValueError("Missing or ambiguous completed turn")
    if "turn.failed" in events or "error" in events:
        raise ValueError("Failure event requires investigation")
    result = json.loads((run / "final.json").read_text(encoding="utf-8-sig"))
    if not isinstance(result, dict) or set(result) != set(SCHEMA["required"]):
        raise ValueError("Unexpected or missing final fields")
    if result["revision"] != "exec-practice-1":
        raise ValueError("Unexpected input revision")
    if any(type(result[key]) is not int or result[key] < 0 for key in ("total", "completed", "pending")):
        raise ValueError("Counts must be nonnegative integers, not booleans")
    if result["total"] != result["completed"] + result["pending"]:
        raise ValueError("Inconsistent count arithmetic")
    return result


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("run_name")
    parser.add_argument("--verify-only", action="store_true")
    args = parser.parse_args()
    root = Path.cwd().resolve()
    if Path(args.run_name).name != args.run_name or args.run_name in {".", ".."}:
        parser.error("Use a folder name, not a path")
    run = root / args.run_name
    if not args.verify_only:
        if not (root / "tasks.md").is_file():
            parser.error("Missing tasks.md in the current folder")
        run.mkdir()  # Refuse to overwrite an earlier run.
        schema_path = run / "schema.json"
        schema_path.write_text(json.dumps(SCHEMA, indent=2) + "\n", encoding="utf-8")
        status_path = run / "status.json"
        status_path.write_text(json.dumps({"state": "running"}) + "\n", encoding="utf-8")
        command = ["codex", "exec", "--sandbox", "read-only", "--ephemeral", "--json",
                   "--output-schema", str(schema_path), "-o", str(run / "final.json"), PROMPT]
        try:
            with (run / "events.jsonl").open("xb") as stdout, (run / "stderr.log").open("xb") as stderr:
                process = subprocess.run(command, cwd=root, stdout=stdout, stderr=stderr, timeout=120, check=False)
            status = {"state": "exited", "exit_code": process.returncode}
        except subprocess.TimeoutExpired:
            status = {"state": "timeout"}
        except OSError:
            status = {"state": "launch_failed"}
        status_path.write_text(json.dumps(status) + "\n", encoding="utf-8")
    result = verify(run)
    # Output data only: never execute text returned by the model.
    print(json.dumps(result, ensure_ascii=False))


if __name__ == "__main__":
    try:
        main()
    except (OSError, ValueError) as error:
        print(f"Not accepted: {error}", file=sys.stderr)
        sys.exit(1)

手順4:実行して内容を確認する

教材フォルダーの Windows PowerShell · powershell
py -3 --version
py -3 run_summary.py run-01
$practiceExit = $LASTEXITCODE
$practiceExit
教材フォルダーの macOS/Linux · sh
python3 --version
python3 run_summary.py run-01
practice_exit=$?
printf '%s\n' "$practice_exit"

Windows に py がなく python --version が Python 3 なら py -3 を python に替えます。版 exec-practice-1、total 3、completed 1、pending 2 のオブジェクトと0終了を期待し、run-01 の5ファイルを確認します。検証器は欄の不足・追加、負数、整数の代わりの true、計算不一致を拒否します。ただし3/2/1は合計が合っても内容が誤りなので、一覧との照合も必要です。schema 適合だけで事実の正しさは証明できません。

手順5:モデル利用のない3つの失敗実習

ファイル管理画面で run-01 全体を run-bad-fields にコピーし、コピー final.json の pending だけを削除します。下記 verify-only は stderr に Not accepted を出し、非0終了するはずです。元の run-01 を別に run-bad-events へコピーし、イベントオブジェクト1つの最後の閉じ波括弧を消してそのフォルダーを検証します。これも失敗します。3つ目の run-bad-status は status.json の exit_code だけ7にし、正しい final.json でも拒否されることを確認します。誤りを重ねず、各例は不変の原本から作ります。

Windows:Codex を呼ばずコピーを検証 · powershell
py -3 run_summary.py run-bad-fields --verify-only
$LASTEXITCODE
macOS/Linux:コピーだけ検証 · sh
python3 run_summary.py run-bad-fields --verify-only
echo $?

応用:形式は合格、内容は不一致

元の run-01 を未使用の run-wrong-counts に複製し、final.json だけ次のオブジェクトへ置き換えます。status と events を保ち、同フォルダーで verify-only を実行します。型・版・合計が適合するので終了 0 でも、tasks.md との照合では拒否します。この検証器は入力を再読せず、最終ファイルとイベント内回答の同一性も照合しないため、一般的な事実検証器ではありません。

run-wrong-counts/final.json:意図的に誤った件数 · json
{
  "revision": "exec-practice-1",
  "total": 3,
  "completed": 2,
  "pending": 1
}

「構造合格/内容拒否」と記録し、元の記録や後続処理に反映しません。未変更 run-01 の検証と 3/1/2 を再確認します。次の CI 篇では固定教材の正確な値を比較して、この採用条件を実行可能にします。

復元・制限・次の学習

復元のため Codex に JSON 修正を頼みません。失敗コピーを証拠として残し、元の run-01 を再検証すると合格するはずです。次の実行は run-02 とし、既存 run-01 の上書きは拒否されます。launch_failed は同じ端末で codex --version を確認し、timeout はを読んでから再試行を判断します。このラッパーは1回の起動を管理するだけで、ホストの全子孫プロセスや分散実行ロックを管理しません。

CLI イベントは増える可能性があるため未知種を保存し、固定 item 数で成功を決めません。未完了や失敗を含むターンは調査のため保守的に拒否します。1ターンの検証であり、任意の再開済み複数ターン記録には直接適用しません。Windows・macOS・Linux は同じ Python を使い、携帯でローカルスクリプトは動かしません。Python/CLI 版、入力版、ファイル、手計算を記録して へ進みます。参考検証は模擬イベントと実際のモデル出力を区別し、教材 JSON を実 API 回答と称しません。

独自の手順図です。製品画面の画像ではありません。
独自の手順図です。製品画面の画像ではありません。 · 画像:Mokaair (© Mokaair)
詳しい説明を読む

Three numbered stages: identify the starting point, perform the exercise, and verify the result. Original illustration, not a product screenshot.

総目次へ

  • ライフスタイル

    Codex 学習ガイド:全記事の目次

    導入と最初のタスクから MD の指示、高度な連携まで、60 レッスン・十単元を予定しています。習熟度、環境、目的、コマンドで次の記事を探せます。未公開の記事には状態を表示します。

  • ライフスタイル

    Worktree とタスクの分離

    Worktree は一つの Git リポジトリに別ブランチの作業場所を作ります。ファイルは分かれても DB、ポート、外部サービスは共有される場合があります。

  • ライフスタイル

    実践:小さな Web サイトを作る

    brief.md から Small Steps のタスクサイトを計画・制作し、追加、完了、削除、絞り込み、ローカル保存を実装します。HTML、CSS、データ関数、画面イベント、テストを分け、Node とブラウザーで検証して再起動・復元の手順を残します。

  • ライフスタイル

    使用量と効率:やり直しを減らす

    条件、モデル設定、時間、成果を記録し、不要な再試行と過剰な文脈を減らします。

最新の旅の情報・ガイド

出典

ライフスタイル