GPT-Live リアルタイム音声
GPT-Live WebSocket API を使用して、全二重の音声対話とセッションイベントを処理します。
GPT-Live は、持続的な WebSocket 接続を通じて全二重の音声対話を提供します。1 つのセッション内で、入力音声の送信、音声と文字起こしの受信、セッション設定の更新、複数ターンのコンテキスト維持を行えます。現在このドキュメントで扱うモデルは gpt-live-1 です。実際の利用可否は、現在のアカウントとモデルカタログによって異なります。
wss://api.agtcloud.cn/v1/live/sessions認証と接続
サーバーアプリケーションは、WebSocket ハンドシェイク Header で Bearer API キーを使用します。モデルは Query String には含めず、接続後の最初の session.start イベントで設定します。
| 項目 | 値 |
|---|---|
| 接続 URL | wss://api.agtcloud.cn/v1/live/sessions |
| Authorization | Bearer your-api-key |
| モデルの指定場所 | session.start.session.model |
| 入力音声 | Base64 でエンコードした生の PCM 音声チャンク |
長期 API キーは、信頼できるサーバーだけに保存してください。このページではこの WebSocket Route のみを扱い、WebRTC、SIP、Sideband 接続、セッション Fork、録音ダウンロードは扱いません。
セッションフロー
- Bearer API キーを使用して WebSocket 接続を確立します。
session.startを送信し、モデル、指示、音声、Delegation 方式を設定します。session.startedを受信してから、音声またはアプリケーションコマンドの送信を開始します。- 入力文字起こし、出力文字起こし、出力音声、Usage イベントを継続的に処理します。
- 音声の生成が停止したら、
session.closeを送信します。 - その後もイベントを受信し、
session.closedの最終 Usage を使用してセッションを終了処理します。
1 回の発話ターンの終了は、セッションの終了ではありません。複数ターンの会話では同じ接続を維持し、アプリケーションがセッション全体を終了する準備ができたときだけ session.close を送信してください。
開始パラメーター
| フィールド | 型 | 説明 |
|---|---|---|
type | string | 最初のフレームでは常に session.start |
event_id | string | エラーの関連付けに使用する任意のクライアントイベント識別子 |
session.model | string | 必須の GPT-Live モデル ID |
session.instructions | string | 任意の音声アシスタント向け指示 |
session.audio.format | object | 24 kHz の audio/pcm などの音声形式 |
session.audio.output.voice | string | marin などの出力 Voice。利用可能な値はモデルによって異なります |
session.delegation | object | client または responses を使用する Delegation 方式 |
接続開始後は session.update を使用して指示などの変更可能な設定を更新し、session.updated を待って変更を確認します。同じ接続で session.start を再送しないでください。また、Update イベントで最上位の音声モデルを切り替えないでください。
音声の準備と例
以下の例では、Header なし、モノラル、符号付き 16-bit little-endian、24 kHz の PCM 音声を使用します。100 ms ごとのチャンクは 4,800 Byte で、送信前に Base64 でエンコードします。
# 入力音声を準備
ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio.pcm
# 保存した出力音声を再生
ffplay -f s16le -ar 24000 -ac 1 reply.pcmNode.js の例では ws が必要です。Python の例はセッションの開始と終了のみを確認し、音声は送信しません。
npm install ws
python -m pip install websocket-clientimport fs from 'node:fs';
import WebSocket from 'ws';
const key = process.env.API_KEY;
const file = process.argv[2];
if (!key || !file) throw new Error('API_KEY と PCM ファイルパスが必要です');
const pcm = fs.readFileSync(file);
if (!pcm.length || pcm.length % 2) {
throw new Error('PCM は空でない 16-bit サンプル単位のデータである必要があります');
}
const ws = new WebSocket('wss://api.agtcloud.cn/v1/live/sessions', {
headers: { Authorization: 'Bearer ' + key },
handshakeTimeout: 10000,
});
const audioOut = fs.createWriteStream('reply.pcm');
let started = false;
let closing = false;
let finalized = false;
let sender;
let drain;
let offset = 0;
let inputFinishedAt = 0;
let lastOutputAt = 0;
function send(event) {
ws.send(JSON.stringify(event));
}
function closeSession(reason) {
if (closing) return;
closing = true;
clearInterval(sender);
console.error('リクエストを終了します:', reason);
if (started && ws.readyState === WebSocket.OPEN) {
send({ type: 'session.close' });
drain = setTimeout(() => ws.terminate(), 15000);
} else {
ws.terminate();
}
}
const limit = setTimeout(() => closeSession('60 秒のサンプル上限'), 60000);
ws.on('open', () => send({
type: 'session.start',
event_id: 'start_001',
session: {
model: 'gpt-live-1',
instructions: '日本語で簡潔に回答し、ユーザーが先に話すのを待ってください。',
audio: {
format: { type: 'audio/pcm', rate: 24000 },
output: { voice: 'marin' },
},
delegation: { type: 'client' },
},
}));
ws.on('message', (raw) => {
let event;
try {
event = JSON.parse(raw.toString());
} catch {
closeSession('無効な JSON');
return;
}
if (event.type !== 'session.output_audio.delta') {
console.log(raw.toString());
}
if (event.type === 'session.started' && !started) {
started = true;
sender = setInterval(() => {
if (closing || ws.readyState !== WebSocket.OPEN) return;
if (offset >= pcm.length) {
inputFinishedAt ||= Date.now();
if (
lastOutputAt &&
Date.now() - Math.max(lastOutputAt, inputFinishedAt) >= 3000
) {
closeSession('出力が完了しました');
return;
}
send({
type: 'session.input_audio.append',
audio: Buffer.alloc(4800).toString('base64'),
});
return;
}
const chunk = pcm.subarray(offset, offset + 4800);
offset += chunk.length;
send({
type: 'session.input_audio.append',
audio: chunk.toString('base64'),
});
}, 100);
}
if (event.type === 'session.output_audio.delta') {
lastOutputAt = Date.now();
audioOut.write(Buffer.from(event.delta, 'base64'));
}
if (event.type === 'session.output_transcript.delta') {
lastOutputAt = Date.now();
}
if (event.type === 'session.closed') {
finalized = true;
ws.close(1000);
}
if (event.type === 'error') closeSession('サービスエラー');
});
ws.on('error', (error) => {
console.error(error.message);
closeSession('接続エラー');
});
ws.on('close', (code, reason) => {
clearTimeout(limit);
clearTimeout(drain);
clearInterval(sender);
audioOut.end();
console.error({ code, reason: reason.toString(), finalized });
if (!finalized || code !== 1000) process.exitCode = 1;
});
process.on('SIGINT', () => closeSession('ユーザーによって停止されました'));イベントリファレンス
この API は、session.input_audio.append で Base64 音声を送信します。Muse の Binary 音声フレームと endStream、OpenAI Realtime の input_audio_buffer.* イベントは使用しません。
session.start
モデル、指示、音声、Delegation 設定を指定して GPT-Live セッションを開始します。
| フィールド | 型 | 必須 | 説明 | 例 |
|---|---|---|---|---|
type | string | はい | 常に `session.start` です。 | session.start |
event_id | string | いいえ | クライアントイベント識別子です。 | start_001 |
session.model | string | はい | GPT-Live モデル ID です。 | gpt-live-1 |
session.instructions | string | いいえ | 音声アシスタントの会話指示です。 | 日本語で簡潔に回答してください。 |
session.audio.format.type | string | いいえ | 音声形式の種類です。 | audio/pcm |
session.audio.format.rate | integer | いいえ | 音声のサンプルレートです。 | 24000 |
session.audio.output.voice | string | いいえ | 出力 Voice です。 | marin |
session.delegation.type | string | はい | Delegation 方式です。 | client |
{
"type": "session.start",
"event_id": "start_001",
"session": {
"model": "gpt-live-1",
"instructions": "日本語で簡潔に回答してください。",
"audio": {
"format": {
"type": "audio/pcm",
"rate": 24000
},
"output": {
"voice": "marin"
}
},
"delegation": {
"type": "client"
}
}
}使用量と課金
session.usage.updated は、1 ターン分の増分ではなく、現在のセッションにおける累積使用量を返します。複数段階の値を加算しないでください。通常終了時は、session.closed.usage.seconds を最終的なセッション時間として使用します。
料金には、音声セッション時間、Delegation 先モデルの使用量、ツール呼び出しが含まれる場合があります。現在の料金とモデルの利用可否は、モデルカタログで確認してください。
Delegation 方式
client:アプリケーション自身が Delegation されたタスクを処理します。基本的な音声セッションを初めて検証する場合は、この方式を使用します。responses:サーバーが設定された Responses モデルとツールを実行します。対象モデル、ツール、権限は現在のサイトで利用可能である必要があります。
responses では、Delegation 先モデルとツールの使用量に対して追加料金が発生する場合があります。例の MODEL_ID を常に利用できるモデルとして扱わないでください。
エラー処理
| 症状 | 確認事項 |
|---|---|
| ハンドシェイクが 401 を返す | Bearer API キー、モデル権限、現在のサイトアドレスを確認します |
| Path が 404 を返す | 最終的な接続 Path が /v1/live/sessions であり、/v1 が重複していないことを確認します |
| 最初のフレームでエラーまたは起動タイムアウト | 接続直後に session.start を送信し、session.started を受信してから音声を送信します |
| 音声がない、または音声が歪む | PCM の Bit Depth、Byte Order、サンプルレート、チャンネル数、WAV Header の除去を確認します |
session.closed イベントがない | 最後の累積 Usage、エラーイベント、終了理由を保存し、入力音声全体を自動再送しないでください |
| Delegation されたタスクが拒否される | Responses モデル、ツール、権限、現在のサイト機能を確認します |