DEV Community

Cover image for Grok 4.6 APIリクエストをテスト・デバッグする方法 (ストリーミング、ツールコール、エラー処理)
Akira
Akira

Posted on Originally published at apidog.com

Grok 4.6 APIリクエストをテスト・デバッグする方法 (ストリーミング、ツールコール、エラー処理)

Grok 4.6は長時間実行されるエージェント向けに構築されています。そのため、統合の障害モードはデバッグが最も難しい箇所に現れます。たとえば、トークン途中で停止するストリーミング応答、ほぼパース可能なツール呼び出しペイロード、本番負荷でのみ発生するレート制限です。xAIのドキュメントはAPIが受け付ける内容を説明していますが、実際にどうテスト・切り分けするかは別問題です。本記事では、リクエスト検証、ストリーム検査、ツール呼び出しのデバッグ、エラー処理、CIでトークンを消費しないためのGrok応答モックまでを実装手順として整理します。

今すぐApidogを試す

ここでは、LLM APIのデバッグ、SSEレンダリング、環境スコープのシークレット、応答アサーション、モックサーバーを1つの作業環境で扱える Apidog を使用します。手動で設定する場合にも考え方はそのまま応用できますが、画面上の操作はApidog固有です。

TL;DR

  • https://api.x.ai/v1XAI_API_KEY を環境変数として管理し、保存済みリクエストにAPIキーをハードコードしない。
  • SSEを可視化して、ストリーム停止・切り捨て・クライアント側の描画停止を切り分ける。
  • tool_calls[].function.arguments はJSONとしてパースし、毎回スキーマ検証する。
  • 429 は指数関数的バックオフ、5xx は回数制限付きリトライで処理する。
  • 全応答の usage をログに残し、トークン数やコストの変化を追跡する。
  • CIではGrokエンドポイントをモックし、ライブAPIのテストは夜間またはリリース前に分離する。
  • デバッグ時に作成したリクエストとアサーションを、そのまま自動テストシナリオに昇格させる。

1. ワークスペースと環境を分離する

最初の確認だけなら curl でも十分です。しかし、失敗するリクエストを複数パターンで比較し始めると、ヘッダー、モデルID、パラメーター、環境差分の管理が破綻します。最初に環境を分離してください。

セットアップ手順

  1. Apidogでプロジェクトを作成します。例: Grok 4.6 インテグレーション
  2. xai-dev 環境を作成します。
  3. 次の環境変数を追加します。
変数 備考
base_url https://api.x.ai/v1 ベースURL
api_key <あなたのキー> シークレットとして扱う
  1. 次のリクエストを保存します。
