AGTCloud API Docs
文件指南API Reference整合與資源更新與幫助
即時互動

GPT-Live 即時語音

透過 GPT-Live WebSocket 進行全雙工語音互動並處理工作階段事件。

GPT-Live 透過持久 WebSocket 提供全雙工語音互動,可在同一工作階段中傳送輸入音訊、接收語音與轉錄結果、更新工作階段設定並維持多輪上下文。目前模型記錄為 gpt-live-1,實際可用範圍以目前帳戶和模型目錄為準。

WSS
wss://api.agtcloud.cn/v1/live/sessions

認證與連線

伺服器端應用程式在 WebSocket 握手 Header 中使用 Bearer API Key。模型不放在查詢參數中,而是在連線後的第一個 session.start 事件中設定。

項目值
連線位址wss://api.agtcloud.cn/v1/live/sessions
AuthorizationBearer your-api-key
模型位置session.start.session.model
輸入音訊Base64 編碼的裸 PCM 音訊區塊

長期 API Key 只應保存在可信伺服器端。本文只覆蓋該 WebSocket 路由,不包含 WebRTC、SIP、sideband、工作階段 fork 或錄音下載。

工作階段流程

  1. 使用 Bearer API Key 建立 WebSocket 連線。
  2. 傳送 session.start,設定模型、指令、音訊和委託方式。
  3. 收到 session.started 後開始傳送音訊或應用程式命令。
  4. 持續處理輸入轉錄、輸出轉錄、輸出音訊和用量事件。
  5. 停止產生音訊後傳送 session.close。
  6. 繼續接收事件,以 session.closed 中的最終用量完成收尾。

說話輪次結束不等於工作階段結束。多輪對話應保持同一連線,只有業務邏輯準備結束整個工作階段時才傳送 session.close。

啟動參數

欄位類型說明
typestring首個訊框固定為 session.start
event_idstring可選的使用者端事件識別碼,用於關聯錯誤
session.modelstring必填,GPT-Live 模型 ID
session.instructionsstring可選的語音助手指令
session.audio.formatobject音訊格式,例如 audio/pcm、24 kHz
session.audio.output.voicestring輸出音色,例如 marin;可選值取決於模型
session.delegationobject委託方式,使用 client 或 responses

連線啟動後可以使用 session.update 更新指令等允許修改的工作階段設定,並等待 session.updated 確認。不要在同一連線上再次傳送 session.start,也不要透過更新事件切換頂層語音模型。

音訊準備與呼叫範例

範例採用 PCM signed 16-bit little-endian、24 kHz、單聲道音訊,不包含 WAV 檔頭。每 100 ms 為 4800 位元組,傳送時將每個音訊區塊編碼為 Base64。

# 準備輸入音訊
ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio.pcm

# 播放保存的輸出音訊
ffplay -f s16le -ar 24000 -ac 1 reply.pcm

Node.js 範例需要 ws;Python 範例只檢查工作階段啟動與關閉,不傳送音訊。

npm install ws
python -m pip install websocket-client
WSSGPT-Live 工作階段範例
import 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('使用者已停止'));

事件 Reference

本 API 使用 session.input_audio.append 傳送 Base64 音訊,不使用 Muse 的二進位音訊訊框和 endStream,也不使用 OpenAI Realtime 的 input_audio_buffer.* 事件。

用戶端事件 · Session

session.start

啟動 GPT-Live 工作階段並提交模型、指令、音訊和委派設定。

欄位類型必填說明範例值
type字串是固定為 session.start。session.start
event_id字串否用戶端事件標識。start_001
session.model字串是GPT-Live 模型 ID。gpt-live-1
session.instructions字串否語音助手的對話指令。請用簡短中文回答。
session.audio.format.type字串否音訊格式類型。audio/pcm
session.audio.format.rate整數否音訊取樣率。24000
session.audio.output.voice字串否輸出音色。marin
session.delegation.type字串是委派方式。client
JSON 範例
{
  "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 回傳的是目前工作階段的累計值,不表示單獨一輪的新增用量。使用者端不應把多個階段值相加;正常關閉時,以 session.closed.usage.seconds 為最終工作階段時長。

費用可能包含語音工作階段時長、後端模型用量和工具呼叫。具體價格及目前模型可用範圍以模型廣場為準。

委託方式

  • client:應用自行處理需要委託的任務,適合先驗證基礎語音工作階段。
  • responses:由伺服器端執行設定的 Responses 模型和工具。目標模型、工具和權限必須在目前站點可用。

使用 responses 時,後端任務的模型和工具費用可能獨立產生。不要把範例中的 MODEL_ID 視為固定可用模型。

錯誤處理

現象檢查與處理
握手回傳 401檢查 Bearer API Key、模型權限和目前站點網址
路徑回傳 404確認最終連線路徑為 /v1/live/sessions,沒有重複拼接 /v1
首個事件錯誤或啟動逾時連線後先傳送 session.start,收到 session.started 後再傳送音訊
無聲音或聲音失真檢查 PCM 位深、位元組序、取樣率、聲道和 WAV 檔頭
未收到 session.closed保存最後一個累計用量、錯誤事件和關閉原因,不自動重放整段音訊
後端委派被拒絕檢查 Responses 模型、工具、權限和目前站點支援範圍

參考資料