> ## Documentation Index
> Fetch the complete documentation index at: https://developer.box.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 抽出 (構造化) APIのベストプラクティス

> 抽出 (構造化) ジョブのペースを調整し、メタデータを保持して、すでに正常に完了したファイルを再処理しないようにします。

一部の抽出 (構造化) 統合では、エラーや、すでに正常に完了しているファイルの再処理が発生します。この場合、新しいメタデータは生成されないのに、月間AIユニットの割り当てが消費されます。このガイドでは、各ファイルが1回だけ抽出されるよう、ジョブのペースを調整し、抽出結果を保持し、スキーマを設計する方法について説明します。

パラメータの詳細とSDKのサンプルについては、[メタデータを抽出 (構造化) APIリファレンス](https://developer.box.com/reference/post-ai-extract-structured.md)と、[ファイルからメタデータを抽出する (構造化)](https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata-structured.md) のガイドを参照してください。

> [!NOTE]
>
> このガイドでは抽出 (構造化) API (`POST /2.0/ai/extract_structured`) について説明します。カスタムコードを使用しないフォルダベースの抽出を行う場合は、BoxウェブアプリでBox Extractを構成してください。

## 抽出の仕組み

Box AIは、変換、OCR、レイアウト解析を実行してから、スキーマに基づいてエージェントによる抽出ループを実行します。ユーザーは1つのリクエストにつき1つのファイルを送信します。処理時間は、ページ数とレイアウトの密度によりますが、通常10秒から45秒です。

## APIを使用する場合

* エージェントの上書き、トークンの制限、インラインフィールド、信頼度スコア、または既存のパイプラインへの統合など、プログラムを通じた制御が必要な場合には**抽出 (構造化) API**を使用します。
* ユーザーがフォルダにファイルをアップロードし、APIの調整を行わずに抽出する場合には、製品UIで**Box Extract**を使用します。Box Extractを使用すると、自分で統合を構築したり管理したりせずに済むため、開発の複雑さが減ります。詳細については、[Box Extract](https://www.box.com/extract)を参照してください。

![メタデータテンプレート、AIエージェント、抽出ポリシーが表示されている、Box Extractの \[抽出エージェントの構成\] パネル](https://mintcdn.com/box/ZZDSIWI5tS4nNq8k/images/guides/box-ai/box-extract-configure-agent.png?fit=max\&auto=format\&n=ZZDSIWI5tS4nNq8k\&q=85\&s=b4c1f61bdd5315f43b51e3dd25102d97)

## 開始する前に

* 企業でBox AIが有効になっていることを確認します。
* 月間の処理キャパシティでワークロードに対応できることを確認します。大規模なバックフィルを行う場合は、ジョブを実行する前に、利用可能なキャパシティの引き上げをリクエストします。
* 大規模な移行またはアーカイブのバックフィルを行う場合は、キャパシティを計画できるようBoxアカウントチームに通知します。
* アプリで \[**AIを管理する**] を有効にします。また、ファイルの読み取り (ファイルにメタデータを追加する場合は書き込みも) を有効にします。
* 本番実行の前に、少数のサンプルファイルでスキーマと期待される出力を確認します。

## 1つのファイルにつき1回抽出する設計

抽出ジョブはそれぞれ独立して実行されます。抽出されたメタデータは、Boxメタデータテンプレートを使用してファイルに保存するか、独自のストアに保存します。すでに正常な抽出結果があるファイルに対して、再度抽出を呼び出さないようにしてください。

スキーマが安定しており、Boxで値をインデックス化する場合は、メタデータテンプレートを使用します。1回のみ使用するスキーマやよく変更されるスキーマの場合は、インラインの`fields`を使用します。

## リクエストの構成

### エージェントを選択する

シンプルなドキュメントの場合は標準のエージェントで十分です。複数列のレイアウト、スキャンされたファイル、情報量の多い表、ネストされた`struct`フィールドや`table`フィールドの場合は、抽出エージェント (強化) を使用します。

```json theme={null}
{
  "ai_agent": {
    "type": "ai_agent_id",
    "id": "enhanced_extract_agent"
  }
}
```

成功したレスポンスには、実行されたモデルとプロセッサを示す`ai_agent_info`が含まれます。これを開発中にチェックして、上書きが有効であることを確認します。

### クライアントのタイムアウトを設定する

HTTPクライアントのタイムアウトを**120秒**以上に設定します。これにより、モデルによる処理が続いている間に、複数ページのドキュメントがキャンセルされません。

### スキーマに含める項目を絞り込む

フィールドの数が多いと処理時間も長くなります。多数のフィールドがある場合は、関連するフィールドをグループ化して、より小さなテンプレートに分けてください。たとえば、1つのテンプレートに請求関連のフィールドをまとめて、別のフィールドにベンダーの連絡先情報をまとめます。

### 長い回答のために完了に必要なトークン数を引き上げる

スキーマで長い要約、条項の抜粋、または多数の表の行を求める場合は、エージェントの上書きで`num_tokens_for_completion`を引き上げます。

```json theme={null}
{
  "items": [{ "id": "123456789", "type": "file" }],
  "fields": [
    {
      "key": "contract_summary",
      "type": "string",
      "prompt": "Summarize the key obligations."
    }
  ],
  "ai_agent": {
    "type": "ai_agent_extract_structured",
    "basic_text": { "num_tokens_for_completion": 4096 },
    "long_text": { "num_tokens_for_completion": 4096 }
  }
}
```

### 信頼度と引用情報を確認する

レビューを自動化するためにフィールドごとの信頼度とドキュメントの引用情報が必要な場合は、`include_confidence_score`と`include_reference`を`true`に設定します。

出力は実行のたびにわずかに異なる場合があります。メタデータの書き込みや下流の作業のトリガーを行う前に、スキーマに照らして検証し、信頼度スコアを使用してください。

## ペース調整と同時実行

* **8つ以上**の並列ワーカーを実行します。
* それぞれのワーカーでは、必ず前のリクエストが完了してから次のリクエストを送信します。
* 小さいバッチから始めて、その後、同時実行数を増やしていきます。
* 可能な場合は、より短いドキュメントを先に処理します。50ページ程度のファイルやスキャン量の多いファイルはまとめて、ピーク時間外に処理します。

一度に数百のリクエストを集中して送信すると、一定数のリクエストを継続的に送信するよりもパフォーマンスが低くなります。APIから`412`または`429`が返された場合は、しばらく待ってから、クラスタのキャパシティが追い付くよう送信レートを少し下げて再開してください。

## 例: ペース調整したバッチランナー

次の例では、同時実行ワーカーと指数バックオフを使用し、JWT認証に`box_sdk_gen`を使用しています。ファイルIDとフィールドのリストはご自身のものに置き換えてください。

```python theme={null}
import json
import logging
import time
from concurrent.futures import ThreadPoolExecutor, as_completed

import requests
from box_sdk_gen import BoxJWTAuth, JWTConfig

logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger(__name__)

JWT_CONFIG_PATH = "config.json"
USER_ID = ""
FILE_IDS = []

NUM_PARALLEL_REQUESTS = 8
TIMEOUT_SECONDS = 120
MAX_RETRIES = 6
BACKOFF_INITIAL_SECONDS = 30
BACKOFF_MAX_SECONDS = 480

RETRYABLE_STATUS_CODES = [412, 429, 503, 544]

FIELDS = [
    {
        "type": "string",
        "key": "name",
        "displayName": "Name",
        "description": "The document name",
        "prompt": "What is the name of this document?",
    },
    {
        "type": "string",
        "key": "document_number",
        "displayName": "Document Number",
        "description": "The document number",
        "prompt": "The document number",
    },
]


def send_request(auth: BoxJWTAuth, file_id: str):
    response = None

    for attempt in range(MAX_RETRIES + 1):
        try:
            response = requests.post(
                "https://api.box.com/2.0/ai/extract_structured",
                headers={
                    "content-type": "application/json",
                    "authorization": f"Bearer {auth.retrieve_token().access_token}",
                },
                json={
                    "items": [{"id": file_id, "type": "file"}],
                    "fields": FIELDS,
                },
                timeout=TIMEOUT_SECONDS,
            )
        except Exception as exc:
            LOGGER.info("Transport error for file %s: %s", file_id, exc)
            response = None

        if response is not None and response.status_code == 200:
            break

        status = None if response is None else response.status_code
        if status is not None and status not in RETRYABLE_STATUS_CODES:
            break

        if attempt < MAX_RETRIES:
            wait_seconds = min(
                BACKOFF_INITIAL_SECONDS * (2 ** attempt),
                BACKOFF_MAX_SECONDS,
            )
            LOGGER.info(
                "Pacing retry for file %s in %ss (status %s)",
                file_id,
                wait_seconds,
                status,
            )
            time.sleep(wait_seconds)

    return response


if __name__ == "__main__":
    with open(JWT_CONFIG_PATH, "rt") as file:
        config_json = json.dumps(json.load(file))

    auth = BoxJWTAuth(
        config=JWTConfig.from_config_json_string(config_json_string=config_json)
    )
    if USER_ID:
        auth = auth.with_user_subject(USER_ID)

    results = []
    with ThreadPoolExecutor(max_workers=NUM_PARALLEL_REQUESTS) as executor:
        futures = {
            executor.submit(send_request, auth, fid): fid for fid in FILE_IDS
        }
        for future in as_completed(futures):
            try:
                results.append(future.result())
            except Exception as exc:
                LOGGER.info(
                    "Failed for file %s", futures[future], exc_info=exc
                )
                results.append(None)

    succeeded = sum(
        1 for r in results if r is not None and r.status_code == 200
    )
    LOGGER.info(
        "Batch complete — %s/%s files successfully processed.",
        succeeded,
        len(FILE_IDS),
    )
```

## ステータスコード

| ステータス | 動作 | 説明 |
| - | - | - |
| `412`, `429` | 指数バックオフと再試行 | クラスタのキャパシティを拡張中です。少し待ってから再開してください。 |
| `503`, `544` | バックオフによる再試行 | サービスまたは処理がタイムアウトしました。 |
| その他の`4xx` | リクエストの修正 | 権限、無効なファイルID、または不正な形式のJSON。 |
| その他の`5xx` | 調査 | 予期しないサーバーエラー。ステータスを確認するか、サポートに問い合わせます。 |

## サポート

抽出ジョブの失敗または遅延についてBoxサポートに問い合わせる場合は、以下の情報を含めてください。

* レスポンスに含まれている`request_id`
* HTTPステータスとレスポンス本文
* ファイルIDとEnterprise ID
* リクエストのタイムスタンプとタイムゾーン
* フィールド定義またはメタデータテンプレートキー
* 同時実行とクライアントタイムアウトの設定

**関連するガイド**

**関連するAPI**
