AI活用

Grok Voice Agent APIの使い方|WebSocketでリアルタイム音声AIを作る方法

Grok Voice Agent APIをWebSocketから使う方法を解説。session.update、音声ストリーミング、VAD、割り込み、短期シークレット、Function Calling、録音・個人情報の注意点まで紹介します。

この記事の目次
  1. 結論:まずサーバー接続でイベントを確認し、ブラウザ版は短期トークンと明示的な承認を追加する
  2. Grok Voice Agent APIとは?
  3. Voice Agent APIが向いているWebアプリ
  4. Node.jsプロジェクトを準備する
  5. WebSocketへ接続する
  6. session.updateで設定する項目
  7. voice
  8. instructions
  9. turn_detection
  10. modalities
  11. マイク音声を送る流れ
  12. ブラウザから安全に接続する構成
  13. 音声を途中で割り込ませる
  14. Function CallingでWebサービスと連携する
  15. 検索と更新を同じ権限にしない
  16. Collections Searchと組み合わせる
  17. 録音・個人情報・同意の注意点
  18. 運用時に記録するログ
  19. 料金と遅延を抑える方法
  20. あわせて読みたい記事
  21. よくある質問
  22. まとめ
  23. 参考リンク

Grok Voice Agent APIを使うと、音声をストリーミングで送り、Grokの音声回答をリアルタイムに受け取るWebアプリや電話エージェントを作れます。WebSocket接続の中で、音声認識、会話、音声生成、ターン検出、ツール呼び出しをまとめて扱える点が特徴です。

一方、音声アプリは通常のチャットより設計項目が多くなります。APIキーの扱い、マイク権限、音声形式、割り込み、無音検出、録音の同意、個人情報、外部APIの実行、有人対応への切り替えを先に決める必要があります。

この記事では、Node.jsからGrok Voice Agent APIへWebSocket接続し、セッションを設定して音声イベントを扱う基本、ブラウザから安全に接続する構成、Function Callingを使ったWebサービス連携を解説します。

情報確認日:2026年7月11日(日本時間)

結論:まずサーバー接続でイベントを確認し、ブラウザ版は短期トークンと明示的な承認を追加する

この記事の結論

  • WebSocket URLはwss://api.x.ai/v1/realtime?model=grok-voice-latest
  • 音声モデルはgrok-voice-latestを利用する
  • 最初はNode.jsから接続し、イベントの送受信をログで確認する
  • ブラウザへ長期APIキーを渡さず、エフェメラルなクライアントシークレットを使う
  • 予約・購入・削除などのツール実行には、利用者の確認とサーバー側の権限検証を入れる

Grok Voice Agent APIとは?

Voice Agent APIは、音声会話を低遅延で実現するリアルタイムAPIです。利用者の音声を入力し、テキストへ変換してから別の音声合成APIへ渡す処理を個別に組み立てる代わりに、1つのセッションで会話を進められます。

主な機能は次のとおりです。

  • マイク音声のストリーミング入力
  • 音声またはテキストによるリアルタイム出力
  • サーバー側の音声区間検出(VAD)
  • 話している途中の割り込み
  • Function Callingによる外部処理
  • Web Search、X Search、Collections Search、MCPとの連携
  • SIPを使った電話システムとの接続

Voice Agent APIが向いているWebアプリ

用途 Voice Agentが担当すること 人間または既存システムが担当すること
製品サポート 質問の聞き取り、資料検索、手順案内 本人確認、契約変更、例外対応
予約受付 希望日時・人数・条件の収集 在庫確定、決済、最終承認
Webアプリ操作支援 画面の説明、検索条件の作成 重要操作の確認、権限判定
社内ヘルプデスク マニュアル検索、初期切り分け 端末操作、権限変更、有人エスカレーション

Node.jsプロジェクトを準備する

mkdir grok-voice-sample
cd grok-voice-sample
npm init -y
npm install ws dotenv
npm pkg set type=module

.envへAPIキーを保存します。

XAI_API_KEY=your_xai_api_key

.gitignoreへ追加します。

.env
.env.*
node_modules/
audio-output/

WebSocketへ接続する

最初は音声入力を送らず、テキストから音声応答を受け取ることで、認証とイベントの流れを確認します。

voice-text-test.jsを作成します。

import "dotenv/config";
import WebSocket from "ws";
import { mkdir, writeFile } from "node:fs/promises";

const apiKey = process.env.XAI_API_KEY;

if (!apiKey) {
  throw new Error("XAI_API_KEYが設定されていません");
}

await mkdir("./audio-output", { recursive: true });

const socket = new WebSocket(
  "wss://api.x.ai/v1/realtime?model=grok-voice-latest",
  {
    headers: {
      Authorization: `Bearer ${apiKey}`,
    },
  },
);

const audioChunks = [];