POST {{base_url}}/chat/completions
Authorization: Bearer {{api_key}}
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode
{
  "model": "grok-4-6",
  "messages": [
    {
      "role": "user",
      "content": "動作確認です。短く応答してください。"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
  1. xai-dev を複製し、xai-prod を作成します。
  2. 本番用のキーは xai-prod にだけ設定します。

これで同じ保存済みリクエストを使いながら、開発中の試行で本番クォータを消費する事故を避けられます。

まだキーを生成していない場合は、Grok 4.6 APIクイックスタート を参照してください。console.x.ai でのセットアップと、curl、Python、JavaScriptでの最初のリクエストを確認できます。

2. モデルを疑う前にリクエストを検証する

リクエストが失敗したときは、まず単純で再現性の高い原因から確認します。次の順番で切り分けると効率的です。

2.1 モデルIDを確認する

ネイティブAPIではモデルIDは grok-4-6 です。ただし、リセラー経由では表記が異なる場合があります。たとえばOpenRouterでは x-ai/grok-4.6 を使用します。

モデルIDやエンドポイントが間違っている場合、まず 404 を疑ってください。サービス障害と判断する前に、対象環境のモデルIDを確認します。

2.2 パラメーター範囲を確認する

次のような値は 400 の原因になります。

  • 範囲外の temperature
  • 残りコンテキストを超える max_tokens
  • 型が異なる値
  • 必須フィールドの欠落

400 を受けたら、リトライする前にレスポンス本文を確認してください。不正なリクエストをリトライしても回復しません。

2.3 messages の構造を確認する

messages 配列は、エージェントループが長くなるほど壊れやすくなります。特に以下を検査対象にしてください。

  • 空の content を持つ不要なメッセージ
  • 重複したシステムプロンプト
  • 想定外のロール
  • ツール実行結果を追加し忘れた会話履歴
  • 同じ履歴を二重に追加する処理

これらは必ずしもエラーになりません。しかし、品質低下として現れるため、見つけにくい不具合になります。

2.4 コンテキスト使用量をログに残す

Grok 4.6のコンテキストウィンドウは500Kトークンですが、無制限ではありません。長いエージェント履歴に大きな max_tokens を予約すると、上限に近づきます。

レスポンスの usage を毎回記録してください。

function logUsage(response, requestId) {
  console.log({
    requestId,
    usage: response.usage,
    finishReason: response.choices?.[0]?.finish_reason
  });
}
Enter fullscreen mode Exit fullscreen mode

少なくとも次を監視対象にします。

  • プロンプトトークン数
  • 出力トークン数
  • 合計トークン数
  • タスクあたりの累積トークン数
  • finish_reason

Apidogのリクエスト検証を使うと、型違い・必須フィールド不足などを送信前に検出でき、構造的なミスの調査時間を減らせます。

3. SSEストリーミングを手探りでデバッグしない

Grok 4.6の応答はサーバー送信イベント(SSE)としてストリーミングできます。エージェント応答は数千トークンに達することもあるため、通常レスポンスよりも途中状態の観測が重要です。

ストリーミングの問題は、主に次の3パターンに分かれます。

3.1 応答が途中で止まる

ターミナルだけでは、「モデルがまだ生成中」なのか「チャンクが届かなくなった」のかを区別しにくいことがあります。

ApidogのSSEビューで同じリクエストを再現し、以下を確認します。

  • チャンクの到着自体が停止したか
  • チャンクは届いているが、アプリが描画・消費していないか
  • 特定のチャンクで接続が閉じたか
  • 最終イベントが到達したか

チャンクが止まっているなら、サーバー・ネットワーク・プロキシ・タイムアウトを確認します。チャンクが届いているなら、クライアントの非同期処理やレンダリング処理を調べます。

3.2 ストリームが正常終了するが短い

ストリームがきれいに閉じても、期待より短いことがあります。その場合は最後のチャンクにある finish_reason を確認してください。

finish_reason 対応
length max_tokens に達した可能性がある。上限やプロンプト長を見直す。
stop モデルが生成を完了した。

長い多段階の応答を期待する場合、length を見落とすと「モデルが途中で回答を放棄した」と誤認しやすくなります。

3.3 ステージング環境だけストリーミングしない

ローカルでは動くのにステージングで停止する場合、リバースプロキシのバッファリングを疑ってください。SSEはプロキシのデフォルト設定でバッファリングされることがあります。

nginxでは、ストリーミング対象パスに次の設定が必要になる場合があります。

location /api/chat {
  proxy_buffering off;
  proxy_pass http://your-upstream;
}
Enter fullscreen mode Exit fullscreen mode

Apidogからローカル環境とステージング環境に同じリクエストを送り、到着チャンクを比較してください。ローカルでのみストリーミングされるなら、xAI側ではなくインフラ側の問題です。

4. ツール呼び出しを防御的に処理する

エージェント連携では、通常のテキスト出力よりツール呼び出しのほうが壊れやすい箇所です。特に tool_calls[].function.arguments はJSON*文字列*として届くため、受信後の処理を明示的に分ける必要があります。

4.1 JSONパース失敗を記録する

引数文字列には、末尾カンマやエスケープされていない引用符など、ほぼJSONに見える不正な値が含まれることがあります。

パース失敗を握りつぶさず、ツール名・リクエストID・引数の長さとともに記録してください。

function parseToolArguments(rawArguments, toolName) {
  try {
    return JSON.parse(rawArguments);
  } catch (error) {
    console.error("tool_arguments_parse_failed", {
      toolName,
      argumentLength: rawArguments.length,
      error: error.message
    });

    throw new Error(`ツール引数をJSONとして解析できません: ${toolName}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

パース失敗率が上昇した場合は、プロンプト、ツール定義、スキーマ、またはストリーム組み立て処理の変更を確認してください。

4.2 パース後もスキーマ検証する

JSONとして有効でも、ツールが期待する形とは限りません。

たとえば、以下のような値はJSONとしては正しいものの、ツール実行には不適切です。

{
  "userId": "42"
}
Enter fullscreen mode Exit fullscreen mode

userId に数値を期待している場合、パース成功だけで実行してはいけません。必須フィールド、型、列挙値、追加フィールドを検証してください。

function validateGetUserArgs(args) {
  if (!Number.isInteger(args.userId)) {
    throw new Error("userId は整数である必要があります");
  }

  return args;
}
Enter fullscreen mode Exit fullscreen mode

開発環境だけでなく、本番でも検証を継続してください。

4.3 未定義ツールを明示的に拒否する

モデルが定義していないツール名を返すケースもあります。ツールディスパッチを辞書参照だけにすると、KeyErrorundefined is not a function のような形でエージェントループが停止します。

許可リストを作り、未知のツール名を明示的に拒否してください。

const tools = {
  get_user: getUser,
  search_docs: searchDocs
};

async function executeTool(name, args) {
  const tool = tools[name];

  if (!tool) {
    throw new Error(`未許可のツール呼び出しです: ${name}`);
  }

  return tool(args);
}
Enter fullscreen mode Exit fullscreen mode

4.4 ストリーミング中は引数を結合してからパースする

ストリーミング応答では、ツール引数が複数チャンクに分割されることがあります。断片を受け取るたびに JSON.parse() すると、不正JSONを生成したように見えてしまいます。

以下の順番を守ってください。

  1. ツール呼び出しIDごとに引数断片を蓄積する
  2. ストリーム終了またはツール呼び出し完了を確認する
  3. 結合済み文字列をパースする
  4. スキーマ検証する
  5. ツールを実行する
const argumentBuffers = new Map();

function appendToolArguments(toolCallId, fragment) {
  const current = argumentBuffers.get(toolCallId) ?? "";
  argumentBuffers.set(toolCallId, current + fragment);
}

function completeToolArguments(toolCallId) {
  const rawArguments = argumentBuffers.get(toolCallId) ?? "";
  argumentBuffers.delete(toolCallId);

  return JSON.parse(rawArguments);
}
Enter fullscreen mode Exit fullscreen mode

Apidogでは、ツール呼び出しを含む成功レスポンスを保存し、次のアサーションを追加してください。

  • ツール名が許可されたセットに含まれる
  • arguments が文字列として存在する
  • 引数文字列をJSONとしてパースできる
  • パース後の値がスキーマ条件を満たす

1回だけでなく、少なくとも10回は実行します。LLMの非決定性により、単発実行では低頻度の失敗を見逃します。

MCPサーバーを使用している場合も同じ考え方です。詳細は Apidogを使用したMCPサーバーのテストガイド を参照してください。

5. エラー・リトライ・レート制限のポリシーを実装する

本番環境では、ステータスコードごとに明示的なポリシーが必要です。

ステータス 意味 ポリシー
400 不正なリクエスト リトライしない。ログを確認し、リクエストを修正する。
401 キーが不正、または存在しない リトライしない。環境変数とコンソール上のキーを確認する。
404 モデルIDまたはエンドポイントが誤っている リトライしない。/v1/models と比較して確認する。
429 レート制限またはクォータ超過 指数関数的バックオフとジッターでリトライする。Retry-After があれば優先する。
5xx サーバーサイドエラー バックオフ付きで最大3回リトライし、それでも失敗したらタスクを明示的に失敗させる。
タイムアウト 生成時間の延長またはネットワーク問題 ストリーミングを使用する。エージェント呼び出しのタイムアウトは秒ではなく分単位で設計する。

4295xx 用のリトライ例

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function requestWithRetry(sendRequest, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await sendRequest();

    if (response.ok) {
      return response;
    }

    const retryable = response.status === 429 || response.status >= 500;

    if (!retryable || attempt === maxRetries) {
      throw new Error(`Grok API request failed: ${response.status}`);
    }

    const retryAfter = response.headers.get("retry-after");
    const baseDelay = retryAfter
      ? Number(retryAfter) * 1000
      : 500 * 2 ** attempt;

    const jitter = Math.floor(Math.random() * 250);
    await sleep(baseDelay + jitter);
  }
}
Enter fullscreen mode Exit fullscreen mode

注意点は2つあります。

  1. リリース直後など負荷が高い時期は、一時的な 4295xx が増える可能性があります。デモ前ではなく、実装時点でバックオフを組み込んでください。
  2. すべてのレスポンスで usage をログに残してください。100万トークンあたり2ドル/6ドルという料金でも、エージェントループでは呼び出し回数が増幅されます。プロンプト変更によるコスト悪化は、請求書より先にトークンログで検出できます。

料金モデルの詳細は Grok料金分析 を参照してください。

6. CIではGrokをモックし、ライブAPIは分離する

コミットごとにライブモデルを呼び出すCIは、遅く、不安定で、継続的にコストが発生します。

たとえば30回のGrok呼び出しを含むエージェント統合テストは、1分以上かかることがあり、プロバイダーの一時的な不安定さでランダムに失敗します。その状態が続くと、開発チームはテスト失敗を信用しなくなります。

テストを2種類に分離してください。

6.1 コミットごとのCI: モックでロジックを検証する

Apidogのスマートモックで、実際のGrok形式に近いレスポンスを用意します。

最低限、次のケースを作成してください。

  • 通常の完了レスポンス
  • ツール呼び出しを含むレスポンス
  • 429 レスポンス
  • 5xx レスポンス
  • finish_reason: length の応答
  • 途中で終了するストリーム
  • パース不能なツール引数
  • 未定義ツール名

これにより、次の処理をコミットごとに数秒で検証できます。

  • リトライロジック
  • JSONパース失敗処理
  • スキーマ検証
  • エージェントループの終了条件
  • エラー表示とログ出力

特に 429 の経路は必ずモックしてください。本番で発生するまでテストされないコードになりがちです。

6.2 夜間またはリリース前: ライブAPIを検証する

ライブAPIを使うテストは、毎コミットではなく夜間またはリリース前に実行します。

ライブテストで確認する対象は次のとおりです。

  • プロバイダー側のレスポンス形式の変化
  • ツール呼び出し形式の変化
  • 新しいレート制限
  • 実際のストリーミング挙動
  • 認証・モデルID・エンドポイントの有効性

Apidogのテストシナリオでは、同じアサーションを使いながら接続先環境だけを切り替えられます。

  • CI: モック環境
  • 定期実行: xai-dev

ターミナルやパイプラインから実行する場合は、Apidog CLI で同じシナリオをヘッドレス実行できます。

本番稼働前チェックリスト

Grok 4.6のトラフィックを有効にする前に、以下を確認してください。

  • [ ] APIキーは環境スコープで管理され、開発環境と本番環境が分離されている
  • [ ] APIキーはバージョン管理システムに含まれていない
  • [ ] ストリーミングで停止、finish_reason: length、プロキシバッファリングを処理できる
  • [ ] ツール引数は毎回、防御的にパースされている
  • [ ] ツール引数はパース後にスキーマ検証されている
  • [ ] 未定義ツール名を明示的に拒否している
  • [ ] 4295xx のリトライポリシーを実装している
  • [ ] リトライ処理をモック経由でテストしている
  • [ ] すべてのリクエストで usage を記録している
  • [ ] タスクあたりのコスト変動を検出できる
  • [ ] CIはモック環境に対して実行される
  • [ ] ライブAPIスイートは夜間またはリリース前に実行される
  • [ ] 次のモデルリリース時に、テストスイート全体を1コマンドで再実行できる

よくある質問

Grok 4.6のストリーミング応答がハングアップした場合、どのようにデバッグすればよいですか?

ApidogのSSEビューで同じリクエストを再現してください。チャンクの到着が止まっているなら、サーバー・ネットワーク・プロキシ・タイムアウトを確認します。チャンクが到着し続けているなら、クライアント側が消費または描画を停止しているため、バッファリングと非同期処理を確認してください。

Grok 4.6のツール呼び出しが時々パースに失敗するのはなぜですか?

関数引数はJSON文字列として到着し、不正な形式になることがあります。また、ストリーミングされたツール呼び出しは断片から結合してからパースする必要があります。引数を早くパースしすぎることが、最も多い自己原因の1つです。防御的なパース、断片の結合、スキーマ検証をすべて実装してください。

テストで実際のGrok APIを呼び出すべきですか?

夜間またはリリース前のスケジュール実行では呼び出してください。プロバイダーの変動を検出できます。一方、コミットごとのCIではエンドポイントをモックしてください。CIを高速・決定論的・低コストに保てます。

このワークフローは他のLLM APIにも適用できますか?

はい。GrokのAPIはOpenAIと互換性があるため、プロバイダーごとに環境を分けた同じApidogプロジェクト構造で、GPT-5.6、Claude、Grokを並行して扱えます。モデル比較を行う場合も、同じテストシナリオとアサーションを利用できます。

Top comments (0)