AIの業務活用

Few-shot の例を使って AI 出力を安定させ、自動で検証する

求める正確な出力構造の例を AI にいくつか示し、返された JSON を使用前に検証します。小さな合成チケットデータセットを使い、書式例と自動チェックを組み合わせる方法を示します。

目次を表示

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

対象読者繰り返し発生する業務項目を AI で変換し、自由記述ではなく予測可能な機械可読出力を必要とする人向けのガイドです。

準備するもの
  • Python 3.12 と、そのバージョンを起動できるターミナルコマンド。
  • 例示プロンプトを貼り付けられる AI アシスタントへのアクセス。
  • UTF-8 JSON ファイルを保存できるテキストエディタ。
  • 検証に必要なのは Python 標準ライブラリのみです: csv, json, pathlib.

01プロンプトを書く前に出力仕様を定義する

短いサポートチケットを構造化レコードへ変換したいとします。各結果には ticket_id, priority, owner_team, action の4つのキーだけを含める必要があります。priority は LOW, MEDIUM, HIGH のいずれかです。owner_team は IT, FACILITIES, FINANCE のいずれかです。4つの値はすべて空でない文字列でなければなりません。

重要なのは、AI の回答とは独立して形式を定義することです。AI が生成するのは候補レコードです。その候補が必要な構造を満たしているかどうかは、スクリプトが判断します。

023件の合成チケットを作成する

以下のチケットは合成データであり、この記事のために作成したものです。意図した構造化結果を手作業で判断できるよう、あえて単純な内容にしています。

ticket_id合成チケット本文想定分類
T101今日の支払い処理前に給与計算スプレッドシートを開けない。HIGH · FINANCE
T102会議室 B の照明がちらついているが、部屋はまだ使用できる。LOW · FACILITIES
T103新入社員のノート PC が社内 Wi-Fi に接続できない。MEDIUM · IT

このチュートリアルでは、想定される action もあらかじめ固定されています。T101 は Check payroll workbook access、T102 は Inspect meeting room B light、T103 は Troubleshoot laptop Wi-Fi connection とします。これらの想定値は合成演習の一部であり、実際のサポート業務に対する一般的なルールではありません。

03AI に正確な形式の例を与える

Few-shot プロンプトでは、新しい入力の前に1つ以上の完成例を提示します。以下のデモでは、分類方法と正確な JSON 形状の両方を示します。確認対象となる3件とは異なる合成チケットを使用します。

text
Convert each ticket into a JSON object.

Rules:
- Return one JSON array only.
- Do not add explanations or Markdown.
- Use exactly these keys in every object: ticket_id, priority, owner_team, action.
- priority must be LOW, MEDIUM, or HIGH.
- owner_team must be IT, FACILITIES, or FINANCE.

Example 1 input:
Ticket ID: E001
Text: Printer on floor 2 is out of paper.

Example 1 output:
{"ticket_id":"E001","priority":"LOW","owner_team":"IT","action":"Refill or check floor 2 printer"}

Example 2 input:
Ticket ID: E002
Text: Expense approval file is unavailable before today's reimbursement deadline.

Example 2 output:
{"ticket_id":"E002","priority":"HIGH","owner_team":"FINANCE","action":"Check expense approval file access"}

Now process these tickets:
T101: Payroll spreadsheet cannot be opened before today's payment run.
T102: Meeting room B light is flickering but the room is still usable.
T103: New employee laptop cannot connect to the office Wi-Fi.

例ではフィールド名だけでなく、大文字のカテゴリ値、短い action 句、引用符付き JSON 文字列、周囲に説明文を付けないことも示しています。これにより、回答の提示方法に関する曖昧さを減らせます。

04AI の回答を見る前に想定出力を定義する

この合成演習では、想定回答は以下の JSON 配列です。チェッカーを実行する前に、実際の AI 回答を ai_output.json として保存してください。形式が壊れた回答を先に黙って修正しないでください。チェッカーの目的は、元の回答が仕様に従っていたかを明らかにすることです。

json
[
  {
    "ticket_id": "T101",
    "priority": "HIGH",
    "owner_team": "FINANCE",
    "action": "Check payroll workbook access"
  },
  {
    "ticket_id": "T102",
    "priority": "LOW",
    "owner_team": "FACILITIES",
    "action": "Inspect meeting room B light"
  },
  {
    "ticket_id": "T103",
    "priority": "MEDIUM",
    "owner_team": "IT",
    "action": "Troubleshoot laptop Wi-Fi connection"
  }
]

オブジェクトは正確に3件、フィールド値は合計12個あるはずです。許可された3つの priority がそれぞれ1回ずつ、3つの owner team もそれぞれ1回ずつ出現します。

05AI の回答を自動検証する

次のスクリプトを check_ai_output.py として保存してください。JSON 構文、配列長、正確なキー、データ型、許可されたカテゴリ値、ticket ID、合成演習で想定した分類を検証します。AI の回答を解析した後にだけ確認用 CSV を書き出します。

python
import csv
import json
from pathlib import Path

SOURCE = Path("ai_output.json")
OUTPUT_DIR = Path("outputs") / "few_shot_check_result"
REPORT = OUTPUT_DIR / "format_check.csv"

EXPECTED = {
    "T101": ("HIGH", "FINANCE", "Check payroll workbook access"),
    "T102": ("LOW", "FACILITIES", "Inspect meeting room B light"),
    "T103": ("MEDIUM", "IT", "Troubleshoot laptop Wi-Fi connection"),
}
REQUIRED_KEYS = {"ticket_id", "priority", "owner_team", "action"}
PRIORITIES = {"LOW", "MEDIUM", "HIGH"}
TEAMS = {"IT", "FACILITIES", "FINANCE"}