socket.on("open", () => {
  console.log("connected");

  socket.send(
    JSON.stringify({
      type: "session.update",
      session: {
        voice: "eve",
        instructions:
          "日本語で簡潔に回答してください。分からないことは推測しないでください。",
        turn_detection: {
          type: "server_vad",
        },
      },
    }),
  );

  socket.send(
    JSON.stringify({
      type: "conversation.item.create",
      item: {
        type: "message",
        role: "user",
        content: [
          {
            type: "input_text",
            text: "この音声APIでできることを30秒程度で説明してください。",
          },
        ],
      },
    }),
  );

  socket.send(
    JSON.stringify({
      type: "response.create",
      response: {
        modalities: ["audio", "text"],
      },
    }),
  );
});

socket.on("message", async (raw) => {
  const event = JSON.parse(raw.toString());

  switch (event.type) {
    case "response.output_text.delta":
      process.stdout.write(event.delta ?? "");
      break;

    case "response.output_audio.delta":
      if (event.delta) {
        audioChunks.push(Buffer.from(event.delta, "base64"));
      }
      break;

    case "response.done": {
      console.log("\nresponse completed");

      if (audioChunks.length > 0) {
        const audio = Buffer.concat(audioChunks);
        await writeFile("./audio-output/response.pcm", audio);
        console.log("audio-output/response.pcmへ保存しました");
      }

      socket.close();
      break;
    }

    case "error":
      console.error("Voice API error:", event);
      socket.close();
      break;

    default:
      console.log("event:", event.type);
  }
});

socket.on("error", (error) => {
  console.error("WebSocket error:", error);
});

イベント名や出力音声形式はAPI更新で変わる可能性があります。まずevent.typeを記録し、公式のRealtimeイベント一覧と実際のレスポンスを照合してください。

PCMファイルは、そのまま一般的な音楽プレーヤーで再生できない場合があります。セッションで設定したサンプルレート、チャンネル数、エンコーディングに合わせてWAVヘッダーを付けるか、Web Audio APIへ流してください。

session.updateで設定する項目

voice

音声の種類を選びます。利用できる音声名はモデルページで確認してください。音声を変えると、話す印象や聞き取りやすさが変わります。

instructions

役割、話す言語、長さ、禁止事項、有人対応へ切り替える条件を設定します。利用者の入力で上書きされない安全ルールは、サーバー側でも強制します。

turn_detection

server_vadを使うと、サーバー側が話し始めと話し終わりを検出します。騒音が多い環境では誤検出が起きるため、しきい値や無音時間を実機で調整します。

modalities

音声だけでなくテキストも受け取ると、字幕、ログ、監査、エラー調査に利用できます。

マイク音声を送る流れ

ブラウザまたはネイティブアプリでマイク音声を取得し、APIが受け付けるPCM形式へ変換して、短いチャンクとして送ります。

// audioChunkはAPI仕様に合わせたPCMバイト列
const base64Audio = Buffer.from(audioChunk).toString("base64");

socket.send(
  JSON.stringify({
    type: "input_audio_buffer.append",
    audio: base64Audio,
  }),
);

手動で発話を確定する構成では、音声の送信後にコミットと回答生成を要求します。

socket.send(
  JSON.stringify({
    type: "input_audio_buffer.commit",
  }),
);

socket.send(
  JSON.stringify({
    type: "response.create",
    response: {
      modalities: ["audio", "text"],
    },
  }),
);

server_vadを使う場合は、サーバーがターンを確定して回答を開始する構成もあります。VADと手動コミットを混在させないよう、セッション設計を統一してください。

ブラウザから安全に接続する構成

ブラウザのJavaScriptへ長期APIキーを埋め込むと、開発者ツールや通信から取得されます。xAIが案内するエフェメラルなクライアントシークレットを、自分のサーバーから短時間だけ発行して渡します。

1. 利用者が自分のWebアプリへログイン
2. ブラウザが自分の /api/voice-session を呼ぶ
3. サーバーが利用者の権限・回数上限を確認
4. サーバーがxAIから短期クライアントシークレットを取得
5. ブラウザへ短期シークレットだけを返す
6. ブラウザがWebSocketへ直接接続
7. 接続終了または期限切れでシークレットを無効化

ブラウザ接続では、クライアントシークレットをWebSocketのサブプロトコルとして渡す方法が案内されています。具体的な発行エンドポイントと有効期限は、最新のVoice Agentドキュメントに合わせて実装してください。

短期シークレットでも、利用者認証と利用上限は必要です。公開エンドポイントから誰でも発行できる状態にすると、第三者に音声APIを利用されます。

音声を途中で割り込ませる

音声エージェントが長く話している途中で利用者が話し始めた場合は、出力を止めて新しい入力を優先します。割り込みを実装しないと、AIと利用者が同時に話し続ける状態になります。

実装では次を管理します。

  • 現在再生中の音声バッファ
  • サーバー側の回答生成状態
  • 利用者が話し始めた時刻
  • 再生済みの音声位置
  • 回答のキャンセルイベント

クライアント側の再生停止だけでは、サーバーが回答を生成し続ける場合があります。公式イベントを使い、生成と再生の両方をキャンセルします。

Function CallingでWebサービスと連携する

