Azure AI Studio で Mistral OCR(mistral-ocr-2503)をデプロイしたのに、/v1/ocr にPDFやJPEGを送ると 200 OK なのに body が null。パブリック API では成功する…。本記事では Azure 側で null が出る原因と、動作報告の多いリクエスト形式、リトライやフォールバックまで実運用の対策をまとめます。
現象:HTTP 200 なのにレスポンスが null/空になる
Azure 上の Mistral OCR デプロイ(例:mistral-ocr-2503 / mistral-ocr-2503-deployment)で、次のような挙動に遭遇するケースがあります。
- レスポンスは HTTP 200 OK だが、レスポンスボディが null(または空の JSON)
- 同じ PDF を Mistral のパブリック API に送ると問題なく OCR 結果が返る
- JPEG を送っても 200 / null
- 同一ファイルでも 1回目は null、2回目以降は成功 といった不安定な再現がある
- PDF によって成功・失敗が分かれ、エラー内容が分からない
この手の「200 だけど実質失敗」は、クライアント側が気づきにくく、運用に乗せると障害になりやすいのが厄介です。まずは、Azure 側が期待するリクエストスキーマに寄せるところから改善できる可能性があります。
原因の本命:Azure では「image_url」ではなく「document.document_url」中心に解釈される
ポイントは、Mistral パブリック API と Azure 上の推論エンドポイントで、受け付ける JSON の形が完全一致しないことがある点です。
特に「動いた」と報告されているのは、入力を次のように送るパターンです。
- トップレベルに
image_urlを置かない documentオブジェクトを作り、その中のdocument_urlに data URL(Base64)を入れるdocument.typeは入力種別に応じてimage_url/document_urlを切り替える
逆に、次のような指定は Azure 側では正しく解釈されず、200 / null になりやすいと考えられます。
image_urlフィールドに Base64 を直接入れるtypeにimage/documentのような値を入れる(Azure 側の想定とズレる)- Base64 の前に MIME 付きの
data:...;base64,を付けない
まず試すべき解決策:document_url と type を正しく組み合わせる
ここからは、「まずこれで改善しやすい」形を、画像と PDF で分けて紹介します。重要なのは、Base64 を data URL として渡すことと、フィールド名・type を揃えることです。
画像(JPEG/PNG)を Base64 で送る例
画像は type を image_url にし、document_url には data:image/jpeg;base64,(PNGなら data:image/png;base64,)を付与します。
{
"model": "mistral-ocr-2503-deployment",
"document": {
"document_url": "data:image/jpeg;base64,{your_base_64_image_data}",
"type": "image_url"
},
"include_image_base64": true
}
- Base64 の前に data:image/jpeg;base64, を付ける
- プロパティ名は image_url ではなく document_url
typeは “image_url”(”image” ではない)
PDF を Base64 で送る例
PDF は document_url に data:application/pdf;base64, を付け、type を document_url に切り替える、という報告が複数あります。
{
"model": "mistral-ocr-2503-deployment",
"document": {
"document_url": "data:application/pdf;base64,{your_base_64_pdf_data}",
"type": "document_url"
},
"include_image_base64": true
}
document_urlに data:application/pdf;base64, を必ず付けるtypeは “document_url”(PDF なのに “image_url” を使わない)
入力種別ごとの推奨値(早見表)
| 入力 | document.type | document.document_url の先頭 | よくある落とし穴 |
|---|---|---|---|
| JPEG | image_url | data:image/jpeg;base64, | type: "image"、prefix なし |
| PNG | image_url | data:image/png;base64, | JPEG と同様に prefix なし |
document_url | data:application/pdf;base64, | type: "image_url" のまま |
Base64 を安全に作る(改行混入を防ぐ)
「フォーマットは合っているはずなのに null」が続くとき、意外と多いのが Base64 に改行が混ざっているケースです。特にコマンドラインで作った Base64 は、環境によって自動的に折り返されます。
| 環境 | 例(PDF を Base64 にする) | ポイント |
|---|---|---|
| PowerShell | $bytes = [System.IO.File]::ReadAllBytes("sample.pdf") $base64 = [System.Convert]::ToBase64String($bytes) $payload = "data:application/pdf;base64,$base64" | 1行の Base64 が得られやすい |
| Linux / macOS(GNU coreutils) | base64 -w 0 sample.pdf | -w 0 で改行なし |
| Linux / macOS(互換用) | base64 sample.pdf | tr -d '\n' | 改行を除去して1行化 |
| Node.js | import fs from "fs"; const b64 = fs.readFileSync("sample.pdf").toString("base64"); const payload = `data:application/pdf;base64,${b64}`; | ファイルをそのまま base64 化できる |
Base64 を作ったら、念のため次もチェックすると安全です。
- 先頭が
data:application/pdf;base64,/data:image/jpeg;base64,になっている - 文字列中に空白・改行が含まれていない(
\nが混ざっていない) - サイズが極端に大きくない(JSON のサイズが膨らむため)
「200 / null」を作りやすい間違いパターン
Azure 側は、入力が想定とズレると明確なエラーを返さずに null を返すように見えることがあります。実際のトラブルシュートでは、次を潰すだけで改善することが多いです。
| よくある指定 | 起きやすいこと | 修正例 |
|---|---|---|
image_url フィールドに Base64 を入れる | 200 / null | document.document_url に移す |
type: "image" / "document" | 解釈されず null | 画像は "image_url"、PDF は "document_url" |
| Base64 に prefix を付けない | MIME 判定できず失敗 | data:...;base64, を付ける |
| Base64 に改行やスペースが混入 | 解析失敗・不安定 | 改行なしの1行文字列にする |
| モデル名とデプロイ名を混同 | 別モデル扱い/意図しない挙動 | Azure AI Studio の「デプロイ名」と一致させる |
切り分け手順:まず「公開 URL」か「小さな入力」で動作確認する
Base64 の PDF をいきなり投げると、原因が「データ」「フォーマット」「デプロイの状態」「一時的な不安定さ」のどれなのかが見えにくくなります。最短で切り分けるなら、次の順序がおすすめです。
- 公開アクセス可能な画像 URL(社内の認証なし CDN や、テスト用に置いた画像)を
document_urlに指定して試す - 成功したら、同じ JSON 形のまま data URL(Base64)に切り替える
- それでも null なら、入力を 小さな1枚画像や 1ページPDF にして、サイズ・ページ数要因を切り分ける
「公開 URL では成功するが Base64 だと null」の場合は、Base64 文字列の prefix、改行混入、サイズ超過などの可能性が濃厚です。
REST(cURL)での最小サンプル
SDK を使う前に、REST で最小のリクエストが通るか確認すると早いです。エンドポイントのパスは Azure AI Studio のデプロイ画面に表示されるものを優先してください(例:/v1/models/{deployment}/infer 系)。
PDF(data URL)を送る例
curl -X POST "$AZUREAI_ENDPOINT/v1/models/mistral-ocr-2503-deployment/infer" \
-H "Content-Type: application/json" \
-H "api-key: $AZUREAI_API_KEY" \
-d '{
"model": "mistral-ocr-2503-deployment",
"document": {
"type": "document_url",
"document_url": "data:application/pdf;base64,{your_base_64_pdf_data}"
},
"include_image_base64": true
}'
もし /v1/ocr を叩く場合の考え方
環境によっては「/v1/ocr を叩いている」ケースもあります。この場合も、JSON の document 部分(document_url + type)を揃えるのが重要です。パスが違っても「入力の与え方」を正すと改善することがあります。
環境によってはヘッダーが Authorization: Bearer(Entra ID トークン)方式の場合もあります。ポータルの「Keys and Endpoint」やサンプルを見て、期待される認証方式に合わせてください。
include_image_base64 は必要なときだけ true にする
include_image_base64 を true にすると、OCR 結果に加えて画像の Base64 が返ってきます。便利な一方、レスポンスが巨大化しやすく、ネットワークやクライアントの制限で「受け取り失敗」につながることがあります。
- テキスト抽出だけが目的なら、まずは false(または省略)で安定性を優先
- デバッグ用途で画像が必要なときだけ true
「null が返る」症状が不安定なときは、まず include_image_base64 を切って試すのも有効です(入力・出力ともに軽くなり、処理が通りやすくなる可能性があります)。
Python(Azure 公式クライアント)での呼び出し例
Azure 側の SDK はバージョンによってメソッド名や引数が変わることがありますが、重要なのは送信する JSON の document 部分です。ここでは分かりやすい形で例を示します。
from azure.ai.inference import InferenceClient
import os
endpoint = os.environ.get("AZUREAI_ENDPOINT")
api_key = os.environ.get("AZUREAI_API_KEY")
client = InferenceClient(endpoint=endpoint, api_key=api_key)
# PDF を data URL で送る例
response = client.chat_completions.create(
model="mistral-ocr-2503-deployment",
document={
"type": "document_url",
"document_url": "data:application/pdf;base64,{your_base_64_pdf_data}"
},
include_image_base64=True
)
print(response)
ここで model に入れる値は、Azure AI Studio 上で作成したデプロイ名と一致させるのが安全です(表示名・モデル名・デプロイ名が似ているので混同しやすい点に注意)。
それでも null が出る/成功したり失敗したりする場合の対処
フォーマットを正しても、次のような不安定さが残ることがあります。
- 同じ PDF でも 1回目だけ null(2回目は成功)
- PDF によって成功・失敗が分かれる
- リージョンを変えても改善しないことがある(例:Sweden Central でも変化なしという報告)
この場合は、サービス側の一時的な不安定さやウォームアップ、内部タイムアウトのような要因を疑い、アプリ側で「落ちにくい設計」に寄せるのが現実的です。
null/空レスポンスを「失敗」とみなすリトライ設計
HTTP 200 でも null は業務上は失敗です。以下の条件を満たすときは、短時間で 1〜2回のリトライを入れると改善することがあります。
- レスポンスボディが
null/{}/ OCR 結果の主要フィールドが欠落 - サーバーの明確なエラーがない
| 推奨 | 理由 | 実装のコツ |
|---|---|---|
| 最大 2 回まで | 一時要因なら 2 回目で回復する報告がある | 失敗時だけ実行し、無限リトライは避ける |
| 待機 300ms → 1s 程度 | 短時間での再試行が効くケースがある | ジッター(乱数)を入れてスパイクを避ける |
| 入力は完全に同一 | フォーマット差を混ぜると原因が追えない | リクエストのハッシュをログに残す |
実装例(Node.js / fetch の疑似コード)
async function ocrWithRetry(payload, maxRetry = 2) {
for (let attempt = 0; attempt <= maxRetry; attempt++) {
const res = await fetch(process.env.AZUREAI_ENDPOINT + "/v1/models/mistral-ocr-2503-deployment/infer", {
method: "POST",
headers: {
"Content-Type": "application/json",
"api-key": process.env.AZUREAI_API_KEY
},
body: JSON.stringify(payload)
});
const text = await res.text();
const data = text ? JSON.parse(text) : null;
const ok = data && data.choices && data.choices.length > 0; // 例:返り値形式に合わせて判定
if (ok) return data;
if (attempt === maxRetry) return data; // 最後はそのまま返して上位でフォールバック
await new Promise(r => setTimeout(r, 300 + Math.random() * 400));
}
}
上記はあくまで考え方の例です。実際のレスポンス形式(どのフィールドに OCR 結果が入るか)は、Azure のデプロイが返すスキーマに合わせて判定条件を調整してください。
「1回目だけ失敗」を減らすウォームアップ
同じ PDF で 1回目だけ null になる現象は、デプロイ直後や長時間アクセスがない時に起きるウォームアップ(コールドスタート)に近い挙動に見えます。運用では次が効くことがあります。
- アプリ起動時に 小さな画像を 1回投げてデプロイを温める
- 定期実行(数十分〜数時間おき)で軽いリクエストを投げ、アイドル状態を避ける
- ユーザーの本番リクエストでは、最初から リトライ前提で UX を設計する(「処理中」を出す等)
フォールバック:別リージョン/別 OCR を用意して止血する
業務で「null が返る可能性」をゼロにできないなら、フォールバックを組み込む価値があります。選択肢は大きく2つです。
- 同じ Mistral OCR を 別リージョンにデプロイして切り替える
- Azure Document Intelligence など、別ベンダーの OCRへ切り替える
フォールバックを入れるとコストは増えますが、ユーザー体験(失敗率)を一定に保てるため、SLA があるシステムでは現実的な落としどころになります。
入力データ側のワークアラウンド(PDF が不安定なとき)
PDF は内容によって解析コストが大きく変わります。特定の PDF だけ失敗する場合は、次の観点で前処理すると改善することがあります。
- ページ数が多い場合は 分割(例:50ページ単位)して順次 OCR
- スキャン画像が重い場合は解像度を下げる(読み取り可能な範囲で)
- パスワード保護、フォーム、埋め込みオブジェクトがある PDF は「印刷して PDF」相当で単純化
- 回転があるページは事前に回転補正
制限値として「最大 1,000 ページ/最大 50MB」が案内されることがありますが、上限ギリギリは失敗しやすくなるため、実運用では余裕を持たせるのが無難です。
運用で困らないためのチェックリスト
最後に、現場で「原因不明の null」を減らすためのチェック項目をまとめます。デバッグ時は表の上から順に潰すのがおすすめです。
| チェック項目 | 確認ポイント | 補足 |
|---|---|---|
| デプロイ名 | model が Azure AI Studio のデプロイ名と一致している | モデル名(mistral-ocr-2503)とデプロイ名が別の場合がある |
| フィールド名 | document.document_url を使っている | image_url に Base64 を入れない |
| type | 画像は image_url、PDF は document_url | 値の表記ゆれ(image/document)は避ける |
| data URL prefix | data:...;base64, が付いている | PDF は application/pdf、JPEG は image/jpeg |
| Base64 の整形 | 改行・空白が入っていない | ファイルから生成した Base64 をそのまま貼ると改行が混ざることがある |
| 入力サイズ | サイズとページ数が現実的な範囲 | 上限内でも大きいと不安定になりやすい |
| include_image_base64 | 必要性がなければ false(省略)にする | レスポンスが軽くなり安定性が上がる可能性 |
| リトライ | null/空 JSON を検知して 1〜2 回リトライ | タイムアウトや 5xx と同等に扱う |
| フォールバック | 継続失敗時に別 OCR へ切り替え | SLA があるなら必須級 |
サポートにエスカレーションするためのログの残し方
「同じ入力で再現するのに 200 / null」という症状は、サービス側の調査が必要になることがあります。サポートに渡せる情報を増やすため、次をログに残しておくと有利です。
- リクエストのタイムスタンプ(UTC 推奨)
- デプロイ名、リージョン、エンドポイント
- 入力ファイルのハッシュ(SHA-256 など)とサイズ、ページ数
- レスポンスヘッダーのリクエスト ID(取得できる場合)
- アプリ側のリトライ回数と最終結果
また、オンラインデプロイのログ(CLI やポータルから閲覧できる範囲)も合わせて保存し、再現条件が揃ったタイミングで問い合わせると解決が早くなります。
まとめ:Azure 上の Mistral OCR を「まず動かす」→「安定させる」
- 最優先で見直すのは リクエスト形式:
document.document_url+ data URL(Base64)+正しいtype - 画像は
type: "image_url"、PDF はtype: "document_url"の切り替えが鍵 - 200 / null は失敗として扱い、短いリトライとフォールバックで運用品質を担保する
- 特定 PDF だけ失敗するなら、分割・単純化など入力側の前処理も有効
- まずは小さな入力で成功を確認し、段階的に本番条件へ広げる
最小構成で「正しいフォーマットで結果が返る」状態を作り、そこから本番データ・運用要件に合わせて堅牢化していくのが、遠回りに見えて最短ルートです。

コメント