def main() -> None:
    if not SOURCE.is_file():
        raise FileNotFoundError(f"AI output not found: {SOURCE}")
    if OUTPUT_DIR.exists():
        raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")

    with SOURCE.open("r", encoding="utf-8") as stream:
        data = json.load(stream)

    if not isinstance(data, list):
        raise ValueError("Top-level JSON value must be an array.")
    if len(data) != len(EXPECTED):
        raise ValueError(f"Expected {len(EXPECTED)} objects, got {len(data)}.")

    rows = []
    seen = set()
    for index, item in enumerate(data, start=1):
        errors = []
        if not isinstance(item, dict):
            raise ValueError(f"Item {index} is not a JSON object.")

        if set(item) != REQUIRED_KEYS:
            errors.append("keys")

        for key in REQUIRED_KEYS:
            if key not in item or not isinstance(item.get(key), str) or not item.get(key).strip():
                errors.append(f"invalid_{key}")

        ticket_id = item.get("ticket_id", "")
        if ticket_id in seen:
            errors.append("duplicate_ticket_id")
        seen.add(ticket_id)

        if item.get("priority") not in PRIORITIES:
            errors.append("priority")
        if item.get("owner_team") not in TEAMS:
            errors.append("owner_team")

        expected = EXPECTED.get(ticket_id)
        if expected is None:
            errors.append("unexpected_ticket_id")
        else:
            actual = (
                item.get("priority"),
                item.get("owner_team"),
                item.get("action"),
            )
            if actual != expected:
                errors.append("content_mismatch")

        rows.append({
            "item": index,
            "ticket_id": ticket_id,
            "status": "PASS" if not errors else "FAIL",
            "errors": ";".join(errors),
        })

    missing_ids = set(EXPECTED) - seen
    if missing_ids:
        raise ValueError(f"Missing ticket IDs: {sorted(missing_ids)}")

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()
    with REPORT.open("x", encoding="utf-8", newline="") as stream:
        writer = csv.DictWriter(
            stream,
            fieldnames=["item", "ticket_id", "status", "errors"],
        )
        writer.writeheader()
        writer.writerows(rows)

    failures = sum(row["status"] == "FAIL" for row in rows)
    print(f"Objects checked: {len(rows)}.")
    print(f"Passed: {len(rows) - failures}; failed: {failures}.")
    print(f"Report: {REPORT.as_posix()}")

    if failures:
        raise RuntimeError("AI output failed one or more checks.")


if __name__ == "__main__":
    main()

06想定結果を確認する

ai_output.json が合成の想定出力と完全に一致する場合、3件すべてのオブジェクトが合格するはずです。以下の想定コンソールテキストは手作業で導出したもので、実行ログではありません。

text
Objects checked: 3.
Passed: 3; failed: 0.
Report: outputs/few_shot_check_result/format_check.csv
  • トップレベルが、配列を囲む説明文ではなく JSON 配列そのものであることを確認します。
  • 各オブジェクトに必要な4つのキーだけが含まれていることを確認します。
  • 欠落または重複しているチケットがないことを確認します。
  • priority と owner_team が許可されたラベルだけを使用していることを確認します。
  • OUTPUT_DIR を変更せずにチェッカーを再実行します。以前のレポートを上書きするのではなく FileExistsError で停止するはずです。

07よくある失敗と制限を理解する

問題チェッカーが示すべき内容
AI が JSON の前に説明文を追加するファイルが単一の有効な JSON 値ではないため json.load が失敗します。
1つのオブジェクトで priority の代わりに urgency を使う正確なキーのチェックが失敗します。
priority が urgent になっている許可値チェックが失敗します。
T103 が2回出現する重複 ticket ID のチェックが失敗し、別の想定 ID が欠落している可能性があります。
JSON 構造は有効だが分類が間違っているこの合成演習では content_mismatch が記録されます。

Few-shot プロンプトは指示を改善しますが、決定論的な出力を保証するものではありません。AI システム、設定、文脈、後続プロンプトが変われば回答も変わる可能性があります。複数回成功した後でもバリデーターは残してください。

この例では、意図的に小さなスキーマを手書きの Python チェックで検証しています。より大規模な本番スキーマでは、正式なスキーマシステムと別の意味検証が役立つ場合があります。また、有効な JSON はあくまで形式上の結果です。基礎となる分類が事実上または運用上間違っている可能性は残ります。

実行・検証の記録

2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · 標準ライブラリ: csv, json, pathlib · 未実行

  • 3件の合成チケットと、それぞれに想定される priority, owner team, action を手作業で定義しました。
  • 想定 JSON が正確に3つのオブジェクトを含み、各オブジェクトに4つのキーがあることを手作業で確認しました。
  • LOW, MEDIUM, HIGH がそれぞれ1回ずつ、IT, FACILITIES, FINANCE もそれぞれ1回ずつ出現することを手作業で確認しました。
  • JSON 解析、正確なキー、空でない文字列、許可値、重複 ID、欠落 ID、想定内容との比較についてチェッカーを確認しました。
  • 想定結果として3件合格、0件失敗になることを手作業で導出しました。
検証範囲の限界
  • この回答の作成者は AI システムに問い合わせていないため、実際のモデル出力の一貫性はテストしていません。
  • Python チェッカーは実行しておらず、JSON または CSV ファイルも作成していません。
  • 合成分類はこの演習専用の想定値であり、一般的なサポート振り分けルールではありません。
  • 有効な構造であっても、AI が生成した分類が事実として正しいことを証明するものではありません。
  • 公式ドキュメントの URL は既知のドキュメント所在地に基づいて記載していますが、ライブでは確認していません。

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

参考資料

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