音声会話から予約検索やチケット作成を行う場合は、セッションへツールを定義します。次は空き時間を検索する例です。

const sessionUpdate = {
  type: "session.update",
  session: {
    voice: "eve",
    instructions: `
あなたは予約受付アシスタントです。
空き時間は必ずcheck_availabilityを使って確認してください。
予約確定は利用者が日時を復唱して同意した後だけ行ってください。
`,
    tools: [
      {
        type: "function",
        name: "check_availability",
        description: "指定日の予約可能な時間帯を取得する",
        parameters: {
          type: "object",
          properties: {
            date: {
              type: "string",
              description: "YYYY-MM-DD形式の日付",
            },
            people: {
              type: "integer",
              minimum: 1,
              maximum: 20,
            },
          },
          required: ["date", "people"],
          additionalProperties: false,
        },
      },
    ],
    turn_detection: {
      type: "server_vad",
    },
  },
};

socket.send(JSON.stringify(sessionUpdate));

モデルからツール呼び出しイベントを受け取ったら、引数を検証して自分のAPIを実行し、結果を会話へ返します。

function validateAvailabilityArgs(args) {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(args.date)) {
    throw new Error("dateの形式が不正です");
  }

  if (!Number.isInteger(args.people) || args.people < 1 || args.people > 20) {
    throw new Error("peopleの値が不正です");
  }

  return args;
}

検索と更新を同じ権限にしない

空き時間の検索は読み取り専用ですが、予約確定は在庫を変更します。ツールを分け、予約確定では利用者の明示的な同意、本人確認、重複防止キーを要求します。

Collections Searchと組み合わせる

製品マニュアルや社内手順をCollectionsへ登録すると、音声サポートエージェントが資料を検索して回答できます。

  • 利用者の質問を聞き取る
  • Collections Searchで関連箇所を取得する
  • 出典に書かれた範囲だけで回答する
  • 画面にも文書名やリンクを表示する
  • 資料にない場合は有人窓口へ切り替える

音声だけでは引用元を確認しづらいため、Web画面に字幕と参照文書を同時表示する設計が安全です。

録音・個人情報・同意の注意点

  • 会話がAIで処理されることを開始前に伝える
  • 録音する場合は目的、保存期間、閲覧者、削除方法を示す
  • カード番号、パスワード、認証コードなどを音声で収集しない
  • 本人確認が必要な操作は既存の安全な認証フローへ戻す
  • 医療、金融、法律、人事などは専門家・担当者への切り替えを用意する
  • 誤認識された文字起こしを確定情報として保存しない

運用時に記録するログ

障害調査のため、秘密情報を除外したうえで次を記録します。

  • セッションID、開始・終了時刻、処理時間
  • 接続・切断・再接続の理由
  • イベントタイプとエラーコード
  • ツール呼び出し名、成否、承認の有無
  • 有人対応へ切り替えた理由
  • 利用分数と概算費用

音声データや文字起こしをログへ残す場合は、必要性を明確にし、アクセス権と保存期間を短くしてください。

料金と遅延を抑える方法

Voice Agent APIの料金は音声時間やテキスト処理に応じて発生します。最新単価はxAIの料金ページで確認してください。

  • 無音状態の接続を長時間維持しない
  • セッションの最大時間を設ける
  • AIの回答を短く設定する
  • 検索やツール呼び出しの回数を制限する
  • 同じ案内はアプリ側の固定音声も検討する
  • 複雑な処理は「後で画面に結果を表示する」非同期フローへ分ける

あわせて読みたい記事

よくある質問

ブラウザからxAI APIへ直接接続できますか?

長期APIキーを渡すのではなく、サーバーで発行した短期のクライアントシークレットを使う構成が案内されています。発行前に自分のサービスの利用者認証と上限確認を行います。

日本語で会話できますか?

セッションのinstructionsで日本語を指定して試せます。固有名詞、騒音、方言などを含む実際の利用環境で認識率を評価してください。

電話の自動応答にも使えますか?

SIP連携が案内されています。電話番号、通信事業者、録音同意、転送、緊急時の有人対応まで含めて設計します。

AIに予約を自動確定させてもよいですか?

検索と確定を別ツールにし、確定前に日時・人数・料金を復唱して利用者の同意を得ます。サーバー側でも認証、在庫、重複、権限を検証してください。

会話内容を保存する必要がありますか?

必須ではありません。品質改善や監査で保存する場合も、目的、同意、保存期間、削除方法を決め、不要な音声や個人情報を残さないようにします。

まとめ

Grok Voice Agent APIは、WebSocketを通じて音声入力、音声回答、ターン検出、検索、ツール実行を1つのリアルタイム会話へまとめられます。最初はNode.jsでテキスト入力とイベント受信を確認し、その後にマイク音声とブラウザ接続を追加すると切り分けやすくなります。

実用化で重要なのは、話せることよりも安全に止められることです。短期シークレット、利用上限、割り込み、重要操作の確認、録音同意、個人情報の除外、有人対応への切り替えを用意してください。

参考リンク